Skip to content

fix(tables): type-aware SQL casts for range filters on date columns#4657

Merged
waleedlatif1 merged 5 commits into
stagingfrom
waleedlatif1/review-pr-4649
May 19, 2026
Merged

fix(tables): type-aware SQL casts for range filters on date columns#4657
waleedlatif1 merged 5 commits into
stagingfrom
waleedlatif1/review-pr-4649

Conversation

@waleedlatif1
Copy link
Copy Markdown
Collaborator

Summary

  • buildComparisonClause now branches on column type via a shared jsonbCastForType helper: number::numeric, date::timestamp (with RHS cast so Postgres doesn't fall back to text comparison), unknown → ::numeric for back-compat
  • columns is now required on buildFilterClause and buildSortClause so the cast can't be silently dropped at a call site
  • queryRows / updateRowsByFilter / deleteRowsByFilter take table: TableDefinition as the first arg (symmetric); all call sites updated
  • Restored defense-in-depth workspace guard in copilot tool paths where getTableById doesn't auth (query_rows, update_rows_by_filter, delete_rows_by_filter, function-execute, generate-visualization)
  • New sql.test.ts suite asserts real generated SQL (numeric vs timestamp, range, nested $and/$or propagation, regression guards)

Type of Change

  • Bug fix

Testing

Tested manually. bun run test lib/table lib/copilot/tools/server/table (135 passing), bun run type-check, and bun run check:api-validation all clean.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel
Copy link
Copy Markdown

vercel Bot commented May 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped May 19, 2026 2:07am

Request Review

@cursor
Copy link
Copy Markdown

cursor Bot commented May 19, 2026

PR Summary

Medium Risk
Touches core table row querying/updating/deleting SQL generation and changes service function signatures, so regressions could affect filtering/sorting correctness and bulk operations. Risk is mitigated by added unit/integration tests and mostly mechanical call-site updates.

Overview
Type-aware filtering/sorting: buildFilterClause/buildSortClause now require column definitions and use a shared cast helper so range operators cast JSONB cells correctly (number::numeric, date::timestamptz with RHS cast) and validate comparison value types early.

Service/API refactor: queryRows, updateRowsByFilter, and deleteRowsByFilter now take table: TableDefinition (instead of tableId/workspaceId), removing redundant IDs from bulk data types and updating all call sites (export route, v1/v2 row routes, copilot tools).

Security/robustness + tests: Copilot table inputs add workspaceId checks before reading rows, and new/expanded tests assert generated SQL casts and ensure service functions always forward table.schema.columns into filter/sort builders.

Reviewed by Cursor Bugbot for commit 6a87e6e. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 19, 2026

Greptile Summary

This PR fixes incorrect SQL comparisons on date columns stored in JSONB by introducing a shared jsonbCastForType helper that selects ::numeric for number columns and ::timestamptz for date columns (with matching RHS casts so Postgres doesn't fall back to lexicographic text comparison). It also refactors queryRows, updateRowsByFilter, and deleteRowsByFilter to accept a full TableDefinition instead of separate tableId/workspaceId parameters, making column schema available for type-aware cast selection throughout the filter/sort path.

  • SQL cast fix: buildComparisonClause and buildSortFieldClause now branch on column type via jsonbCastForType; date comparisons emit ::timestamptz on both sides, number comparisons emit ::numeric; nested $and/$or propagate the ColumnTypeMap correctly.
  • Signature consolidation: BulkUpdateData/BulkDeleteData drop tableId/workspaceId (now sourced from the TableDefinition); buildFilterClause and buildSortClause require columns: ColumnDefinition[] as a non-optional arg so callers can't silently omit the type map.
  • Security hardening: Copilot tool paths (query_rows, update_rows_by_filter, delete_rows_by_filter, function-execute, generate-visualization) now include table.workspaceId !== workspaceId guards before operating on the fetched table.

Confidence Score: 5/5

Safe to merge — the cast-selection logic is correct, all call sites are updated, and workspace ownership checks are now consistent across every copilot tool path that handles user tables.

The changes are narrowly scoped to the SQL builder and its callers. The shared jsonbCastForType helper eliminates the drift that caused the original bug, both filter and sort paths are unified, and the new test suite verifies actual generated SQL strings rather than mere existence of a result. No existing auth boundary is weakened; the workspace guard additions close gaps in the copilot tool layer.

No files require special attention — the sql.ts cast logic is well-tested and the service refactoring is consistent across all call sites.

Important Files Changed

Filename Overview
apps/sim/lib/table/sql.ts Core SQL builder: adds jsonbCastForType as the single source of truth for cast selection; buildComparisonClause now casts both LHS and RHS for ::timestamptz; buildFilterClause/buildSortClause require columns; validateComparisonValue gives an actionable error at application layer.
apps/sim/lib/table/service.ts Refactored queryRows, updateRowsByFilter, deleteRowsByFilter to accept TableDefinition as first arg; table.schema.columns fed to both buildFilterClause and buildSortClause; workspace scoping now comes from table.workspaceId rather than a separate parameter.
apps/sim/lib/table/types.ts Widened $gt/$gte/$lt/$lte from number to `number
apps/sim/lib/copilot/tools/server/table/user-table.ts Added table.workspaceId !== workspaceId guards to query_rows, update_rows_by_filter, delete_rows_by_filter, get_table_info, and get_table_schema; updated call sites to the new service signatures.
apps/sim/lib/table/tests/sql.test.ts Replaced existence-only assertions with SQL-string assertions via a recursive renderSql helper; covers ::timestamptz vs ::numeric cast selection, combined range operators, nested $and/$or propagation, and value-type validation errors.
apps/sim/lib/table/tests/service-filter-threading.test.ts New integration test asserting that table.schema.columns is threaded through queryRows, updateRowsByFilter, and deleteRowsByFilter to buildFilterClause/buildSortClause; guards against the regression that originally prompted this PR.

Sequence Diagram

sequenceDiagram
    participant Caller as Route / Copilot Tool
    participant Service as service.ts
    participant SQL as sql.ts

    Caller->>Service: queryRows(table, options, requestId)
    note over Service: columns = table.schema.columns

    Service->>SQL: buildFilterClause(filter, tableName, columns)
    note over SQL: buildColumnTypeMap(columns)
    SQL->>SQL: "for each field -> jsonbCastForType(colType)"
    note over SQL: 'number' -> ::numeric, 'date' -> ::timestamptz, other -> null (text)
    SQL->>SQL: buildComparisonClause(field, op, value, colType)
    note over SQL: LHS cast + RHS cast for date columns
    SQL-->>Service: SQL WHERE clause

    Service->>SQL: buildSortClause(sort, tableName, columns)
    note over SQL: same jsonbCastForType path
    SQL-->>Service: SQL ORDER BY clause

    Service-->>Caller: QueryResult
Loading

Reviews (3): Last reviewed commit: "improvement(table): validate range opera..." | Re-trigger Greptile

Comment thread apps/sim/lib/table/sql.ts Outdated
- Drop redundant tableId/workspaceId from BulkUpdateData and BulkDeleteData; service uses table.id / table.workspaceId so column metadata and DB scope can't drift apart.
- Add missing workspace-id guards to copilot user-table cases (insert_row, batch_insert_rows, update_row, batch_update_rows, rename); collapse duplicated rename check.
- Add service-level integration tests that buildFilterClause/buildSortClause receive table.schema.columns from queryRows, updateRowsByFilter, deleteRowsByFilter.
::timestamp strips timezone offsets from ISO strings, making comparisons
depend on the server TimeZone setting. ::timestamptz preserves the offset
so chronological comparisons are correct regardless of server config.
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 8503120. Configure here.

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 6a87e6e. Configure here.

@waleedlatif1 waleedlatif1 merged commit a0d9e4d into staging May 19, 2026
14 checks passed
@waleedlatif1 waleedlatif1 deleted the waleedlatif1/review-pr-4649 branch May 19, 2026 02:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant