From d5122cf7af2c74f41734cd4ea61348623422d201 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 20 May 2023 17:11:48 +0800 Subject: [PATCH 1/4] enhancement(nocodb): allow formula callee name case-insensitive --- .../nocodb/src/db/formulav2/formulaQueryBuilderv2.ts | 9 +++++---- packages/nocodb/src/db/mapFunctionName.ts | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts b/packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts index 08e5ef8588..3c5e49d503 100644 --- a/packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts +++ b/packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts @@ -578,11 +578,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) { @@ -661,13 +661,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, diff --git a/packages/nocodb/src/db/mapFunctionName.ts b/packages/nocodb/src/db/mapFunctionName.ts index 669442e157..c351cabbbc 100644 --- a/packages/nocodb/src/db/mapFunctionName.ts +++ b/packages/nocodb/src/db/mapFunctionName.ts @@ -20,7 +20,7 @@ export interface MapFnArgs { } const mapFunctionName = async (args: MapFnArgs): Promise => { - const name = args.pt.callee.name; + const name = args.pt.callee.name.toUpperCase(); let val; switch (args.knex.clientType()) { From c1796a78f58db3fa0ff6e626e2517638a22f56d2 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 20 May 2023 17:11:55 +0800 Subject: [PATCH 2/4] enhancement(sdk): allow formula callee name case-insensitive --- packages/nocodb-sdk/src/lib/formulaHelpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nocodb-sdk/src/lib/formulaHelpers.ts b/packages/nocodb-sdk/src/lib/formulaHelpers.ts index 0ddb2f4376..d002977eba 100644 --- a/packages/nocodb-sdk/src/lib/formulaHelpers.ts +++ b/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; } From 46c64b58eae8617e88a9248ed323d64fa6e5054b Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 20 May 2023 17:12:01 +0800 Subject: [PATCH 3/4] enhancement(nc-gui): allow formula callee name case-insensitive --- .../smartsheet/column/FormulaOptions.vue | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue b/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue index beb16b58be..6889cfae2e 100644 --- a/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue +++ b/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) => 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) => 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 if (col?.uidt === UITypes.Formula) { From 80702eac01416b74070aa71b8f859cd9a5e180e4 Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Mon, 22 May 2023 16:29:15 +0530 Subject: [PATCH 4/4] test: formula case insensitive Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> --- tests/playwright/tests/db/columnFormula.spec.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/playwright/tests/db/columnFormula.spec.ts b/tests/playwright/tests/db/columnFormula.spec.ts index 4aabefe37e..18ddb182af 100644 --- a/tests/playwright/tests/db/columnFormula.spec.ts +++ b/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', () => {