From 4e7a543fccf2f6b06dbd602429a2cb7fb2a26d9a Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 15 Apr 2023 17:01:15 +0800 Subject: [PATCH] fix: formula concat --- .../src/db/formulav2/formulaQueryBuilderv2.ts | 41 +++++++++++++---- .../src/helpers/convertDateFormat.ts | 23 ++++++++++ .../src/helpers/formulaFnHelper.ts | 45 +++++++++++++++++++ 3 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 packages/nocodb-nest/src/helpers/convertDateFormat.ts diff --git a/packages/nocodb-nest/src/db/formulav2/formulaQueryBuilderv2.ts b/packages/nocodb-nest/src/db/formulav2/formulaQueryBuilderv2.ts index b16594ed37..6953c880bb 100644 --- a/packages/nocodb-nest/src/db/formulav2/formulaQueryBuilderv2.ts +++ b/packages/nocodb-nest/src/db/formulav2/formulaQueryBuilderv2.ts @@ -3,12 +3,12 @@ import { jsepCurlyHook, UITypes } from 'nocodb-sdk'; import mapFunctionName from '../mapFunctionName'; import genRollupSelectv2 from '../genRollupSelectv2'; import FormulaColumn from '../../models/FormulaColumn'; -import { validateDateWithUnknownFormat } from '../../helpers/formulaFnHelper'; +import { convertDateFormatForConcat, validateDateWithUnknownFormat } from '../../helpers/formulaFnHelper'; import { CacheGetType, CacheScope } from '../../utils/globals'; import NocoCache from '../../cache/NocoCache'; import type { XKnex } from '../CustomKnex'; -import type Model from '../../models/Model'; import type Column from '../../models/Column'; +import type Model from '../../models/Model'; import type RollupColumn from '../../models/RollupColumn'; import type LinkToAnotherRecordColumn from '../../models/LinkToAnotherRecordColumn'; import type LookupColumn from '../../models/LookupColumn'; @@ -633,8 +633,19 @@ async function _formulaQueryBuilder( `${pt.callee.name}(${( await Promise.all( pt.arguments.map(async (arg) => { - const query = (await fn(arg)).builder.toQuery(); + let query = (await fn(arg)).builder.toQuery(); if (pt.callee.name === 'CONCAT') { + if (knex.clientType() !== 'sqlite3') { + query = await convertDateFormatForConcat( + arg, + columnIdToUidt, + query, + knex.clientType() + ); + } else { + // sqlite3: special handling - See BinaryExpression + } + if (knex.clientType() === 'mysql2') { // mysql2: CONCAT() returns NULL if any argument is NULL. // adding IFNULL to convert NULL values to empty strings @@ -646,9 +657,9 @@ async function _formulaQueryBuilder( } } return query; - }), + }) ) - ).join()})${colAlias}`.replace(/\?/g, '\\?'), + ).join()})${colAlias}`.replace(/\?/g, '\\?') ), }; } else if (pt.type === 'Literal') { @@ -679,8 +690,8 @@ async function _formulaQueryBuilder( pt.left.fnName = pt.left.fnName || 'ARITH'; pt.right.fnName = pt.right.fnName || 'ARITH'; - const left = (await fn(pt.left, null, pt.operator)).builder.toQuery(); - const right = (await fn(pt.right, null, pt.operator)).builder.toQuery(); + let left = (await fn(pt.left, null, pt.operator)).builder.toQuery(); + let right = (await fn(pt.right, null, pt.operator)).builder.toQuery(); let sql = `${left} ${pt.operator} ${right}${colAlias}`; // comparing a date with empty string would throw @@ -724,8 +735,22 @@ async function _formulaQueryBuilder( } } - // handle NULL values when calling CONCAT for sqlite3 if (pt.left.fnName === 'CONCAT' && knex.clientType() === 'sqlite3') { + // handle date format + left = await convertDateFormatForConcat( + pt.left?.arguments?.[0], + columnIdToUidt, + left, + knex.clientType() + ); + right = await convertDateFormatForConcat( + pt.right?.arguments?.[0], + columnIdToUidt, + right, + knex.clientType() + ); + + // handle NULL values when calling CONCAT for sqlite3 sql = `COALESCE(${left}, '') ${pt.operator} COALESCE(${right},'')${colAlias}`; } diff --git a/packages/nocodb-nest/src/helpers/convertDateFormat.ts b/packages/nocodb-nest/src/helpers/convertDateFormat.ts new file mode 100644 index 0000000000..4b3d681a5c --- /dev/null +++ b/packages/nocodb-nest/src/helpers/convertDateFormat.ts @@ -0,0 +1,23 @@ +export function convertDateFormat(date_format: string, type: string) { + if (date_format === 'YYYY-MM-DD') { + if (type === 'mysql2' || type === 'sqlite3') return '%Y-%m-%d'; + } else if (date_format === 'YYYY/MM/DD') { + if (type === 'mysql2' || type === 'sqlite3') return '%Y/%m/%d'; + } else if (date_format === 'DD-MM-YYYY') { + if (type === 'mysql2' || type === 'sqlite3') return '%d/%m/%Y'; + } else if (date_format === 'MM-DD-YYYY') { + if (type === 'mysql2' || type === 'sqlite3') return '%d-%m-%Y'; + } else if (date_format === 'DD/MM/YYYY') { + if (type === 'mysql2' || type === 'sqlite3') return '%d/%m/%Y'; + } else if (date_format === 'MM/DD/YYYY') { + if (type === 'mysql2' || type === 'sqlite3') return '%m-%d-%Y'; + } else if (date_format === 'DD MM YYYY') { + if (type === 'mysql2' || type === 'sqlite3') return '%d %m %Y'; + } else if (date_format === 'MM DD YYYY') { + if (type === 'mysql2' || type === 'sqlite3') return '%m %d %Y'; + } else if (date_format === 'YYYY MM DD') { + if (type === 'mysql2' || type === 'sqlite3') return '%Y %m %d'; + } + // pg / mssql + return date_format; + } \ No newline at end of file diff --git a/packages/nocodb-nest/src/helpers/formulaFnHelper.ts b/packages/nocodb-nest/src/helpers/formulaFnHelper.ts index 8b2de80954..d03068361e 100644 --- a/packages/nocodb-nest/src/helpers/formulaFnHelper.ts +++ b/packages/nocodb-nest/src/helpers/formulaFnHelper.ts @@ -1,4 +1,7 @@ import dayjs from 'dayjs'; +import { UITypes } from 'nocodb-sdk'; +import Column from '../models/Column'; +import { convertDateFormat } from './convertDateFormat'; // todo: tobe fixed // import customParseFormat from 'dayjs/plugin/customParseFormat'; @@ -52,3 +55,45 @@ export function validateDateWithUnknownFormat(v: string) { } return false; } + +export async function convertDateFormatForConcat( + o, + columnIdToUidt, + query, + clientType +) { + if ( + o?.type === 'Identifier' && + o?.name in columnIdToUidt && + columnIdToUidt[o.name] === UITypes.Date + ) { + const meta = ( + await Column.get({ + colId: o.name, + }) + ).meta; + + if (clientType === 'mysql2') { + query = `DATE_FORMAT(${query}, '${convertDateFormat( + meta.date_format, + clientType + )}')`; + } else if (clientType === 'pg') { + query = `TO_CHAR(${query}, '${convertDateFormat( + meta.date_format, + clientType + )}')`; + } else if (clientType === 'sqlite3') { + query = `strftime('${convertDateFormat( + meta.date_format, + clientType + )}', ${query})`; + } else if (clientType === 'mssql') { + query = `FORMAT(${query}, '${convertDateFormat( + meta.date_format, + clientType + )}')`; + } + } + return query; +} \ No newline at end of file