Skip to content

Commit 8142bf4

Browse files
committed
fix(api): type timestamp cursor parameters
1 parent e7e432b commit 8142bf4

2 files changed

Lines changed: 18 additions & 6 deletions

File tree

apps/sim/lib/api/list-query.test.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,17 @@ describe('timestampKey', () => {
105105
)
106106
})
107107

108-
it('truncates the bound cursor value to match, binding it through the column encoder', () => {
108+
/**
109+
* The cast is not cosmetic. `date_trunc` is overloaded on timestamp,
110+
* timestamptz and interval, so an untyped parameter makes the call ambiguous
111+
* and Postgres rejects the whole statement with "function date_trunc(unknown,
112+
* unknown) is not unique" — which only ever fires on a second page, since
113+
* page one carries no cursor.
114+
*/
115+
it('truncates the bound cursor value to match, cast to the column type', () => {
109116
const { sql: text, params } = render(createdKey.bind('2024-01-01T00:00:00.123Z')!)
110117

111-
expect(text).toBe(`date_trunc('milliseconds', $1)`)
118+
expect(text).toBe(`date_trunc('milliseconds', $1::timestamp)`)
112119
expect(params).toEqual(['2024-01-01T00:00:00.123Z'])
113120
})
114121

@@ -173,8 +180,8 @@ describe('keysetAfter', () => {
173180
)
174181

175182
expect(text).toBe(
176-
`(date_trunc('milliseconds', "thing"."created_at") > date_trunc('milliseconds', $1) or ` +
177-
`(date_trunc('milliseconds', "thing"."created_at") = date_trunc('milliseconds', $2) and ` +
183+
`(date_trunc('milliseconds', "thing"."created_at") > date_trunc('milliseconds', $1::timestamp) or ` +
184+
`(date_trunc('milliseconds', "thing"."created_at") = date_trunc('milliseconds', $2::timestamp) and ` +
178185
`"thing"."id" > $3))`
179186
)
180187
expect(params).toEqual(['2024-01-01T00:00:00.123Z', '2024-01-01T00:00:00.123Z', 'file-7'])

apps/sim/lib/api/list-query.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,13 @@ export function timestampKey<Row>(column: Column, read: (row: Row) => Date): Key
116116
if (typeof value !== 'string') return null
117117
const date = new Date(value)
118118
if (Number.isNaN(date.getTime())) return null
119-
// Bound through the column so drizzle's own timestamp encoder serializes it.
120-
return sql`date_trunc('milliseconds', ${sql.param(date, column)})`
119+
/* Bound through the column so drizzle's own timestamp encoder serializes
120+
it, then cast: an untyped parameter leaves `date_trunc(unknown,
121+
unknown)` ambiguous across its timestamp, timestamptz and interval
122+
overloads, and Postgres rejects the statement rather than guess. Every
123+
column reaching here is `timestamp without time zone`, matching the
124+
type `expr` yields on the other side of the comparison. */
125+
return sql`date_trunc('milliseconds', ${sql.param(date, column)}::timestamp)`
121126
},
122127
}
123128
}

0 commit comments

Comments
 (0)