Browse Source

Merge pull request #4586 from nocodb/fix/if-formula-with-date-empty-string

fix(nocodb): handle comparing a date with empty string in pg
pull/4654/head
աɨռɢӄաօռɢ 2 years ago committed by GitHub
parent
commit
4bbcec8b62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 45
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts
  2. 29
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/helpers/formulaFnHelper.ts

45
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts

@ -8,6 +8,7 @@ import { XKnex } from '../../../index';
import LinkToAnotherRecordColumn from '../../../../../models/LinkToAnotherRecordColumn'; import LinkToAnotherRecordColumn from '../../../../../models/LinkToAnotherRecordColumn';
import LookupColumn from '../../../../../models/LookupColumn'; import LookupColumn from '../../../../../models/LookupColumn';
import { jsepCurlyHook, UITypes } from 'nocodb-sdk'; import { jsepCurlyHook, UITypes } from 'nocodb-sdk';
import { validateDateWithUnknownFormat } from '../helpers/formulaFnHelper';
// todo: switch function based on database // todo: switch function based on database
@ -55,8 +56,11 @@ export default async function formulaQueryBuilderv2(
jsep.plugins.register(jsepCurlyHook); jsep.plugins.register(jsepCurlyHook);
const tree = jsep(_tree); const tree = jsep(_tree);
const columnIdToUidt = {};
// todo: improve - implement a common solution for filter, sort, formula, etc // todo: improve - implement a common solution for filter, sort, formula, etc
for (const col of await model.getColumns()) { for (const col of await model.getColumns()) {
columnIdToUidt[col.id] = col.uidt;
if (col.id in aliasToColumn) continue; if (col.id in aliasToColumn) continue;
switch (col.uidt) { switch (col.uidt) {
case UITypes.Formula: case UITypes.Formula:
@ -659,6 +663,47 @@ export default async function formulaQueryBuilderv2(
const right = fn(pt.right, null, pt.operator).toQuery(); const right = fn(pt.right, null, pt.operator).toQuery();
let sql = `${left} ${pt.operator} ${right}${colAlias}`; let sql = `${left} ${pt.operator} ${right}${colAlias}`;
// comparing a date with empty string would throw
// `ERROR: zero-length delimited identifier` in Postgres
if (
knex.clientType() === 'pg' &&
columnIdToUidt[pt.left.name] === UITypes.Date
) {
// The correct way to compare with Date should be using
// `IS_AFTER`, `IS_BEFORE`, or `IS_SAME`
// This is to prevent empty data returned to UI due to incorrect SQL
if (pt.right.value === '') {
if (pt.operator === '=') {
sql = `${left} IS NULL ${colAlias}`;
} else {
sql = `${left} IS NOT NULL ${colAlias}`;
}
} else if (!validateDateWithUnknownFormat(pt.right.value)) {
// left tree value is date but right tree value is not date
// return true if left tree value is not null, else false
sql = `${left} IS NOT NULL ${colAlias}`;
}
}
if (
knex.clientType() === 'pg' &&
columnIdToUidt[pt.right.name] === UITypes.Date
) {
// The correct way to compare with Date should be using
// `IS_AFTER`, `IS_BEFORE`, or `IS_SAME`
// This is to prevent empty data returned to UI due to incorrect SQL
if (pt.left.value === '') {
if (pt.operator === '=') {
sql = `${right} IS NULL ${colAlias}`;
} else {
sql = `${right} IS NOT NULL ${colAlias}`;
}
} else if (!validateDateWithUnknownFormat(pt.left.value)) {
// right tree value is date but left tree value is not date
// return true if right tree value is not null, else false
sql = `${right} IS NOT NULL ${colAlias}`;
}
}
// handle NULL values when calling CONCAT for sqlite3 // handle NULL values when calling CONCAT for sqlite3
if (pt.left.fnName === 'CONCAT' && knex.clientType() === 'sqlite3') { if (pt.left.fnName === 'CONCAT' && knex.clientType() === 'sqlite3') {
sql = `COALESCE(${left}, '') ${pt.operator} COALESCE(${right},'')${colAlias}`; sql = `COALESCE(${left}, '') ${pt.operator} COALESCE(${right},'')${colAlias}`;

29
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/helpers/formulaFnHelper.ts

@ -1,3 +1,7 @@
import dayjs, { extend } from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat.js';
extend(customParseFormat);
export function getWeekdayByText(v: string) { export function getWeekdayByText(v: string) {
return { return {
monday: 0, monday: 0,
@ -21,3 +25,28 @@ export function getWeekdayByIndex(idx: number): string {
6: 'sunday', 6: 'sunday',
}[idx || 0]; }[idx || 0];
} }
export function validateDateWithUnknownFormat(v: string) {
const dateFormats = [
'DD-MM-YYYY',
'MM-DD-YYYY',
'YYYY-MM-DD',
'DD/MM/YYYY',
'MM/DD/YYYY',
'YYYY/MM/DD',
'DD MM YYYY',
'MM DD YYYY',
'YYYY MM DD',
];
for (const format of dateFormats) {
if (dayjs(v, format, true).isValid() as any) {
return true;
}
for (const timeFormat of ['HH:mm', 'HH:mm:ss', 'HH:mm:ss.SSS']) {
if (dayjs(v, `${format} ${timeFormat}`, true).isValid() as any) {
return true;
}
}
}
return false;
}

Loading…
Cancel
Save