diff --git a/packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue b/packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue index 0762a29b5e..f9832bca00 100644 --- a/packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue +++ b/packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue @@ -73,7 +73,7 @@ export default { formula: {}, // formulas: ['AVERAGE()', 'COUNT()', 'COUNTA()', 'COUNTALL()', 'SUM()', 'MIN()', 'MAX()', 'AND()', 'OR()', 'TRUE()', 'FALSE()', 'NOT()', 'XOR()', 'ISERROR()', 'IF()', 'LEN()', 'MID()', 'LEFT()', 'RIGHT()', 'FIND()', 'CONCATENATE()', 'T()', 'VALUE()', 'ARRAYJOIN()', 'ARRAYUNIQUE()', 'ARRAYCOMPACT()', 'ARRAYFLATTEN()', 'ROUND()', 'ROUNDUP()', 'ROUNDDOWN()', 'INT()', 'EVEN()', 'ODD()', 'MOD()', 'LOG()', 'EXP()', 'POWER()', 'SQRT()', 'CEILING()', 'FLOOR()', 'ABS()', 'RECORD_ID()', 'CREATED_TIME()', 'ERROR()', 'BLANK()', 'YEAR()', 'MONTH()', 'DAY()', 'HOUR()', 'MINUTE()', 'SECOND()', 'TODAY()', 'NOW()', 'WORKDAY()', 'DATETIME_PARSE()', 'DATETIME_FORMAT()', 'SET_LOCALE()', 'SET_TIMEZONE()', 'DATESTR()', 'TIMESTR()', 'TONOW()', 'FROMNOW()', 'DATEADD()', 'WEEKDAY()', 'WEEKNUM()', 'DATETIME_DIFF()', 'WORKDAY_DIFF()', 'IS_BEFORE()', 'IS_SAME()', 'IS_AFTER()', 'REPLACE()', 'REPT()', 'LOWER()', 'UPPER()', 'TRIM()', 'SUBSTITUTE()', 'SEARCH()', 'SWITCH()', 'LAST_MODIFIED_TIME()', 'ENCODE_URL_COMPONENT()', 'REGEX_EXTRACT()', 'REGEX_MATCH()', 'REGEX_REPLACE()'] availableFunctions: formulaList, - availableBinOps: ['+', '-', '*', '/'], + availableBinOps: ['+', '-', '*', '/', '>', '==', '<=', '>=', '!='], autocomplete: false, suggestion: null, wordToComplete: '', diff --git a/packages/nc-gui/helpers/formulaList.js b/packages/nc-gui/helpers/formulaList.js index a5a46c9e79..9c053df3bd 100644 --- a/packages/nc-gui/helpers/formulaList.js +++ b/packages/nc-gui/helpers/formulaList.js @@ -7,7 +7,6 @@ const validations = { ADD: { validation: { args: { min: 1 } - } }, CONCAT: { @@ -42,7 +41,9 @@ const validations = { validation: { args: { rqd: 1 } } }, SUBSTR: { validation: { args: { min: 2, max: 3 } } }, - MID: { validation: { args: { rqd: 1 } } } + MID: { validation: { args: { rqd: 1 } } }, + IF: { validation: { args: { min: 2, max: 3 } } }, + SWITCH: { validation: { args: { min: 3 } } } } export default Object.keys(validations) diff --git a/packages/nocodb/src/lib/dataMapper/lib/sql/formulaQueryBuilderFromString.ts b/packages/nocodb/src/lib/dataMapper/lib/sql/formulaQueryBuilderFromString.ts index c4199322b8..ddeecb2ceb 100644 --- a/packages/nocodb/src/lib/dataMapper/lib/sql/formulaQueryBuilderFromString.ts +++ b/packages/nocodb/src/lib/dataMapper/lib/sql/formulaQueryBuilderFromString.ts @@ -66,6 +66,10 @@ export default function formulaQueryBuilder(tree, alias, knex, aliasToColumn = { } else if (pt.type === 'Identifier') { return knex.raw(`??${colAlias}`, [aliasToColumn[pt.name] || pt.name]); } else if (pt.type === 'BinaryExpression') { + if (pt.operator === '==') { + pt.operator = '=' + } + const query = knex.raw(`${fn(pt.left, null, pt.operator).toQuery()} ${pt.operator} ${fn(pt.right, null, pt.operator).toQuery()}${colAlias}`) if (prevBinaryOp && pt.operator !== prevBinaryOp) { query.wrap('(', ')') diff --git a/packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/commonFns.ts b/packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/commonFns.ts new file mode 100644 index 0000000000..50e64c3e43 --- /dev/null +++ b/packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/commonFns.ts @@ -0,0 +1,28 @@ +import {MapFnArgs} from "../mapFunctionName"; + +export default { + // todo: handle default case + SWITCH: (args: MapFnArgs) => { + const count = Math.floor((args.pt.arguments.length-1) / 2) + let query = ''; + + const switchVal = args.fn(args.pt.arguments[0]).toQuery(); + + for (let i = 0; i < count; i++) { + query += args.knex.raw(`\n\tWHEN ${args.fn(args.pt.arguments[i * 2 + 1]).toQuery()} THEN ${args.fn(args.pt.arguments[i * 2 + 2]).toQuery()}`).toQuery() + } + if (args.pt.arguments.length % 2 === 0) { + query += args.knex.raw(`\n\tELSE ${args.fn(args.pt.arguments[args.pt.arguments.length - 1]).toQuery()}`).toQuery() + } + return args.knex.raw(`CASE ${switchVal} ${query}\n END${args.colAlias}`) + }, + IF: (args: MapFnArgs) => { + let query = args.knex.raw(`\n\tWHEN ${args.fn(args.pt.arguments[0]).toQuery()} THEN ${args.fn(args.pt.arguments[1]).toQuery()}`).toQuery(); + if (args.pt.arguments[2]) { + query += args.knex.raw(`\n\tELSE ${args.fn(args.pt.arguments[2]).toQuery()}`).toQuery() + } + return args.knex.raw(`CASE ${query}\n END${args.colAlias}`) + }, + TRUE:(_args) => 1, + FALSE:(_args) => 0 +} diff --git a/packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/mssql.ts b/packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/mssql.ts index 1eeb433de5..b567d3596b 100644 --- a/packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/mssql.ts +++ b/packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/mssql.ts @@ -1,6 +1,8 @@ import {MapFnArgs} from "../mapFunctionName"; +import commonFns from "./commonFns"; const mssql = { + ...commonFns, MIN: (args: MapFnArgs) => { if (args.pt.arguments.length === 1) { return args.fn(args.pt.arguments[0]) @@ -49,7 +51,7 @@ const mssql = { INT: (args: MapFnArgs) => { return args.knex.raw(`CASE WHEN ISNUMERIC(${args.fn(args.pt.arguments[0]).toQuery()}) = 1 THEN FLOOR(${args.fn(args.pt.arguments[0]).toQuery()}) ELSE 0 END${args.colAlias}`) }, - MID:'SUBSTR' + MID:'SUBSTR', } export default mssql; diff --git a/packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/mysql.ts b/packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/mysql.ts index 05055924e6..15733c8de2 100644 --- a/packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/mysql.ts +++ b/packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/mysql.ts @@ -1,7 +1,9 @@ import {MapFnArgs} from "../mapFunctionName"; +import commonFns from "./commonFns"; const mysql2 = { + ...commonFns, LEN: 'CHAR_LENGTH', MIN: 'LEAST', MAX: 'GREATEST', @@ -20,7 +22,7 @@ const mysql2 = { RIGHT:(args: MapFnArgs)=> { return args.knex.raw(`SUBSTR(${args.fn(args.pt.arguments[0])},-${args.fn(args.pt.arguments[1])})${args.colAlias}`) }, - MID:'SUBSTR' + MID:'SUBSTR', } diff --git a/packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/pg.ts b/packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/pg.ts index 250f3788f0..4b7e83bb4d 100644 --- a/packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/pg.ts +++ b/packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/pg.ts @@ -1,6 +1,8 @@ import {MapFnArgs} from "../mapFunctionName"; +import commonFns from "./commonFns"; const pg = { + ...commonFns, LEN: 'length', MIN: 'least', MAX: 'greatest', @@ -15,7 +17,7 @@ const pg = { // todo: correction return args.knex.raw(`REGEXP_REPLACE(COALESCE(${args.fn(args.pt.arguments[0])}::character varying, '0'), '[^0-9]+|\\.[0-9]+' ,'')${args.colAlias}`) }, - MID: 'SUBSTR' + MID: 'SUBSTR', } diff --git a/packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/sqlite.ts b/packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/sqlite.ts index 0986b31541..ad60b59d80 100644 --- a/packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/sqlite.ts +++ b/packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/sqlite.ts @@ -1,38 +1,38 @@ import {MapFnArgs} from "../mapFunctionName"; +import commonFns from "./commonFns"; const sqlite3 = { + ...commonFns, LEN: 'LENGTH', - CEILING(_pt) { - // todo: - }, FLOOR(_pt) { - // todo: + CEILING(args) { + return args.knex.raw(`round(${args.fn(args.pt.arguments[0])} + 0.5)${args.colAlias}`) + }, FLOOR(args) { + return args.knex.raw(`round(${args.fn(args.pt.arguments[0])} - 0.5)${args.colAlias}`) }, - MOD:(pt) => { - Object.assign(pt, { + MOD: (args: MapFnArgs) => { + return args.fn({ type: 'BinaryExpression', operator: '%', - left: pt.arguments[0], - right: pt.arguments[1] + left: args.pt.arguments[0], + right: args.pt.arguments[1] }) }, REPEAT(args: MapFnArgs) { return args.knex.raw(`replace(printf('%.' || ${args.fn(args.pt.arguments[1])} || 'c', '/'),'/',${args.fn(args.pt.arguments[0])})${args.colAlias}`) }, NOW: 'DATE', - SEARCH:'INSTR', + SEARCH: 'INSTR', INT(args: MapFnArgs) { return args.knex.raw(`CAST(${args.fn(args.pt.arguments[0])} as INTEGER)${args.colAlias}`) }, - - - LEFT:(args: MapFnArgs)=> { + LEFT: (args: MapFnArgs) => { return args.knex.raw(`SUBSTR(${args.fn(args.pt.arguments[0])},1,${args.fn(args.pt.arguments[1])})${args.colAlias}`) }, - RIGHT:(args: MapFnArgs)=> { + RIGHT: (args: MapFnArgs) => { return args.knex.raw(`SUBSTR(${args.fn(args.pt.arguments[0])},-${args.fn(args.pt.arguments[1])})${args.colAlias}`) }, - MID:'SUBSTR' + MID: 'SUBSTR', }