Browse Source

feat: add support to more formula functions

Added - 'MIN','MAX','CEILING', 'FLOOR', 'ROUND', 'MOD', 'REPEAT', 'LOG', 'EXP', 'POWER', 'SQRT', 'ABS', 'NOW', 'REPLACE

Signed-off-by: Pranav C <61551451+pranavxc@users.noreply.github.com>
pull/448/head
Pranav C 3 years ago committed by Pranav C
parent
commit
8b29877f23
  1. 10
      packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue
  2. 2
      packages/nc-gui/plugins/ncApis/restApi.js
  3. 8
      packages/nocodb/src/lib/dataMapper/lib/sql/formulaQueryBuilderFromString.ts
  4. 44
      packages/nocodb/src/lib/dataMapper/lib/sql/getFunctionName.ts
  5. 116
      packages/nocodb/src/lib/dataMapper/lib/sql/mapFunctionName.ts

10
packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue

@ -75,7 +75,15 @@ export default {
'AVG', 'ADD', 'CONCAT', 'TRIM',
'UPPER',
'LOWER',
'LEN'
'LEN',
'MIN',
'MAX',
'CEILING', 'FLOOR', 'ROUND',
'MOD', 'REPEAT',
'LOG', 'EXP', 'POWER', 'SQRT', // todo: remove in sqlite
'ABS',
'NOW',
'REPLACE'
],
availableBinOps: ['+', '-', '*', '/'],
autocomplete: false,

2
packages/nc-gui/plugins/ncApis/restApi.js

@ -76,7 +76,7 @@ export default class RestApi {
prevValue: oldData[colName]
}])
return res
return res.data
}
async insert(data) {

8
packages/nocodb/src/lib/dataMapper/lib/sql/formulaQueryBuilderFromString.ts

@ -1,5 +1,5 @@
import jsep from 'jsep';
import getFunctionName from "./getFunctionName";
import mapFunctionName from "./mapFunctionName";
// todo: switch function based on database
@ -53,8 +53,10 @@ export default function formulaQueryBuilder(tree, alias, knex, aliasToColumn = {
}
}
break;
default:
pt.callee.name = getFunctionName(pt.callee.name, knex)
default: {
const res = mapFunctionName({pt, knex, alias, aliasToCol: aliasToColumn, fn})
if (res) return res;
}
break
}

44
packages/nocodb/src/lib/dataMapper/lib/sql/getFunctionName.ts

@ -1,44 +0,0 @@
import {XKnex} from "../../index";
const pg = {
LEN: 'length'
}
const mssql = {
LEN: 'LEN'
}
const mysql2 = {
LEN: 'CHAR_LENGTH'
}
const sqlite3 = {
LEN: 'LENGTH'
}
const getFunctionName = (name, knex: XKnex) => {
switch (knex.clientType()) {
case 'mysql':
case 'mysql2':
return mysql2[name] || name;
break;
case 'pg':
case 'postgre':
return pg[name] || name;
break;
case 'mssql':
return mssql[name] || name;
break;
case 'sqlite':
case 'sqlite3':
return sqlite3[name] || name;
break;
}
}
export default getFunctionName;

116
packages/nocodb/src/lib/dataMapper/lib/sql/mapFunctionName.ts

@ -0,0 +1,116 @@
import {XKnex} from "../../index";
interface MapFnArgs {
pt: any,
aliasToCol: { [alias: string]: string },
knex: XKnex,
alias: string,
fn: (...args: any) => any
}
const MOD = (pt) => {
Object.assign(pt, {
type: 'BinaryExpression',
operator: '%',
left: pt.arguments[0],
right: pt.arguments[1]
})
}
const mysql2 = {
LEN: 'CHAR_LENGTH',
MIN: 'LEAST',
MAX: 'GREATEST',
}
const pg = {
LEN: 'length',
MIN: 'least',
MAX: 'greatest',
CEILING: 'ceil',
ROUND: 'round',
POWER: 'pow',
SQRT: 'sqrt'
}
const mssql = {
MIN: (args: MapFnArgs) => {
if (args.pt.arguments.length === 1) {
return args.fn(args.pt.arguments[0])
}
let query = '';
for (const [i, arg] of Object.entries(args.pt.arguments)) {
if (+i === args.pt.arguments.length - 1) {
query += args.knex.raw(`\n\tElse ${args.fn(arg).toQuery()}`).toQuery()
} else {
query += args.knex.raw(`\n\tWhen ${args.pt.arguments.filter((_, j) => +i !== j).map(arg1 => `${args.fn(arg).toQuery()} < ${args.fn(arg1).toQuery()}`).join(' And ')} Then ${args.fn(arg).toQuery()}`).toQuery()
}
}
return args.knex.raw(`Case ${query}\n End as ${args.alias}`)
},
MAX: (args: MapFnArgs) => {
if (args.pt.arguments.length === 1) {
return args.fn(args.pt.arguments[0])
}
let query = '';
for (const [i, arg] of Object.entries(args.pt.arguments)) {
if (+i === args.pt.arguments.length - 1) {
query += args.knex.raw(`\nElse ${args.fn(arg).toQuery()}`).toQuery()
} else {
query += args.knex.raw(`\nWhen ${args.pt.arguments.filter((_, j) => +i !== j).map(arg1 => `${args.fn(arg).toQuery()} > ${args.fn(arg1).toQuery()}`).join(' And ')} Then ${args.fn(arg).toQuery()}`).toQuery()
}
}
return args.knex.raw(`Case ${query}\n End as ${args.alias}`)
},
MOD,
REPEAT: 'REPLICATE',
NOW: 'getdate'
}
const sqlite3 = {
LEN: 'LENGTH',
CEILING(_pt) {
// todo:
}, FLOOR(_pt) {
// todo:
},
MOD,
REPEAT(args: MapFnArgs) {
return args.knex.raw(`replace(printf('%.' || ${args.fn(args.pt.arguments[1])} || 'c', '/'),'/',${args.fn(args.pt.arguments[0])}) ${args.alias}`)
},
NOW: 'DATE'
}
const mapFunctionName = (args: MapFnArgs): any => {
const name = args.pt.callee.name;
let val;
switch (args.knex.clientType()) {
case 'mysql':
case 'mysql2':
val = mysql2[name] || name;
break;
case 'pg':
case 'postgre':
val = pg[name] || name;
break;
case 'mssql':
val = mssql[name] || name;
break;
case 'sqlite':
case 'sqlite3':
val = sqlite3[name] || name;
break;
}
if (typeof val === 'function') {
return val(args)
} else if (typeof val === 'string') {
args.pt.callee.name = val;
}
}
export default mapFunctionName;
Loading…
Cancel
Save