Browse Source

fix: extract type of id/foreign key using sqlui

pull/7268/head
Pranav C 11 months ago
parent
commit
0ad6b17298
  1. 3
      packages/nc-gui/components/smartsheet/column/FormulaOptions.vue
  2. 40
      packages/nocodb-sdk/src/lib/formulaHelpers.spec.ts
  3. 72
      packages/nocodb-sdk/src/lib/formulaHelpers.ts
  4. 37
      packages/nocodb/src/services/columns.service.ts

3
packages/nc-gui/components/smartsheet/column/FormulaOptions.vue

@ -105,8 +105,9 @@ const validators = {
if (!formula?.trim()) return reject(new Error('Required'))
try {
validateFormulaAndExtractTreeWithType(formula, supportedColumns.value)
validateFormulaAndExtractTreeWithType({ formula, columns: supportedColumns.value, clientOrSqlUi: sqlUi.value })
} catch (e: any) {
console.log(e)
if (e instanceof FormulaError && e.extra?.key) {
return reject(new Error(t(e.extra.key, e.extra)))
}

40
packages/nocodb-sdk/src/lib/formulaHelpers.spec.ts

@ -6,29 +6,35 @@ import UITypes from './UITypes';
describe('Formula parsing and type validation', () => {
it('Simple formula', async () => {
const result = validateFormulaAndExtractTreeWithType('1 + 2', []);
const result = validateFormulaAndExtractTreeWithType({
formula: '1 + 2',
columns: [],
clientOrSqlUi: 'mysql2',
});
expect(result.dataType).toEqual(FormulaDataTypes.NUMERIC);
});
it('Formula with IF condition', async () => {
const result = validateFormulaAndExtractTreeWithType(
'IF({column}, "Found", BLANK())',
[
const result = validateFormulaAndExtractTreeWithType({
formula: 'IF({column}, "Found", BLANK())',
columns: [
{
id: 'cid',
title: 'column',
uidt: UITypes.Number,
},
]
);
],
clientOrSqlUi: 'mysql2',
});
expect(result.dataType).toEqual(FormulaDataTypes.STRING);
});
it('Complex formula', async () => {
const result = validateFormulaAndExtractTreeWithType(
'SWITCH({column2},"value1",IF({column1}, "Found", BLANK()),"value2", 2)',
[
const result = validateFormulaAndExtractTreeWithType({
formula:
'SWITCH({column2},"value1",IF({column1}, "Found", BLANK()),"value2", 2)',
columns: [
{
id: 'id1',
title: 'column1',
@ -39,14 +45,15 @@ describe('Formula parsing and type validation', () => {
title: 'column2',
uidt: UITypes.SingleLineText,
},
]
);
],
clientOrSqlUi: 'mysql2',
});
expect(result.dataType).toEqual(FormulaDataTypes.STRING);
const result1 = validateFormulaAndExtractTreeWithType(
'SWITCH({column2},"value1",IF({column1}, 1, 2),"value2", 2)',
[
const result1 = validateFormulaAndExtractTreeWithType({
formula: 'SWITCH({column2},"value1",IF({column1}, 1, 2),"value2", 2)',
columns: [
{
id: 'id1',
title: 'column1',
@ -57,8 +64,9 @@ describe('Formula parsing and type validation', () => {
title: 'column2',
uidt: UITypes.SingleLineText,
},
]
);
],
clientOrSqlUi: 'mysql2',
});
expect(result1.dataType).toEqual(FormulaDataTypes.NUMERIC);
});

72
packages/nocodb-sdk/src/lib/formulaHelpers.ts

@ -3,6 +3,14 @@ import jsep from 'jsep';
import { ColumnType } from './Api';
import UITypes from './UITypes';
import dayjs from 'dayjs';
import {
MssqlUi,
MysqlUi,
OracleUi,
PgUi,
SnowflakeUi,
SqlUiFactory,
} from './sqlUi';
// todo: move to date utils and export, remove duplicate from gui
@ -242,6 +250,7 @@ export enum FormulaDataTypes {
COND_EXP = 'conditional_expression',
NULL = 'null',
BOOLEAN = 'boolean',
UNKNOWN = 'unknown',
}
export enum JSEPNode {
@ -1176,10 +1185,30 @@ export class FormulaError extends Error {
}
}
export function validateFormulaAndExtractTreeWithType(
export function validateFormulaAndExtractTreeWithType({
formula,
columns: ColumnType[]
) {
columns,
clientOrSqlUi,
getMeta,
}: {
formula: string;
columns: ColumnType[];
clientOrSqlUi:
| 'mysql'
| 'pg'
| 'sqlite3'
| 'mssql'
| 'mysql2'
| 'oracledb'
| 'mariadb'
| 'sqlite'
| MysqlUi
| MssqlUi
| SnowflakeUi
| PgUi
| OracleUi;
getMeta?: (tableId: string) => Promise<any>;
}) {
const colAliasToColMap = {};
const colIdToColMap = {};
@ -1270,7 +1299,9 @@ export function validateFormulaAndExtractTreeWithType(
if (
argTypes.some(
(argType) =>
argType !== expectedArgType && argType !== FormulaDataTypes.NULL
argType !== expectedArgType &&
argType !== FormulaDataTypes.NULL &&
argType !== FormulaDataTypes.UNKNOWN
)
) {
let key = '';
@ -1317,8 +1348,14 @@ export function validateFormulaAndExtractTreeWithType(
validateFormulaAndExtractTreeWithType(
// formula may include double curly brackets in previous version
// convert to single curly bracket here for compatibility
col.colOptions.formula.replaceAll('{{', '{').replaceAll('}}', '}'),
columns
{
formula: col.colOptions.formula
.replaceAll('{{', '{')
.replaceAll('}}', '}'),
columns,
clientOrSqlUi,
getMeta,
}
);
res.dataType = (formulaRes as any)?.dataType;
@ -1368,7 +1405,26 @@ export function validateFormulaAndExtractTreeWithType(
case UITypes.ID:
case UITypes.ForeignKey:
{
res.dataType = FormulaDataTypes.NUMERIC;
const sqlUI =
typeof clientOrSqlUi === 'string'
? SqlUiFactory.create(clientOrSqlUi)
: clientOrSqlUi;
if (sqlUI) {
const abstractType = sqlUI.getAbstractType(col);
if (['integer', 'float', 'decimal'].includes(abstractType)) {
res.dataType = FormulaDataTypes.NUMERIC;
} else if (['boolean'].includes(abstractType)) {
res.dataType = FormulaDataTypes.BOOLEAN;
} else if (
['date', 'datetime', 'time', 'year'].includes(abstractType)
) {
res.dataType = FormulaDataTypes.DATE;
} else {
res.dataType = FormulaDataTypes.STRING;
}
} else {
res.dataType = FormulaDataTypes.UNKNOWN;
}
}
break;
// not supported
@ -1379,8 +1435,8 @@ export function validateFormulaAndExtractTreeWithType(
case UITypes.Collaborator:
case UITypes.QrCode:
default:
res.dataType = FormulaDataTypes.UNKNOWN;
break;
// throw new FormulaError(FormulaErrorType.NOT_SUPPORTED, {});
}
}
} else if (parsedTree.type === JSEPNode.LITERAL) {

37
packages/nocodb/src/services/columns.service.ts

@ -210,10 +210,11 @@ export class ColumnsService {
colBody.formula_raw || colBody.formula,
table.columns,
);
colBody.parsed_tree = validateFormulaAndExtractTreeWithType(
colBody.formula_raw || colBody.formula,
table.columns,
);
colBody.parsed_tree = validateFormulaAndExtractTreeWithType({
formula: colBody.formula_raw || colBody.formula,
columns: table.columns,
clientOrSqlUi: source.type,
});
try {
const baseModel = await reuseOrSave('baseModel', reuse, async () =>
@ -938,10 +939,11 @@ export class ColumnsService {
]);
await FormulaColumn.update(c.id, {
formula_raw: new_formula_raw,
parsed_tree: validateFormulaAndExtractTreeWithType(
new_formula_raw,
table.columns,
),
parsed_tree: validateFormulaAndExtractTreeWithType({
formula: new_formula_raw,
columns: table.columns,
clientOrSqlUi: source.type,
}),
});
}
}
@ -1003,10 +1005,11 @@ export class ColumnsService {
]);
await FormulaColumn.update(c.id, {
formula_raw: new_formula_raw,
parsed_tree: validateFormulaAndExtractTreeWithType(
new_formula_raw,
table.columns,
),
parsed_tree: validateFormulaAndExtractTreeWithType({
formula: new_formula_raw,
columns: table.columns,
clientOrSqlUi: source.type,
}),
});
}
}
@ -1216,13 +1219,15 @@ export class ColumnsService {
colBody.formula_raw ||
colBody.formula?.replaceAll('{{', '{').replaceAll('}}', '}'),
);
colBody.parsed_tree = validateFormulaAndExtractTreeWithType(
colBody.parsed_tree = validateFormulaAndExtractTreeWithType({
// formula may include double curly brackets in previous version
// convert to single curly bracket here for compatibility
colBody.formula_raw ||
formula:
colBody.formula_raw ||
colBody.formula?.replaceAll('{{', '{').replaceAll('}}', '}'),
table.columns,
);
columns: table.columns,
clientOrSqlUi: source.type,
});
try {
const baseModel = await reuseOrSave('baseModel', reuse, async () =>

Loading…
Cancel
Save