Browse Source

Merge pull request #5682 from nocodb/enhancement/case-insensitive-formula

enhancement: case insensitive formula
pull/5699/head
Raju Udava 1 year ago committed by GitHub
parent
commit
7a78c526e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 28
      packages/nc-gui/components/smartsheet/column/FormulaOptions.vue
  2. 2
      packages/nocodb-sdk/src/lib/formulaHelpers.ts
  3. 9
      packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
  4. 2
      packages/nocodb/src/db/mapFunctionName.ts
  5. 14
      tests/playwright/tests/db/columnFormula.spec.ts

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

@ -149,28 +149,29 @@ function parseAndValidateFormula(formula: string) {
function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = new Set()) {
if (parsedTree.type === JSEPNode.CALL_EXP) {
const calleeName = parsedTree.callee.name.toUpperCase()
// validate function name
if (!availableFunctions.includes(parsedTree.callee.name)) {
errors.add(`'${parsedTree.callee.name}' function is not available`)
if (!availableFunctions.includes(calleeName)) {
errors.add(`'${calleeName}' function is not available`)
}
// validate arguments
const validation = formulas[parsedTree.callee.name] && formulas[parsedTree.callee.name].validation
const validation = formulas[calleeName] && formulas[calleeName].validation
if (validation && validation.args) {
if (validation.args.rqd !== undefined && validation.args.rqd !== parsedTree.arguments.length) {
errors.add(`'${parsedTree.callee.name}' required ${validation.args.rqd} arguments`)
errors.add(`'${calleeName}' required ${validation.args.rqd} arguments`)
} else if (validation.args.min !== undefined && validation.args.min > parsedTree.arguments.length) {
errors.add(`'${parsedTree.callee.name}' required minimum ${validation.args.min} arguments`)
errors.add(`'${calleeName}' required minimum ${validation.args.min} arguments`)
} else if (validation.args.max !== undefined && validation.args.max < parsedTree.arguments.length) {
errors.add(`'${parsedTree.callee.name}' required maximum ${validation.args.max} arguments`)
errors.add(`'${calleeName}' required maximum ${validation.args.max} arguments`)
}
}
parsedTree.arguments.map((arg: Record<string, any>) => validateAgainstMeta(arg, errors))
// validate data type
if (parsedTree.callee.type === JSEPNode.IDENTIFIER) {
const expectedType = formulas[parsedTree.callee.name].type
const expectedType = formulas[calleeName.toUpperCase()].type
if (expectedType === formulaTypes.NUMERIC) {
if (parsedTree.callee.name === 'WEEKDAY') {
if (calleeName === 'WEEKDAY') {
// parsedTree.arguments[0] = date
validateAgainstType(
parsedTree.arguments[0],
@ -202,7 +203,7 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
parsedTree.arguments.map((arg: Record<string, any>) => validateAgainstType(arg, expectedType, null, typeErrors))
}
} else if (expectedType === formulaTypes.DATE) {
if (parsedTree.callee.name === 'DATEADD') {
if (calleeName === 'DATEADD') {
// parsedTree.arguments[0] = date
validateAgainstType(
parsedTree.arguments[0],
@ -236,7 +237,7 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
},
typeErrors,
)
} else if (parsedTree.callee.name === 'DATETIME_DIFF') {
} else if (calleeName === 'DATETIME_DIFF') {
// parsedTree.arguments[0] = date
validateAgainstType(
parsedTree.arguments[0],
@ -504,8 +505,9 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
typeErrors.add(`${formulaTypes.NUMERIC} type is found but ${expectedType} type is expected`)
}
} else if (parsedTree.type === JSEPNode.CALL_EXP) {
if (formulas[parsedTree.callee.name]?.type && expectedType !== formulas[parsedTree.callee.name].type) {
typeErrors.add(`${expectedType} not matched with ${formulas[parsedTree.callee.name].type}`)
const calleeName = parsedTree.callee.name.toUpperCase()
if (formulas[calleeName]?.type && expectedType !== formulas[calleeName].type) {
typeErrors.add(`${expectedType} not matched with ${formulas[calleeName].type}`)
}
}
return typeErrors
@ -514,7 +516,7 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
function getRootDataType(parsedTree: any): any {
// given a parse tree, return the data type of it
if (parsedTree.type === JSEPNode.CALL_EXP) {
return formulas[parsedTree.callee.name].type
return formulas[parsedTree.callee.name.toUpperCase()].type
} else if (parsedTree.type === JSEPNode.IDENTIFIER) {
const col = supportedColumns.value.find((c) => c.title === parsedTree.name) as Record<string, any>
if (col?.uidt === UITypes.Formula) {

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

@ -173,7 +173,7 @@ export function jsepTreeToFormula(node) {
'SWITCH',
'URL',
];
if (!formulas.includes(node.name)) return '{' + node.name + '}';
if (!formulas.includes(node.name.toUpperCase())) return '{' + node.name + '}';
return node.name;
}

9
packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts

@ -548,11 +548,11 @@ async function _formulaQueryBuilder(
const colAlias = a ? ` as ${a}` : '';
pt.arguments?.forEach?.((arg) => {
if (arg.fnName) return;
arg.fnName = pt.callee.name;
arg.fnName = pt.callee.name.toUpperCase();
arg.argsCount = pt.arguments?.length;
});
if (pt.type === 'CallExpression') {
switch (pt.callee.name) {
switch (pt.callee.name.toUpperCase()) {
case 'ADD':
case 'SUM':
if (pt.arguments.length > 1) {
@ -631,13 +631,14 @@ async function _formulaQueryBuilder(
break;
}
const calleeName = pt.callee.name.toUpperCase();
return {
builder: knex.raw(
`${pt.callee.name}(${(
`${calleeName}(${(
await Promise.all(
pt.arguments.map(async (arg) => {
let query = (await fn(arg)).builder.toQuery();
if (pt.callee.name === 'CONCAT') {
if (calleeName === 'CONCAT') {
if (knex.clientType() !== 'sqlite3') {
query = await convertDateFormatForConcat(
arg,

2
packages/nocodb/src/db/mapFunctionName.ts

@ -20,7 +20,7 @@ export interface MapFnArgs {
}
const mapFunctionName = async (args: MapFnArgs): Promise<any> => {
const name = args.pt.callee.name;
const name = args.pt.callee.name.toUpperCase();
let val;
switch (args.knex.clientType()) {

14
tests/playwright/tests/db/columnFormula.spec.ts

@ -134,6 +134,20 @@ const formulaDataByDbType = (context: NcContext) => [
formula: `IF((SEARCH({Address List}, "Parkway") != 0), "2.0","WRONG")`,
result: ['WRONG', 'WRONG', 'WRONG', '2.0', '2.0'],
},
// additional tests for formula case-insensitivity
{
formula: `weekday("2022-07-19")`,
result: ['1', '1', '1', '1', '1'],
},
{
formula: `Weekday("2022-07-19")`,
result: ['1', '1', '1', '1', '1'],
},
{
formula: `WeekDay("2022-07-19")`,
result: ['1', '1', '1', '1', '1'],
},
];
test.describe('Virtual Columns', () => {

Loading…
Cancel
Save