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 3eb91300d9..3bd4a073d8 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 @@ -53,7 +53,7 @@ async function _formulaQueryBuilder( alias, knex: XKnex, model: Model, - aliasToColumn = {}, + aliasToColumn: Record Promise<{ builder: any }>> = {}, tableAlias?: string ) { // formula may include double curly brackets in previous version @@ -69,21 +69,25 @@ async function _formulaQueryBuilder( switch (col.uidt) { case UITypes.Formula: { - const formulOption = await col.getColOptions(); - const { builder } = await _formulaQueryBuilder( - formulOption.formula, - alias, - knex, - model, - { ...aliasToColumn, [col.id]: null }, - tableAlias - ); - builder.sql = '(' + builder.sql + ')'; - aliasToColumn[col.id] = builder; + aliasToColumn[col.id] = async () => { + const formulOption = await col.getColOptions(); + const { builder } = await _formulaQueryBuilder( + formulOption.formula, + alias, + knex, + model, + { ...aliasToColumn, [col.id]: null }, + tableAlias + ); + builder.sql = '(' + builder.sql + ')'; + return { + builder, + }; + }; } break; case UITypes.Lookup: - { + aliasToColumn[col.id] = async (): Promise => { let aliasCount = 0; let selectQb; let isMany = false; @@ -398,25 +402,27 @@ async function _formulaQueryBuilder( } if (selectQb) - aliasToColumn[col.id] = - typeof selectQb === 'function' - ? selectQb - : knex.raw(selectQb as any).wrap('(', ')'); + return { + builder: + typeof selectQb === 'function' + ? selectQb + : knex.raw(selectQb as any).wrap('(', ')'), + }; } - } + }; break; case UITypes.Rollup: - { + aliasToColumn[col.id] = async (): Promise => { const qb = await genRollupSelectv2({ knex, columnOptions: (await col.getColOptions()) as RollupColumn, alias: tableAlias, }); - aliasToColumn[col.id] = knex.raw(qb.builder).wrap('(', ')'); - } + return { builder: knex.raw(qb.builder).wrap('(', ')') }; + }; break; case UITypes.LinkToAnotherRecord: - { + aliasToColumn[col.id] = async (): Promise => { const alias = `__nc_formula_ll`; const relation = await col.getColOptions(); // if (relation.type !== 'bt') continue; @@ -520,19 +526,22 @@ async function _formulaQueryBuilder( .wrap('(', ')'); } if (selectQb) - aliasToColumn[col.id] = - typeof selectQb === 'function' - ? selectQb - : knex.raw(selectQb as any).wrap('(', ')'); - } + return { + builder: + typeof selectQb === 'function' + ? selectQb + : knex.raw(selectQb as any).wrap('(', ')'), + }; + }; break; default: - aliasToColumn[col.id] = col.column_name; + aliasToColumn[col.id] = () => + Promise.resolve({ builder: col.column_name }); break; } } - const fn = (pt, a?, prevBinaryOp?) => { + const fn = async (pt, a?, prevBinaryOp?) => { const colAlias = a ? ` as ${a}` : ''; pt.arguments?.forEach?.((arg) => { if (arg.fnName) return; @@ -558,18 +567,6 @@ async function _formulaQueryBuilder( return fn(pt.arguments[0], a, prevBinaryOp); } break; - // case 'AVG': - // if (pt.arguments.length > 1) { - // return fn({ - // type: 'BinaryExpression', - // operator: '/', - // left: {...pt, callee: {name: 'SUM'}}, - // right: {type: 'Literal', value: pt.arguments.length} - // }, a, prevBinaryOp) - // } else { - // return fn(pt.arguments[0], a, prevBinaryOp) - // } - // break; case 'CONCAT': if (knex.clientType() === 'sqlite3') { if (pt.arguments.length > 1) { @@ -653,8 +650,12 @@ async function _formulaQueryBuilder( } else if (pt.type === 'Literal') { return knex.raw(`?${colAlias}`, [pt.value]); } else if (pt.type === 'Identifier') { - if (typeof aliasToColumn?.[pt.name] === 'function') { - return knex.raw(`??${colAlias}`, aliasToColumn?.[pt.name](pt.fnName)); + const { builder } = await aliasToColumn?.[pt.name]() + if (typeof builder === 'function') { + return knex.raw( + `??${colAlias}`, + await builder(pt.fnName) + ); } return knex.raw(`??${colAlias}`, [aliasToColumn?.[pt.name] || pt.name]); } else if (pt.type === 'BinaryExpression') { @@ -772,7 +773,7 @@ async function _formulaQueryBuilder( if (prevBinaryOp && pt.operator !== prevBinaryOp) { query.wrap('(', ')'); } - return query; + return { builder: query }; } else if (pt.type === 'UnaryExpression') { const query = knex.raw( `${pt.operator}${fn( @@ -784,7 +785,7 @@ async function _formulaQueryBuilder( if (prevBinaryOp && pt.operator !== prevBinaryOp) { query.wrap('(', ')'); } - return query; + return { builder: query }; } }; return { builder: fn(tree, alias) }; diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/commonFns.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/commonFns.ts index 2b65d2cace..1fac4fbbd7 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/commonFns.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/commonFns.ts @@ -2,66 +2,80 @@ import type { MapFnArgs } from '../mapFunctionName'; export default { // todo: handle default case - SWITCH: (args: MapFnArgs) => { + SWITCH: async (args: MapFnArgs) => { const count = Math.floor((args.pt.arguments.length - 1) / 2); let query = ''; - const switchVal = args.fn(args.pt.arguments[0]).toQuery(); + const switchVal = (await args.fn(args.pt.arguments[0])).builder.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()}` + `\n\tWHEN ${( + await args.fn(args.pt.arguments[i * 2 + 1]) + ).builder.toQuery()} THEN ${( + await args.fn(args.pt.arguments[i * 2 + 2]) + ).builder.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()}` + `\n\tELSE ${( + await args.fn(args.pt.arguments[args.pt.arguments.length - 1]) + ).builder.toQuery()}` ) .toQuery(); } return args.knex.raw(`CASE ${switchVal} ${query}\n END${args.colAlias}`); }, - IF: (args: MapFnArgs) => { + IF: async (args: MapFnArgs) => { let query = args.knex .raw( - `\n\tWHEN ${args.fn(args.pt.arguments[0]).toQuery()} THEN ${args - .fn(args.pt.arguments[1]) - .toQuery()}` + `\n\tWHEN ${( + await args.fn(args.pt.arguments[0]) + ).builder.toQuery()} THEN ${( + await args.fn(args.pt.arguments[1]) + ).builder.toQuery()}` ) .toQuery(); if (args.pt.arguments[2]) { query += args.knex - .raw(`\n\tELSE ${args.fn(args.pt.arguments[2]).toQuery()}`) + .raw(`\n\tELSE ${(await args.fn(args.pt.arguments[2])).builder.toQuery()}`) .toQuery(); } return args.knex.raw(`CASE ${query}\n END${args.colAlias}`); }, TRUE: (_args) => 1, FALSE: (_args) => 0, - AND: (args: MapFnArgs) => { + AND: async (args: MapFnArgs) => { return args.knex.raw( `${args.knex .raw( - `${args.pt.arguments - .map((ar) => args.fn(ar).toQuery()) - .join(' AND ')}` + `${( + await Promise.all( + args.pt.arguments.map(async (ar) => + (await args.fn(ar)).builder.toQuery() + ) + ) + ).join(' AND ')}` ) .wrap('(', ')') .toQuery()}${args.colAlias}` ); }, - OR: (args: MapFnArgs) => { + OR: async (args: MapFnArgs) => { return args.knex.raw( `${args.knex .raw( - `${args.pt.arguments.map((ar) => args.fn(ar).toQuery()).join(' OR ')}` + `${( + await Promise.all( + args.pt.arguments.map(async (ar) => + (await args.fn(ar)).builder.toQuery() + ) + ) + ).join(' OR ')}` ) .wrap('(', ')') .toQuery()}${args.colAlias}` @@ -83,7 +97,7 @@ export default { return args.fn(args.pt.arguments[0], args.a, args.prevBinaryOp); } }, - FLOAT: (args: MapFnArgs) => { - return args.fn(args.pt?.arguments?.[0]).wrap('(', ')'); + FLOAT: async (args: MapFnArgs) => { + return (await args.fn(args.pt?.arguments?.[0])).builder.wrap('(', ')'); }, }; diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/mssql.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/mssql.ts index 105311cade..6bf5e41831 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/mssql.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/mssql.ts @@ -6,48 +6,61 @@ import type { MapFnArgs } from '../mapFunctionName'; const mssql = { ...commonFns, - MIN: (args: MapFnArgs) => { + MIN: async (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(); + query += args.knex + .raw( + `\n\tElse ${(await await args.fn(arg)).builder.builder.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()}` + `\n\tWhen ${( + await Promise.all( + args.pt.arguments + .filter((_, j) => +i !== j) + .map( + async (arg1) => + `${(await args.fn(arg)).builder.toQuery()} < ${( + await args.fn(arg1) + ).builder.toQuery()}` + ) ) - .join(' And ')} Then ${args.fn(arg).toQuery()}` + ).join(' And ')} Then ${(await args.fn(arg)).builder.toQuery()}` ) .toQuery(); } } return args.knex.raw(`Case ${query}\n End${args.colAlias}`); }, - MAX: (args: MapFnArgs) => { + MAX: async (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(); + query += args.knex + .raw(`\nElse ${(await args.fn(arg)).builder.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()}` + async (arg1) => + `${(await args.fn(arg)).builder.toQuery()} > ${( + await args.fn(arg1) + ).builder.toQuery()}` ) - .join(' And ')} Then ${args.fn(arg).toQuery()}` + .join(' And ')} Then ${(await args.fn(arg)).builder.toQuery()}` ) .toQuery(); } @@ -55,12 +68,15 @@ const mssql = { return args.knex.raw(`Case ${query}\n End${args.colAlias}`); }, - LOG: (args: MapFnArgs) => { + LOG: async (args: MapFnArgs) => { return args.knex.raw( - `LOG(${args.pt.arguments - .reverse() - .map((ar) => args.fn(ar).toQuery()) - .join(',')})${args.colAlias}` + `LOG(${( + await Promise.all( + args.pt.arguments + .reverse() + .map(async (ar) => (await args.fn(ar)).builder.toQuery()) + ) + ).join(',')})${args.colAlias}` ); }, MOD: (pt) => { @@ -73,19 +89,19 @@ const mssql = { }, REPEAT: 'REPLICATE', NOW: 'getdate', - SEARCH: (args: MapFnArgs) => { + SEARCH: async (args: MapFnArgs) => { args.pt.callee.name = 'CHARINDEX'; const temp = args.pt.arguments[0]; args.pt.arguments[0] = args.pt.arguments[1]; args.pt.arguments[1] = temp; }, - INT: (args: MapFnArgs) => { + INT: async (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}` + `CASE WHEN ISNUMERIC(${( + await args.fn(args.pt.arguments[0]) + ).builder.toQuery()}) = 1 THEN FLOOR(${( + await args.fn(args.pt.arguments[0]) + ).builder.toQuery()}) ELSE 0 END${args.colAlias}` ); }, MID: 'SUBSTR', @@ -94,8 +110,8 @@ const mssql = { .raw(`CAST(${args.fn(args.pt.arguments[0])} as FLOAT)${args.colAlias}`) .wrap('(', ')'); }, - DATEADD: ({ fn, knex, pt, colAlias }: MapFnArgs) => { - const dateIN = fn(pt.arguments[1]); + DATEADD: async ({ fn, knex, pt, colAlias }: MapFnArgs) => { + const dateIN = (await fn(pt.arguments[1])).builder; return knex.raw( `CASE WHEN ${fn(pt.arguments[0])} LIKE '%:%' THEN @@ -111,49 +127,59 @@ const mssql = { END${colAlias}` ); }, - DATETIME_DIFF: ({ fn, knex, pt, colAlias }: MapFnArgs) => { + DATETIME_DIFF: async ({ fn, knex, pt, colAlias }: MapFnArgs) => { const datetime_expr1 = fn(pt.arguments[0]); const datetime_expr2 = fn(pt.arguments[1]); const rawUnit = pt.arguments[2] - ? fn(pt.arguments[2]).bindings[0] + ? (await fn(pt.arguments[2])).builder.bindings[0] : 'seconds'; const unit = convertUnits(rawUnit, 'mssql'); return knex.raw( `DATEDIFF(${unit}, ${datetime_expr2}, ${datetime_expr1}) ${colAlias}` ); }, - WEEKDAY: ({ fn, knex, pt, colAlias }: MapFnArgs) => { + WEEKDAY: async ({ fn, knex, pt, colAlias }: MapFnArgs) => { // DATEPART(WEEKDAY, DATE): sunday = 1, monday = 2, ..., saturday = 7 // WEEKDAY() returns an index from 0 to 6 for Monday to Sunday return knex.raw( `(DATEPART(WEEKDAY, ${ pt.arguments[0].type === 'Literal' - ? `'${dayjs(fn(pt.arguments[0])).format('YYYY-MM-DD')}'` + ? `'${dayjs((await fn(pt.arguments[0])).builder).format( + 'YYYY-MM-DD' + )}'` : fn(pt.arguments[0]) }) - 2 - ${getWeekdayByText( pt?.arguments[1]?.value )} % 7 + 7) % 7 ${colAlias}` ); }, - AND: (args: MapFnArgs) => { + AND: async (args: MapFnArgs) => { return args.knex.raw( `CASE WHEN ${args.knex .raw( - `${args.pt.arguments - .map((ar) => args.fn(ar, '', 'AND').toQuery()) - .join(' AND ')}` + `${( + await Promise.all( + args.pt.arguments.map(async (ar) => + (await args.fn(ar, '', 'AND')).builder.toQuery() + ) + ) + ).join(' AND ')}` ) .wrap('(', ')') .toQuery()} THEN 1 ELSE 0 END ${args.colAlias}` ); }, - OR: (args: MapFnArgs) => { + OR: async (args: MapFnArgs) => { return args.knex.raw( `CASE WHEN ${args.knex .raw( - `${args.pt.arguments - .map((ar) => args.fn(ar, '', 'OR').toQuery()) - .join(' OR ')}` + `${( + await Promise.all( + args.pt.arguments.map(async (ar) => + (await args.fn(ar, '', 'OR')).builder.toQuery() + ) + ) + ).join(' OR ')}` ) .wrap('(', ')') .toQuery()} THEN 1 ELSE 0 END ${args.colAlias}` diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/mysql.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/mysql.ts index eae5c0f3f1..1c10b63434 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/mysql.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/mysql.ts @@ -1,99 +1,99 @@ -import dayjs from 'dayjs'; -import { convertUnits } from '../helpers/convertUnits'; -import { getWeekdayByText } from '../helpers/formulaFnHelper'; +// import dayjs from 'dayjs'; +// import { convertUnits } from '../helpers/convertUnits'; +// import { getWeekdayByText } from '../helpers/formulaFnHelper'; import commonFns from './commonFns'; -import type { MapFnArgs } from '../mapFunctionName'; +// import type { MapFnArgs } from '../mapFunctionName'; const mysql2 = { ...commonFns, - LEN: 'CHAR_LENGTH', - MIN: 'LEAST', - MAX: 'GREATEST', - SEARCH: (args: MapFnArgs) => { - args.pt.callee.name = 'LOCATE'; - const temp = args.pt.arguments[0]; - args.pt.arguments[0] = args.pt.arguments[1]; - args.pt.arguments[1] = temp; - }, - INT: (args: MapFnArgs) => { - return args.knex.raw( - `CAST(${args.fn(args.pt.arguments[0])} as SIGNED)${args.colAlias}` - ); - }, - 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) => { - return args.knex.raw( - `SUBSTR(${args.fn(args.pt.arguments[0])}, -(${args.fn( - args.pt.arguments[1] - )}))${args.colAlias}` - ); - }, - MID: 'SUBSTR', - FLOAT: (args: MapFnArgs) => { - return args.knex - .raw( - `CAST(CAST(${args.fn(args.pt.arguments[0])} as CHAR) AS DOUBLE)${ - args.colAlias - }` - ) - .wrap('(', ')'); - }, - DATEADD: ({ fn, knex, pt, colAlias }: MapFnArgs) => { - return knex.raw( - `CASE - WHEN ${fn(pt.arguments[0])} LIKE '%:%' THEN - DATE_FORMAT(DATE_ADD(${fn(pt.arguments[0])}, INTERVAL - ${fn(pt.arguments[1])} ${String(fn(pt.arguments[2])).replace( - /["']/g, - '' - )}), '%Y-%m-%d %H:%i') - ELSE - DATE(DATE_ADD(${fn(pt.arguments[0])}, INTERVAL - ${fn(pt.arguments[1])} ${String(fn(pt.arguments[2])).replace( - /["']/g, - '' - )})) - END${colAlias}` - ); - }, - DATETIME_DIFF: ({ fn, knex, pt, colAlias }: MapFnArgs) => { - const datetime_expr1 = fn(pt.arguments[0]); - const datetime_expr2 = fn(pt.arguments[1]); - - const unit = convertUnits( - pt.arguments[2] ? fn(pt.arguments[2]).bindings[0] : 'seconds', - 'mysql' - ); - - if (unit === 'MICROSECOND') { - // MySQL doesn't support millisecond - // hence change from MICROSECOND to millisecond manually - return knex.raw( - `TIMESTAMPDIFF(${unit}, ${datetime_expr2}, ${datetime_expr1}) div 1000 ${colAlias}` - ); - } - return knex.raw( - `TIMESTAMPDIFF(${unit}, ${datetime_expr2}, ${datetime_expr1}) ${colAlias}` - ); - }, - WEEKDAY: ({ fn, knex, pt, colAlias }: MapFnArgs) => { - // WEEKDAY() returns an index from 0 to 6 for Monday to Sunday - return knex.raw( - `(WEEKDAY(${ - pt.arguments[0].type === 'Literal' - ? `'${dayjs(fn(pt.arguments[0])).format('YYYY-MM-DD')}'` - : fn(pt.arguments[0]) - }) - ${getWeekdayByText( - pt?.arguments[1]?.value - )} % 7 + 7) % 7 ${colAlias}` - ); - }, + // LEN: 'CHAR_LENGTH', + // MIN: 'LEAST', + // MAX: 'GREATEST', + // SEARCH: (args: MapFnArgs) => { + // args.pt.callee.name = 'LOCATE'; + // const temp = args.pt.arguments[0]; + // args.pt.arguments[0] = args.pt.arguments[1]; + // args.pt.arguments[1] = temp; + // }, + // INT: (args: MapFnArgs) => { + // return args.knex.raw( + // `CAST(${args.fn(args.pt.arguments[0])} as SIGNED)${args.colAlias}` + // ); + // }, + // 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) => { + // return args.knex.raw( + // `SUBSTR(${args.fn(args.pt.arguments[0])}, -(${args.fn( + // args.pt.arguments[1] + // )}))${args.colAlias}` + // ); + // }, + // MID: 'SUBSTR', + // FLOAT: (args: MapFnArgs) => { + // return args.knex + // .raw( + // `CAST(CAST(${args.fn(args.pt.arguments[0])} as CHAR) AS DOUBLE)${ + // args.colAlias + // }` + // ) + // .wrap('(', ')'); + // }, + // DATEADD: ({ fn, knex, pt, colAlias }: MapFnArgs) => { + // return knex.raw( + // `CASE + // WHEN ${fn(pt.arguments[0])} LIKE '%:%' THEN + // DATE_FORMAT(DATE_ADD(${fn(pt.arguments[0])}, INTERVAL + // ${fn(pt.arguments[1])} ${String(fn(pt.arguments[2])).replace( + // /["']/g, + // '' + // )}), '%Y-%m-%d %H:%i') + // ELSE + // DATE(DATE_ADD(${fn(pt.arguments[0])}, INTERVAL + // ${fn(pt.arguments[1])} ${String(fn(pt.arguments[2])).replace( + // /["']/g, + // '' + // )})) + // END${colAlias}` + // ); + // }, + // DATETIME_DIFF: ({ fn, knex, pt, colAlias }: MapFnArgs) => { + // const datetime_expr1 = fn(pt.arguments[0]); + // const datetime_expr2 = fn(pt.arguments[1]); + // + // const unit = convertUnits( + // pt.arguments[2] ? fn(pt.arguments[2]).bindings[0] : 'seconds', + // 'mysql' + // ); + // + // if (unit === 'MICROSECOND') { + // // MySQL doesn't support millisecond + // // hence change from MICROSECOND to millisecond manually + // return knex.raw( + // `TIMESTAMPDIFF(${unit}, ${datetime_expr2}, ${datetime_expr1}) div 1000 ${colAlias}` + // ); + // } + // return knex.raw( + // `TIMESTAMPDIFF(${unit}, ${datetime_expr2}, ${datetime_expr1}) ${colAlias}` + // ); + // }, + // WEEKDAY: ({ fn, knex, pt, colAlias }: MapFnArgs) => { + // // WEEKDAY() returns an index from 0 to 6 for Monday to Sunday + // return knex.raw( + // `(WEEKDAY(${ + // pt.arguments[0].type === 'Literal' + // ? `'${dayjs(fn(pt.arguments[0])).format('YYYY-MM-DD')}'` + // : fn(pt.arguments[0]) + // }) - ${getWeekdayByText( + // pt?.arguments[1]?.value + // )} % 7 + 7) % 7 ${colAlias}` + // ); + // }, }; export default mysql2; diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/pg.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/pg.ts index 0efcfc9cb3..b361996859 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/pg.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/pg.ts @@ -12,13 +12,13 @@ const pg = { CEILING: 'ceil', POWER: 'pow', SQRT: 'sqrt', - SEARCH: (args: MapFnArgs) => { + SEARCH: async (args: MapFnArgs) => { return args.knex.raw( `POSITION(${args.knex.raw( - args.fn(args.pt.arguments[1]).toQuery() - )} in ${args.knex.raw(args.fn(args.pt.arguments[0]).toQuery())})${ - args.colAlias - }` + (await args.fn(args.pt.arguments[1])).builder.toQuery() + )} in ${args.knex + .raw((await args.fn(args.pt.arguments[0])).builder) + .toQuery()})${args.colAlias}` ); }, INT(args: MapFnArgs) { @@ -51,11 +51,11 @@ const pg = { )}')::interval${colAlias}` ); }, - DATETIME_DIFF: ({ fn, knex, pt, colAlias }: MapFnArgs) => { + DATETIME_DIFF: async ({ fn, knex, pt, colAlias }: MapFnArgs) => { const datetime_expr1 = fn(pt.arguments[0]); const datetime_expr2 = fn(pt.arguments[1]); const rawUnit = pt.arguments[2] - ? fn(pt.arguments[2]).bindings[0] + ? (await fn(pt.arguments[2])).builder.bindings[0] : 'seconds'; let sql; const unit = convertUnits(rawUnit, 'pg'); @@ -101,37 +101,39 @@ const pg = { } return knex.raw(`${sql} ${colAlias}`); }, - WEEKDAY: ({ fn, knex, pt, colAlias }: MapFnArgs) => { + WEEKDAY: async ({ fn, knex, pt, colAlias }: MapFnArgs) => { // isodow: the day of the week as Monday (1) to Sunday (7) // WEEKDAY() returns an index from 0 to 6 for Monday to Sunday return knex.raw( `(EXTRACT(ISODOW FROM ${ pt.arguments[0].type === 'Literal' - ? `date '${dayjs(fn(pt.arguments[0])).format('YYYY-MM-DD')}'` + ? `date '${dayjs((await fn(pt.arguments[0])).builder).format( + 'YYYY-MM-DD' + )}'` : fn(pt.arguments[0]) }) - 1 - ${getWeekdayByText( pt?.arguments[1]?.value )} % 7 + 7) ::INTEGER % 7 ${colAlias}` ); }, - AND: (args: MapFnArgs) => { + AND: async (args: MapFnArgs) => { return args.knex.raw( `CASE WHEN ${args.knex .raw( `${args.pt.arguments - .map((ar) => args.fn(ar, '', 'AND').toQuery()) + .map(async (ar) => (await args.fn(ar, '', 'AND')).builder.toQuery()) .join(' AND ')}` ) .wrap('(', ')') .toQuery()} THEN TRUE ELSE FALSE END ${args.colAlias}` ); }, - OR: (args: MapFnArgs) => { + OR: async (args: MapFnArgs) => { return args.knex.raw( `CASE WHEN ${args.knex .raw( `${args.pt.arguments - .map((ar) => args.fn(ar, '', 'OR').toQuery()) + .map(async (ar) => (await args.fn(ar, '', 'OR')).builder.toQuery()) .join(' OR ')}` ) .wrap('(', ')') diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/sqlite.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/sqlite.ts index d4bfb56d82..187c8cf156 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/sqlite.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/sqlite.ts @@ -1,198 +1,198 @@ -import dayjs from 'dayjs'; -import { convertUnits } from '../helpers/convertUnits'; -import { getWeekdayByText } from '../helpers/formulaFnHelper'; -import { - convertToTargetFormat, - getDateFormat, -} from '../../../../../utils/dateTimeUtils'; +// import dayjs from 'dayjs'; +// import { convertUnits } from '../helpers/convertUnits'; +// import { getWeekdayByText } from '../helpers/formulaFnHelper'; +// import { +// convertToTargetFormat, +// getDateFormat, +// } from '../../../../../utils/dateTimeUtils'; import commonFns from './commonFns'; -import type { MapFnArgs } from '../mapFunctionName'; +// import type { MapFnArgs } from '../mapFunctionName'; const sqlite3 = { ...commonFns, - LEN: 'LENGTH', - 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: (args: MapFnArgs) => { - return args.fn({ - type: 'BinaryExpression', - operator: '%', - 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', - INT(args: MapFnArgs) { - return args.knex.raw( - `CAST(${args.fn(args.pt.arguments[0])} as INTEGER)${args.colAlias}` - ); - }, - 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) => { - return args.knex.raw( - `SUBSTR(${args.fn(args.pt.arguments[0])},-(${args.fn( - args.pt.arguments[1] - )}))${args.colAlias}` - ); - }, - MID: 'SUBSTR', - FLOAT: (args: MapFnArgs) => { - return args.knex - .raw(`CAST(${args.fn(args.pt.arguments[0])} as FLOAT)${args.colAlias}`) - .wrap('(', ')'); - }, - DATEADD: ({ fn, knex, pt, colAlias }: MapFnArgs) => { - const dateIN = fn(pt.arguments[1]); - return knex.raw( - `CASE - WHEN ${fn(pt.arguments[0])} LIKE '%:%' THEN - STRFTIME('%Y-%m-%d %H:%M', DATETIME(DATETIME(${fn( - pt.arguments[0] - )}, 'localtime'), - ${dateIN > 0 ? '+' : ''}${fn(pt.arguments[1])} || ' ${String( - fn(pt.arguments[2]) - ).replace(/["']/g, '')}')) - ELSE - DATE(DATETIME(${fn(pt.arguments[0])}, 'localtime'), - ${dateIN > 0 ? '+' : ''}${fn(pt.arguments[1])} || ' ${String( - fn(pt.arguments[2]) - ).replace(/["']/g, '')}') - END${colAlias}` - ); - }, - DATETIME_DIFF: ({ fn, knex, pt, colAlias }: MapFnArgs) => { - let datetime_expr1 = fn(pt.arguments[0]); - let datetime_expr2 = fn(pt.arguments[1]); - // JULIANDAY takes YYYY-MM-DD - if (datetime_expr1.sql === '?' && datetime_expr1.bindings?.[0]) { - datetime_expr1 = `'${convertToTargetFormat( - datetime_expr1.bindings[0], - getDateFormat(datetime_expr1.bindings[0]), - 'YYYY-MM-DD' - )}'`; - } - - if (datetime_expr2.sql === '?' && datetime_expr2.bindings?.[0]) { - datetime_expr2 = `'${convertToTargetFormat( - datetime_expr2.bindings[0], - getDateFormat(datetime_expr2.bindings[0]), - 'YYYY-MM-DD' - )}'`; - } - - const rawUnit = pt.arguments[2] - ? fn(pt.arguments[2]).bindings[0] - : 'seconds'; - let sql; - const unit = convertUnits(rawUnit, 'sqlite'); - switch (unit) { - case 'seconds': - sql = `(strftime('%s', ${datetime_expr1}) - strftime('%s', ${datetime_expr2}))`; - break; - case 'minutes': - sql = `(strftime('%s', ${datetime_expr1}) - strftime('%s', ${datetime_expr2})) / 60`; - break; - case 'hours': - sql = `(strftime('%s', ${datetime_expr1}) - strftime('%s', ${datetime_expr2})) / 3600`; - break; - case 'milliseconds': - sql = `(strftime('%s', ${datetime_expr1}) - strftime('%s', ${datetime_expr2})) * 1000`; - break; - case 'weeks': - sql = `ROUND((JULIANDAY(${datetime_expr1}) - JULIANDAY(${datetime_expr2})) / 7)`; - break; - case 'months': - sql = `(strftime('%Y', ${datetime_expr1}) - strftime('%Y', ${datetime_expr2})) * 12 + (strftime('%m', ${datetime_expr1}) - strftime('%m', ${datetime_expr2})) `; - break; - case 'quarters': - sql = `(strftime('%Y', ${datetime_expr1}) - strftime('%Y', ${datetime_expr2})) * 4 + (strftime('%m', ${datetime_expr1}) - strftime('%m', ${datetime_expr2})) / 3`; - break; - case 'years': - sql = `CASE - WHEN (${datetime_expr2} < ${datetime_expr1}) THEN - ( - (strftime('%Y', ${datetime_expr1}) - strftime('%Y', ${datetime_expr2})) - - (strftime('%m', ${datetime_expr1}) < strftime('%m', ${datetime_expr2}) - OR (strftime('%m', ${datetime_expr1}) = strftime('%m', ${datetime_expr2}) - AND strftime('%d', ${datetime_expr1}) < strftime('%d', ${datetime_expr2}))) - ) - WHEN (${datetime_expr2} > ${datetime_expr1}) THEN - -1 * ( - (strftime('%Y', ${datetime_expr2}) - strftime('%Y', ${datetime_expr1})) - - (strftime('%m', ${datetime_expr2}) < strftime('%m', ${datetime_expr1}) - OR (strftime('%m', ${datetime_expr2}) = strftime('%m', ${datetime_expr1}) - AND strftime('%d', ${datetime_expr2}) < strftime('%d', ${datetime_expr1}))) - ) - ELSE 0 - END`; - break; - case 'days': - sql = `JULIANDAY(${datetime_expr1}) - JULIANDAY(${datetime_expr2})`; - break; - default: - sql = ''; - } - return knex.raw(`${sql} ${colAlias}`); - }, - WEEKDAY: ({ fn, knex, pt, colAlias }: MapFnArgs) => { - // strftime('%w', date) - day of week 0 - 6 with Sunday == 0 - // WEEKDAY() returns an index from 0 to 6 for Monday to Sunday - return knex.raw( - `(strftime('%w', ${ - pt.arguments[0].type === 'Literal' - ? `'${dayjs(fn(pt.arguments[0])).format('YYYY-MM-DD')}'` - : fn(pt.arguments[0]) - }) - 1 - ${getWeekdayByText( - pt?.arguments[1]?.value - )} % 7 + 7) % 7 ${colAlias}` - ); - }, - AND: (args: MapFnArgs) => { - return args.knex.raw( - `CASE WHEN ${args.knex - .raw( - `${args.pt.arguments - .map((ar) => args.fn(ar, '', 'AND').toQuery()) - .join(' AND ')}` - ) - .wrap('(', ')') - .toQuery()} THEN 1 ELSE 0 END ${args.colAlias}` - ); - }, - OR: (args: MapFnArgs) => { - return args.knex.raw( - `CASE WHEN ${args.knex - .raw( - `${args.pt.arguments - .map((ar) => args.fn(ar, '', 'OR').toQuery()) - .join(' OR ')}` - ) - .wrap('(', ')') - .toQuery()} THEN 1 ELSE 0 END ${args.colAlias}` - ); - }, + // LEN: 'LENGTH', + // 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: (args: MapFnArgs) => { + // return args.fn({ + // type: 'BinaryExpression', + // operator: '%', + // 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', + // INT(args: MapFnArgs) { + // return args.knex.raw( + // `CAST(${args.fn(args.pt.arguments[0])} as INTEGER)${args.colAlias}` + // ); + // }, + // 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) => { + // return args.knex.raw( + // `SUBSTR(${args.fn(args.pt.arguments[0])},-(${args.fn( + // args.pt.arguments[1] + // )}))${args.colAlias}` + // ); + // }, + // MID: 'SUBSTR', + // FLOAT: (args: MapFnArgs) => { + // return args.knex + // .raw(`CAST(${args.fn(args.pt.arguments[0])} as FLOAT)${args.colAlias}`) + // .wrap('(', ')'); + // }, + // DATEADD: ({ fn, knex, pt, colAlias }: MapFnArgs) => { + // const dateIN = fn(pt.arguments[1]); + // return knex.raw( + // `CASE + // WHEN ${fn(pt.arguments[0])} LIKE '%:%' THEN + // STRFTIME('%Y-%m-%d %H:%M', DATETIME(DATETIME(${fn( + // pt.arguments[0] + // )}, 'localtime'), + // ${dateIN > 0 ? '+' : ''}${fn(pt.arguments[1])} || ' ${String( + // fn(pt.arguments[2]) + // ).replace(/["']/g, '')}')) + // ELSE + // DATE(DATETIME(${fn(pt.arguments[0])}, 'localtime'), + // ${dateIN > 0 ? '+' : ''}${fn(pt.arguments[1])} || ' ${String( + // fn(pt.arguments[2]) + // ).replace(/["']/g, '')}') + // END${colAlias}` + // ); + // }, + // DATETIME_DIFF: ({ fn, knex, pt, colAlias }: MapFnArgs) => { + // let datetime_expr1 = fn(pt.arguments[0]); + // let datetime_expr2 = fn(pt.arguments[1]); + // // JULIANDAY takes YYYY-MM-DD + // if (datetime_expr1.sql === '?' && datetime_expr1.bindings?.[0]) { + // datetime_expr1 = `'${convertToTargetFormat( + // datetime_expr1.bindings[0], + // getDateFormat(datetime_expr1.bindings[0]), + // 'YYYY-MM-DD' + // )}'`; + // } + // + // if (datetime_expr2.sql === '?' && datetime_expr2.bindings?.[0]) { + // datetime_expr2 = `'${convertToTargetFormat( + // datetime_expr2.bindings[0], + // getDateFormat(datetime_expr2.bindings[0]), + // 'YYYY-MM-DD' + // )}'`; + // } + // + // const rawUnit = pt.arguments[2] + // ? fn(pt.arguments[2]).bindings[0] + // : 'seconds'; + // let sql; + // const unit = convertUnits(rawUnit, 'sqlite'); + // switch (unit) { + // case 'seconds': + // sql = `(strftime('%s', ${datetime_expr1}) - strftime('%s', ${datetime_expr2}))`; + // break; + // case 'minutes': + // sql = `(strftime('%s', ${datetime_expr1}) - strftime('%s', ${datetime_expr2})) / 60`; + // break; + // case 'hours': + // sql = `(strftime('%s', ${datetime_expr1}) - strftime('%s', ${datetime_expr2})) / 3600`; + // break; + // case 'milliseconds': + // sql = `(strftime('%s', ${datetime_expr1}) - strftime('%s', ${datetime_expr2})) * 1000`; + // break; + // case 'weeks': + // sql = `ROUND((JULIANDAY(${datetime_expr1}) - JULIANDAY(${datetime_expr2})) / 7)`; + // break; + // case 'months': + // sql = `(strftime('%Y', ${datetime_expr1}) - strftime('%Y', ${datetime_expr2})) * 12 + (strftime('%m', ${datetime_expr1}) - strftime('%m', ${datetime_expr2})) `; + // break; + // case 'quarters': + // sql = `(strftime('%Y', ${datetime_expr1}) - strftime('%Y', ${datetime_expr2})) * 4 + (strftime('%m', ${datetime_expr1}) - strftime('%m', ${datetime_expr2})) / 3`; + // break; + // case 'years': + // sql = `CASE + // WHEN (${datetime_expr2} < ${datetime_expr1}) THEN + // ( + // (strftime('%Y', ${datetime_expr1}) - strftime('%Y', ${datetime_expr2})) + // - (strftime('%m', ${datetime_expr1}) < strftime('%m', ${datetime_expr2}) + // OR (strftime('%m', ${datetime_expr1}) = strftime('%m', ${datetime_expr2}) + // AND strftime('%d', ${datetime_expr1}) < strftime('%d', ${datetime_expr2}))) + // ) + // WHEN (${datetime_expr2} > ${datetime_expr1}) THEN + // -1 * ( + // (strftime('%Y', ${datetime_expr2}) - strftime('%Y', ${datetime_expr1})) + // - (strftime('%m', ${datetime_expr2}) < strftime('%m', ${datetime_expr1}) + // OR (strftime('%m', ${datetime_expr2}) = strftime('%m', ${datetime_expr1}) + // AND strftime('%d', ${datetime_expr2}) < strftime('%d', ${datetime_expr1}))) + // ) + // ELSE 0 + // END`; + // break; + // case 'days': + // sql = `JULIANDAY(${datetime_expr1}) - JULIANDAY(${datetime_expr2})`; + // break; + // default: + // sql = ''; + // } + // return knex.raw(`${sql} ${colAlias}`); + // }, + // WEEKDAY: ({ fn, knex, pt, colAlias }: MapFnArgs) => { + // // strftime('%w', date) - day of week 0 - 6 with Sunday == 0 + // // WEEKDAY() returns an index from 0 to 6 for Monday to Sunday + // return knex.raw( + // `(strftime('%w', ${ + // pt.arguments[0].type === 'Literal' + // ? `'${dayjs(fn(pt.arguments[0])).format('YYYY-MM-DD')}'` + // : fn(pt.arguments[0]) + // }) - 1 - ${getWeekdayByText( + // pt?.arguments[1]?.value + // )} % 7 + 7) % 7 ${colAlias}` + // ); + // }, + // AND: (args: MapFnArgs) => { + // return args.knex.raw( + // `CASE WHEN ${args.knex + // .raw( + // `${args.pt.arguments + // .map((ar) => args.fn(ar, '', 'AND').toQuery()) + // .join(' AND ')}` + // ) + // .wrap('(', ')') + // .toQuery()} THEN 1 ELSE 0 END ${args.colAlias}` + // ); + // }, + // OR: (args: MapFnArgs) => { + // return args.knex.raw( + // `CASE WHEN ${args.knex + // .raw( + // `${args.pt.arguments + // .map((ar) => args.fn(ar, '', 'OR').toQuery()) + // .join(' OR ')}` + // ) + // .wrap('(', ')') + // .toQuery()} THEN 1 ELSE 0 END ${args.colAlias}` + // ); + // }, }; export default sqlite3; diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/mapFunctionName.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/mapFunctionName.ts index 49abd50ac2..001e82fa26 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/mapFunctionName.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/mapFunctionName.ts @@ -7,16 +7,19 @@ import type { Knex } from 'knex'; export interface MapFnArgs { pt: any; - aliasToCol: { [alias: string]: string }; + aliasToCol: Record< + string, + (() => Promise<{ builder: any }>) | string | undefined + >; knex: XKnex; alias: string; a?: string; - fn: (...args: any) => Knex.QueryBuilder | any; + fn: (...args: any) => Promise<{ builder: Knex.QueryBuilder | any }>; colAlias: string; prevBinaryOp?: any; } -const mapFunctionName = (args: MapFnArgs): any => { +const mapFunctionName = async (args: MapFnArgs): Promise => { const name = args.pt.callee.name; let val;