From 6c71afe28d4e9d7412b903ec9c86a3da1d3b8201 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 7 Jan 2023 19:52:49 +0800 Subject: [PATCH 01/12] fix(nocodb-sdk): revise parsing identifier name logic --- packages/nocodb-sdk/src/lib/formulaHelpers.ts | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/nocodb-sdk/src/lib/formulaHelpers.ts b/packages/nocodb-sdk/src/lib/formulaHelpers.ts index ffa7964488..7103bac4d1 100644 --- a/packages/nocodb-sdk/src/lib/formulaHelpers.ts +++ b/packages/nocodb-sdk/src/lib/formulaHelpers.ts @@ -19,10 +19,7 @@ export const jsepCurlyHook = { context.index += 1; env.node = { type: jsep.IDENTIFIER, - // column name with space would break it down to jsep.IDENTIFIER + jsep.LITERAL - // either take node.name for jsep.IDENTIFIER - // or take node.value for jsep.LITERAL - name: nodes.map((node) => node.name || node.value).join(' '), + name: nodes.map((node) => parseIdentifierName(node)).join(''), }; return env.node; } else { @@ -33,6 +30,23 @@ export const jsepCurlyHook = { }, } as jsep.IPlugin; +function parseIdentifierName(node) { + if (node.type === 'Identifier') { + // e.g. col + return node.name; + } else if (node.type === 'BinaryExpression') { + // e.g. col-1 would be considered as col (left), - (operator), 1 (right) + return ( + parseIdentifierName(node.left) + + node.operator + + parseIdentifierName(node.right) + ); + } else if (node.type === 'Literal') { + // e.g col (identifier) + 123 (literal) + return node.value; + } +} + export async function substituteColumnAliasWithIdInFormula( formula, columns: ColumnType[] From bcb6031db6d6d95510539c57c8fadf146bef34d9 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Mon, 9 Jan 2023 17:18:17 +0800 Subject: [PATCH 02/12] chore(nocodb): add formula comment --- .../lib/sql/formulav2/formulaQueryBuilderv2.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts index b9a47522d1..b9ec6658e6 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts @@ -54,7 +54,10 @@ export default async function formulaQueryBuilderv2( ) { // register jsep curly hook jsep.plugins.register(jsepCurlyHook); - const tree = jsep(_tree); + + // formula may include double curly brackets in previous version + // convert to single curly bracket here for compatibility + const tree = jsep(_tree.replaceAll('{{', '{').replaceAll('}}', '}')); const columnIdToUidt = {}; From 8eb8d9d5dc75ea31ab063381dddb61eeeecab5bf Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Mon, 9 Jan 2023 17:26:26 +0800 Subject: [PATCH 03/12] fix(nocodb-sdk): revise jsepCurlyHook logic to handle column names with special characters --- packages/nocodb-sdk/src/lib/formulaHelpers.ts | 55 +++++++++++-------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/packages/nocodb-sdk/src/lib/formulaHelpers.ts b/packages/nocodb-sdk/src/lib/formulaHelpers.ts index 7103bac4d1..ef082a2516 100644 --- a/packages/nocodb-sdk/src/lib/formulaHelpers.ts +++ b/packages/nocodb-sdk/src/lib/formulaHelpers.ts @@ -8,19 +8,35 @@ export const jsepCurlyHook = { jsep.hooks.add('gobble-token', function gobbleCurlyLiteral(env) { const OCURLY_CODE = 123; // { const CCURLY_CODE = 125; // } + let start = -1; + let end = -1; const { context } = env; if ( !jsep.isIdentifierStart(context.code) && context.code === OCURLY_CODE ) { + if (start == -1) { + start = context.index; + } context.index += 1; - const nodes = context.gobbleExpressions(CCURLY_CODE); + context.gobbleExpressions(CCURLY_CODE); if (context.code === CCURLY_CODE) { + if (start != -1 && end == -1) { + end = context.index; + } context.index += 1; - env.node = { - type: jsep.IDENTIFIER, - name: nodes.map((node) => parseIdentifierName(node)).join(''), - }; + if (start != -1 && end != -1) { + env.node = { + type: jsep.IDENTIFIER, + name: /{{(.*?)}}/.test(context.expr) + ? // start would be the position of the first curly bracket + // add 2 to point to the first character for expressions like {{col1}} + context.expr.slice(start + 2, end) + : // start would be the position of the first curly bracket + // add 1 to point to the first character for expressions like {col1} + context.expr.slice(start + 1, end), + }; + } return env.node; } else { context.throwError('Unclosed }'); @@ -30,23 +46,6 @@ export const jsepCurlyHook = { }, } as jsep.IPlugin; -function parseIdentifierName(node) { - if (node.type === 'Identifier') { - // e.g. col - return node.name; - } else if (node.type === 'BinaryExpression') { - // e.g. col-1 would be considered as col (left), - (operator), 1 (right) - return ( - parseIdentifierName(node.left) + - node.operator + - parseIdentifierName(node.right) - ); - } else if (node.type === 'Literal') { - // e.g col (identifier) + 123 (literal) - return node.value; - } -} - export async function substituteColumnAliasWithIdInFormula( formula, columns: ColumnType[] @@ -74,7 +73,11 @@ export async function substituteColumnAliasWithIdInFormula( }; // register jsep curly hook jsep.plugins.register(jsepCurlyHook); - const parsedFormula = jsep(formula); + const parsedFormula = jsep( + // formula may include double curly brackets in previous version + // convert to single curly bracket here for compatibility + formula.replaceAll('{{', '{').replaceAll('}}', '}') + ); await substituteId(parsedFormula); return jsepTreeToFormula(parsedFormula); } @@ -109,7 +112,11 @@ export function substituteColumnIdWithAliasInFormula( // register jsep curly hook jsep.plugins.register(jsepCurlyHook); - const parsedFormula = jsep(formula); + const parsedFormula = jsep( + // formula may include double curly brackets in previous version + // convert to single curly bracket here for compatibility + formula.replaceAll('{{', '{').replaceAll('}}', '}') + ); const parsedRawFormula = rawFormula && jsep(rawFormula); substituteId(parsedFormula, parsedRawFormula); return jsepTreeToFormula(parsedFormula); From 5f555e1f501af19cf2cfe40b28d4d97827a20356 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Mon, 9 Jan 2023 17:35:30 +0800 Subject: [PATCH 04/12] refactor(nocodb-sdk): formula curly hook --- packages/nocodb-sdk/src/lib/formulaHelpers.ts | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/nocodb-sdk/src/lib/formulaHelpers.ts b/packages/nocodb-sdk/src/lib/formulaHelpers.ts index ef082a2516..14d50009d5 100644 --- a/packages/nocodb-sdk/src/lib/formulaHelpers.ts +++ b/packages/nocodb-sdk/src/lib/formulaHelpers.ts @@ -9,7 +9,6 @@ export const jsepCurlyHook = { const OCURLY_CODE = 123; // { const CCURLY_CODE = 125; // } let start = -1; - let end = -1; const { context } = env; if ( !jsep.isIdentifierStart(context.code) && @@ -21,22 +20,17 @@ export const jsepCurlyHook = { context.index += 1; context.gobbleExpressions(CCURLY_CODE); if (context.code === CCURLY_CODE) { - if (start != -1 && end == -1) { - end = context.index; - } context.index += 1; - if (start != -1 && end != -1) { - env.node = { - type: jsep.IDENTIFIER, - name: /{{(.*?)}}/.test(context.expr) - ? // start would be the position of the first curly bracket - // add 2 to point to the first character for expressions like {{col1}} - context.expr.slice(start + 2, end) - : // start would be the position of the first curly bracket - // add 1 to point to the first character for expressions like {col1} - context.expr.slice(start + 1, end), - }; - } + env.node = { + type: jsep.IDENTIFIER, + name: /{{(.*?)}}/.test(context.expr) + ? // start would be the position of the first curly bracket + // add 2 to point to the first character for expressions like {{col1}} + context.expr.slice(start + 2, context.index - 1) + : // start would be the position of the first curly bracket + // add 1 to point to the first character for expressions like {col1} + context.expr.slice(start + 1, context.index - 1), + }; return env.node; } else { context.throwError('Unclosed }'); From 414db7e2890aaf5588b6cfc939f5198ae26ae80c Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Mon, 9 Jan 2023 21:35:57 +0800 Subject: [PATCH 05/12] fix(nc-gui): show either formula error or value --- packages/nc-gui/components/virtual-cell/Formula.vue | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/nc-gui/components/virtual-cell/Formula.vue b/packages/nc-gui/components/virtual-cell/Formula.vue index 87e39e2458..8fbfad39d9 100644 --- a/packages/nc-gui/components/virtual-cell/Formula.vue +++ b/packages/nc-gui/components/virtual-cell/Formula.vue @@ -24,11 +24,10 @@ const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning, activ - ERR! -
+
{{ result }}
From 4da148802f224651da474b67ecc6e918f8f9106b Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 10 Jan 2023 11:55:11 +0800 Subject: [PATCH 06/12] feat(nocodb): dry run formula to catch unexpected query errors --- .../nocodb/src/lib/meta/api/columnApis.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/nocodb/src/lib/meta/api/columnApis.ts b/packages/nocodb/src/lib/meta/api/columnApis.ts index d09d1fc534..c498045bc2 100644 --- a/packages/nocodb/src/lib/meta/api/columnApis.ts +++ b/packages/nocodb/src/lib/meta/api/columnApis.ts @@ -40,6 +40,7 @@ import { metaApiMetrics } from '../helpers/apiMetrics'; import FormulaColumn from '../../models/FormulaColumn'; import KanbanView from '../../models/KanbanView'; import { MetaTable } from '../../utils/globals'; +import formulaQueryBuilderv2 from '../../db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2'; const randomID = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 10); @@ -523,6 +524,23 @@ export async function columnAdd( colBody.formula_raw || colBody.formula, table.columns ); + try { + const dbDriver = NcConnectionMgrv2.get(base); + // retrieve the builder from formulaQueryBuilderv2 + const builder = await formulaQueryBuilderv2( + colBody.formula, + null, + dbDriver, + table + ); + // dry-run it to see if the query is valid + // if not, we show an error in UI to prevent from breaking the grid view + await dbDriver(table.table_name).select(builder).as('dry-run'); + } catch (e) { + console.error(e); + NcError.badRequest('Invalid Formula'); + } + await Column.insert({ ...colBody, fk_model_id: table.id, @@ -759,6 +777,24 @@ export async function columnUpdate(req: Request, res: Response) { colBody.formula_raw || colBody.formula, table.columns ); + + try { + const dbDriver = NcConnectionMgrv2.get(base); + // retrieve the builder from formulaQueryBuilderv2 + const builder = await formulaQueryBuilderv2( + colBody.formula, + null, + dbDriver, + table + ); + // dry-run it to see if the query is valid + // if not, we show an error in UI to prevent from breaking the grid view + await dbDriver(table.table_name).select(builder).as('dry-run'); + } catch (e) { + console.error(e); + NcError.badRequest('Invalid Formula'); + } + await Column.update(column.id, { // title: colBody.title, ...column, From 81fe17186cb8cd96e2230894518ba3bf086e147a Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 10 Jan 2023 12:39:39 +0800 Subject: [PATCH 07/12] fix(nocodb): handle existing formula select qb failure --- .../sql-data-mapper/lib/sql/BaseModelSqlv2.ts | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts index 1ea5d068b1..35a88c7577 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts @@ -1206,14 +1206,34 @@ class BaseModelSqlv2 { private async getSelectQueryBuilderForFormula(column: Column) { const formula = await column.getColOptions(); if (formula.error) throw new Error(`Formula error: ${formula.error}`); - const selectQb = await formulaQueryBuilderv2( + const qb = await formulaQueryBuilderv2( formula.formula, null, this.dbDriver, this.model ); - return selectQb; + try { + // dry run the existing qb.builder to see if it will break the grid view or not + // if so, set formula error and show empty selectQb instead + await this.dbDriver(this.tnPath) + .select(qb.builder) + .as(sanitize(column.title)); + // clean the previous formula error if the formula works this time + if (formula.error) { + await FormulaColumn.update(formula.id, { + error: null, + }); + } + } catch (e) { + console.error(e); + // add formula error to show in UI + await FormulaColumn.update(formula.id, { + error: e.message, + }); + throw new Error(`Formula error: ${e.message}`); + } + return qb; } async getProto() { @@ -1502,7 +1522,6 @@ class BaseModelSqlv2 { const selectQb = await this.getSelectQueryBuilderForFormula( column ); - // todo: verify syntax of as ? / ?? qb.select( this.dbDriver.raw(`?? as ??`, [ selectQb.builder, @@ -1510,7 +1529,10 @@ class BaseModelSqlv2 { ]) ); } catch { - continue; + // return dummy select + qb.select( + this.dbDriver.raw(`'ERR' as ??`, [sanitize(column.title)]) + ); } } break; From c20d0b7ebe12941e5d071d767598f076dc59f641 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 10 Jan 2023 14:14:04 +0800 Subject: [PATCH 08/12] refactor(nocodb): formula dry run logic --- .../sql-data-mapper/lib/sql/BaseModelSqlv2.ts | 24 ++---------------- .../nocodb/src/lib/meta/api/columnApis.ts | 25 ++++--------------- 2 files changed, 7 insertions(+), 42 deletions(-) diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts index 35a88c7577..8934f85616 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts @@ -1210,29 +1210,9 @@ class BaseModelSqlv2 { formula.formula, null, this.dbDriver, - this.model + this.model, + column ); - - try { - // dry run the existing qb.builder to see if it will break the grid view or not - // if so, set formula error and show empty selectQb instead - await this.dbDriver(this.tnPath) - .select(qb.builder) - .as(sanitize(column.title)); - // clean the previous formula error if the formula works this time - if (formula.error) { - await FormulaColumn.update(formula.id, { - error: null, - }); - } - } catch (e) { - console.error(e); - // add formula error to show in UI - await FormulaColumn.update(formula.id, { - error: e.message, - }); - throw new Error(`Formula error: ${e.message}`); - } return qb; } diff --git a/packages/nocodb/src/lib/meta/api/columnApis.ts b/packages/nocodb/src/lib/meta/api/columnApis.ts index c498045bc2..aaec69a65e 100644 --- a/packages/nocodb/src/lib/meta/api/columnApis.ts +++ b/packages/nocodb/src/lib/meta/api/columnApis.ts @@ -524,18 +524,11 @@ export async function columnAdd( colBody.formula_raw || colBody.formula, table.columns ); + try { + // test the query to see if it is valid in db level const dbDriver = NcConnectionMgrv2.get(base); - // retrieve the builder from formulaQueryBuilderv2 - const builder = await formulaQueryBuilderv2( - colBody.formula, - null, - dbDriver, - table - ); - // dry-run it to see if the query is valid - // if not, we show an error in UI to prevent from breaking the grid view - await dbDriver(table.table_name).select(builder).as('dry-run'); + await formulaQueryBuilderv2(colBody.formula, null, dbDriver, table); } catch (e) { console.error(e); NcError.badRequest('Invalid Formula'); @@ -779,17 +772,9 @@ export async function columnUpdate(req: Request, res: Response) { ); try { + // test the query to see if it is valid in db level const dbDriver = NcConnectionMgrv2.get(base); - // retrieve the builder from formulaQueryBuilderv2 - const builder = await formulaQueryBuilderv2( - colBody.formula, - null, - dbDriver, - table - ); - // dry-run it to see if the query is valid - // if not, we show an error in UI to prevent from breaking the grid view - await dbDriver(table.table_name).select(builder).as('dry-run'); + await formulaQueryBuilderv2(colBody.formula, null, dbDriver, table); } catch (e) { console.error(e); NcError.badRequest('Invalid Formula'); From f80bddcc3801e4b6b2cea175ae20127e06785acb Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 10 Jan 2023 14:14:53 +0800 Subject: [PATCH 09/12] feat(nocodb): pass column to formulaQueryBuilderv2 --- .../src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts | 2 +- .../nocodb/src/lib/db/sql-data-mapper/lib/sql/sortV2.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts index 942b1dbb8f..e15235b93b 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts @@ -246,7 +246,7 @@ const parseConditionV2 = async ( const model = await column.getModel(); const formula = await column.getColOptions(); const builder = ( - await formulaQueryBuilderv2(formula.formula, null, knex, model) + await formulaQueryBuilderv2(formula.formula, null, knex, model, column) ).builder; return parseConditionV2( new Filter({ ...filter, value: knex.raw('?', [filter.value]) } as any), diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/sortV2.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/sortV2.ts index e020c7c4d5..0e3e33a40d 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/sortV2.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/sortV2.ts @@ -51,7 +51,8 @@ export default async function sortV2( ).formula, null, knex, - model + model, + column ) ).builder; qb.orderBy(builder, sort.direction || 'asc'); @@ -161,7 +162,8 @@ export default async function sortV2( ).formula, null, knex, - model + model, + column ) ).builder; From bbf69c74a9678f595405ee3eb26ab5a3f96fdb26 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 10 Jan 2023 14:15:16 +0800 Subject: [PATCH 10/12] refactor(nocodb): formulaQueryBuilderv2 --- .../sql/formulav2/formulaQueryBuilderv2.ts | 74 +++++++++++++++++-- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts index b9ec6658e6..5447fdf9b8 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts @@ -1,6 +1,7 @@ import jsep from 'jsep'; import mapFunctionName from '../mapFunctionName'; import Model from '../../../../../models/Model'; +import Column from '../../../../../models/Column'; import genRollupSelectv2 from '../genRollupSelectv2'; import RollupColumn from '../../../../../models/RollupColumn'; import FormulaColumn from '../../../../../models/FormulaColumn'; @@ -45,16 +46,13 @@ const getAggregateFn: (fnName: string) => (args: { qb; knex?; cn }) => any = ( } }; -export default async function formulaQueryBuilderv2( +async function _formulaQueryBuilder( _tree, alias, knex: XKnex, model: Model, aliasToColumn = {} ) { - // register jsep curly hook - jsep.plugins.register(jsepCurlyHook); - // formula may include double curly brackets in previous version // convert to single curly bracket here for compatibility const tree = jsep(_tree.replaceAll('{{', '{').replaceAll('}}', '}')); @@ -69,7 +67,7 @@ export default async function formulaQueryBuilderv2( case UITypes.Formula: { const formulOption = await col.getColOptions(); - const { builder } = await formulaQueryBuilderv2( + const { builder } = await _formulaQueryBuilder( formulOption.formula, alias, knex, @@ -343,7 +341,7 @@ export default async function formulaQueryBuilderv2( const formulaOption = await lookupColumn.getColOptions(); const lookupModel = await lookupColumn.getModel(); - const { builder } = await formulaQueryBuilderv2( + const { builder } = await _formulaQueryBuilder( formulaOption.formula, '', knex, @@ -774,3 +772,67 @@ export default async function formulaQueryBuilderv2( }; return { builder: fn(tree, alias) }; } + +function getTnPath(tb: Model, knex) { + const schema = knex.searchPath?.(); + if (knex.clientType() === 'mssql' && schema) { + return knex.raw('??.??', [schema, tb.table_name]); + } else if (knex.clientType() === 'snowflake') { + return [ + knex.client.config.connection.database, + knex.client.config.connection.schema, + tb.table_name, + ].join('.'); + } else { + return tb.table_name; + } +} + +export default async function formulaQueryBuilderv2( + _tree, + alias, + knex: XKnex, + model: Model, + column?: Column, + aliasToColumn = {} +) { + // register jsep curly hook once only + jsep.plugins.register(jsepCurlyHook); + // generate qb + const qb = await _formulaQueryBuilder( + _tree, + alias, + knex, + model, + aliasToColumn + ); + + let formula; + + try { + // dry run qb.builder to see if it will break the grid view or not + // if so, set formula error and show empty selectQb instead + await knex(getTnPath(model, knex)).select(qb.builder).as('dry-run-only'); + + // if column is provided, i.e. formula has been created + if (column) { + const formula = await column.getColOptions(); + // clean the previous formula error if the formula works this time + if (formula.error) { + await FormulaColumn.update(formula.id, { + error: null, + }); + } + } + } catch (e) { + console.error(e); + if (column) { + // add formula error to show in UI + await FormulaColumn.update(formula.id, { + error: e.message, + }); + } + throw new Error(`Formula error: ${e.message}`); + } + return qb; +} From c244638630b7f7762b0b136482fb8a814d7944a6 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 10 Jan 2023 16:41:15 +0800 Subject: [PATCH 11/12] fix(nocodb): clear the cache to reflect the formula error in UI --- .../lib/sql/formulav2/formulaQueryBuilderv2.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts index 5447fdf9b8..99652cec1c 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts @@ -10,6 +10,8 @@ import LinkToAnotherRecordColumn from '../../../../../models/LinkToAnotherRecord import LookupColumn from '../../../../../models/LookupColumn'; import { jsepCurlyHook, UITypes } from 'nocodb-sdk'; import { validateDateWithUnknownFormat } from '../helpers/formulaFnHelper'; +import { CacheGetType, CacheScope } from '../../../../../utils/globals'; +import NocoCache from '../../../../../cache/NocoCache'; // todo: switch function based on database @@ -807,8 +809,6 @@ export default async function formulaQueryBuilderv2( aliasToColumn ); - let formula; - try { // dry run qb.builder to see if it will break the grid view or not // if so, set formula error and show empty selectQb instead @@ -827,10 +827,19 @@ export default async function formulaQueryBuilderv2( } catch (e) { console.error(e); if (column) { + const formula = await column.getColOptions(); // add formula error to show in UI await FormulaColumn.update(formula.id, { error: e.message, }); + // update cache to reflect the error in UI + const key = `${CacheScope.COL_FORMULA}:${column.id}`; + let o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT); + if (o) { + o = { ...o, error: e.message }; + // set cache + await NocoCache.set(key, o); + } } throw new Error(`Formula error: ${e.message}`); } From a2cabc5b912d43a3a956fdb7ac3731dc0b82a9d3 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 11 Jan 2023 16:18:03 +0800 Subject: [PATCH 12/12] chore(nocodb-sdk): remove unnecessary replace in formula --- packages/nocodb-sdk/src/lib/formulaHelpers.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/nocodb-sdk/src/lib/formulaHelpers.ts b/packages/nocodb-sdk/src/lib/formulaHelpers.ts index 14d50009d5..e10e491dea 100644 --- a/packages/nocodb-sdk/src/lib/formulaHelpers.ts +++ b/packages/nocodb-sdk/src/lib/formulaHelpers.ts @@ -67,11 +67,7 @@ export async function substituteColumnAliasWithIdInFormula( }; // register jsep curly hook jsep.plugins.register(jsepCurlyHook); - const parsedFormula = jsep( - // formula may include double curly brackets in previous version - // convert to single curly bracket here for compatibility - formula.replaceAll('{{', '{').replaceAll('}}', '}') - ); + const parsedFormula = jsep(formula); await substituteId(parsedFormula); return jsepTreeToFormula(parsedFormula); } @@ -106,11 +102,7 @@ export function substituteColumnIdWithAliasInFormula( // register jsep curly hook jsep.plugins.register(jsepCurlyHook); - const parsedFormula = jsep( - // formula may include double curly brackets in previous version - // convert to single curly bracket here for compatibility - formula.replaceAll('{{', '{').replaceAll('}}', '}') - ); + const parsedFormula = jsep(formula); const parsedRawFormula = rawFormula && jsep(rawFormula); substituteId(parsedFormula, parsedRawFormula); return jsepTreeToFormula(parsedFormula);