From bc62aa726438d24769ab582dd6ef70845feb85d5 Mon Sep 17 00:00:00 2001 From: Tushar Motwani Date: Mon, 27 Jul 2026 20:06:48 +0530 Subject: [PATCH 1/2] fix[table-block]: fix filtering on built in timestamp columns --- apps/sim/app/api/table/utils.ts | 17 +++++++++++++++-- apps/sim/lib/table/sql.ts | 21 +++++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/apps/sim/app/api/table/utils.ts b/apps/sim/app/api/table/utils.ts index 7424258ad0e..2aefbb5b4cb 100644 --- a/apps/sim/app/api/table/utils.ts +++ b/apps/sim/app/api/table/utils.ts @@ -156,7 +156,10 @@ async function checkTableAccess(tableId: string, userId: string): Promise '{"field": value}'::jsonb` (uses GIN index) */ function buildContainmentClause(tableName: string, field: string, value: JsonValue): SQL { + if (isBuiltInDateField(field)) { + return sql`${sql.raw(`${tableName}.${field}`)} = ${value}::timestamptz` + } + const jsonObj = JSON.stringify({ [field]: value }) return sql`${sql.raw(`${tableName}.data`)} @> ${jsonObj}::jsonb` + + // const jsonObj = JSON.stringify({ [field]: value }) + // return sql`${sql.raw(`${tableName}.data`)} @> ${jsonObj}::jsonb` } /** @@ -485,10 +495,17 @@ function buildComparisonClause( value: number | string, columnType: ColumnType | undefined ): SQL { + if (isBuiltInDateField(field)) { + columnType = 'date' + } + const escapedField = field.replace(/'/g, "''") const cast = jsonbCastForType(columnType) ?? 'numeric' validateComparisonValue(field, columnType, cast, value) - const cell = sql.raw(`(${tableName}.data->>'${escapedField}')::${cast}`) + + const cell = isBuiltInDateField(field) + ? sql.raw(`${tableName}.${field}`) + : sql.raw(`(${tableName}.data->>'${escapedField}')::${cast}`) return cast === 'timestamptz' ? sql`${cell} ${sql.raw(operator)} ${value}::timestamptz` : sql`${cell} ${sql.raw(operator)} ${value}` From 56cb60b5f518e9c1b6f5a4004a5ace35ff20473a Mon Sep 17 00:00:00 2001 From: Tushar Motwani Date: Mon, 27 Jul 2026 21:08:56 +0530 Subject: [PATCH 2/2] refactor[table]: use import alias for shared helper and keep the column names consistent --- apps/sim/app/api/table/utils.ts | 2 +- apps/sim/lib/table/__tests__/sql.test.ts | 32 +++++++++++++++++++++--- apps/sim/lib/table/sql.ts | 2 +- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/apps/sim/app/api/table/utils.ts b/apps/sim/app/api/table/utils.ts index 2aefbb5b4cb..28f08241a76 100644 --- a/apps/sim/app/api/table/utils.ts +++ b/apps/sim/app/api/table/utils.ts @@ -289,7 +289,7 @@ export function normalizeColumn(col: ColumnDefinition): ColumnDefinition { } export function isBuiltInDateField(field: string): boolean { - if (field === 'created_at' || field === 'updated_at') { + if (['created_at', 'updated_at', 'createdAt', 'updatedAt'].includes(field)) { return true } return false diff --git a/apps/sim/lib/table/__tests__/sql.test.ts b/apps/sim/lib/table/__tests__/sql.test.ts index ca293848779..bbfa3de5fc7 100644 --- a/apps/sim/lib/table/__tests__/sql.test.ts +++ b/apps/sim/lib/table/__tests__/sql.test.ts @@ -227,7 +227,9 @@ describe('SQL Builder', () => { it('handles nested $or and $and', () => { const out = render( buildFilterClause( - { $or: [{ $and: [{ status: 'active' }, { verified: true }] }, { role: 'admin' }] }, + { + $or: [{ $and: [{ status: 'active' }, { verified: true }] }, { role: 'admin' }], + }, TABLE, NO_COLUMNS ) @@ -297,7 +299,9 @@ describe('SQL Builder', () => { it('propagates date cast through nested $and', () => { const out = render( buildFilterClause( - { $and: [{ birthDate: { $gte: '2024-01-01' } }, { birthDate: { $lt: '2025-01-01' } }] }, + { + $and: [{ birthDate: { $gte: '2024-01-01' } }, { birthDate: { $lt: '2025-01-01' } }], + }, TABLE, dateCols ) @@ -309,7 +313,9 @@ describe('SQL Builder', () => { it('propagates date cast through nested $or', () => { const out = render( buildFilterClause( - { $or: [{ birthDate: { $lt: '2000-01-01' } }, { birthDate: { $gt: '2024-01-01' } }] }, + { + $or: [{ birthDate: { $lt: '2000-01-01' } }, { birthDate: { $gt: '2024-01-01' } }], + }, TABLE, dateCols ) @@ -399,6 +405,26 @@ describe('SQL Builder', () => { ) }) + it('uses direct timestamp column for created_at comparisons', () => { + const out = render( + buildFilterClause({ created_at: { $gt: '2026-07-20' } }, TABLE, NO_COLUMNS) + ) + + expect(out).toContain(`${TABLE}.created_at`) + expect(out).not.toContain(`data->>'created_at'`) + expect(out).toContain('::timestamptz') + }) + + it('uses direct column equality for created_at', () => { + const out = render( + buildFilterClause({ created_at: { $eq: '2026-07-20' } }, TABLE, NO_COLUMNS) + ) + + expect(out).toContain(`${TABLE}.created_at`) + expect(out).toContain('=') + expect(out).not.toContain(`${TABLE}.data @>`) + }) + it('combines multiple sort fields with commas', () => { const cols: ColumnDefinition[] = [ { name: 'name', type: 'string' }, diff --git a/apps/sim/lib/table/sql.ts b/apps/sim/lib/table/sql.ts index ab705c127a1..61c035c02ba 100644 --- a/apps/sim/lib/table/sql.ts +++ b/apps/sim/lib/table/sql.ts @@ -17,7 +17,7 @@ import type { JsonValue, Sort, } from '@/lib/table/types' -import { isBuiltInDateField } from '../../app/api/table/utils' +import { isBuiltInDateField } from '@/app/api/table/utils.ts' /** * Error thrown when caller-supplied filter or sort input is malformed.