drizzle-team / drizzle-orm

Headless TypeScript ORM with a head. Runs on Node, Bun and Deno. Lives on the Edge and yes, it's a JavaScript ORM too 😅
https://orm.drizzle.team
Apache License 2.0
23.51k stars 576 forks source link

[BUG]: Type error with postgresjs strings/dates in version 0.30 #1993

Open jam-fran opened 6 months ago

jam-fran commented 6 months ago

What version of drizzle-orm are you using?

0.30.1

What version of drizzle-kit are you using?

0.20.14

Describe the Bug

I'm using Drizzle with postgresjs and recently updated to v0.30.1, where I know the driver was updated to always return strings for dates.

Many of my queries then failed with an error ERR_INVALID_ARG_TYPE: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received an instance of Date".

Here's an example of a failed query that produced that error message:

const startDate = subDays(new Date(), 30) // Date object

const results = await db
  .select({
    id: sessions.id,
    userId: sessions.userId,
  })
  .from(sessions)
  .where(gte(sessions.timestamp, startDate))

Where sessions.timestamp is defined as timestamp('timestamp', { mode: 'date', withTimezone: true }) in my schema.

When I convert startDate to a string with .toISOString(), the query works, but I get the following type error:

No overload matches this call.
  Overload 1 of 3, '(left: PgColumn<{ name: "timestamp"; tableName: "sessions"; dataType: "date"; columnType: "PgTimestamp"; data: Date; driverParam: string; notNull: true; hasDefault: true; enumValues: undefined; baseColumn: never; }, {}, {}>, right: Date | SQLWrapper): SQL<...>', gave the following error.
    Argument of type 'string' is not assignable to parameter of type 'Date | SQLWrapper'.
  Overload 2 of 3, '(left: Aliased<string>, right: string | SQLWrapper): SQL<unknown>', gave the following error.
    Argument of type 'PgColumn<{ name: "timestamp"; tableName: "sessions"; dataType: "date"; columnType: "PgTimestamp"; data: Date; driverParam: string; notNull: true; hasDefault: true; enumValues: undefined; baseColumn: never; }, {}, {}>' is not assignable to parameter of type 'Aliased<string>'.
      Type 'PgColumn<{ name: "timestamp"; tableName: "sessions"; dataType: "date"; columnType: "PgTimestamp"; data: Date; driverParam: string; notNull: true; hasDefault: true; enumValues: undefined; baseColumn: never; }, {}, {}>' is missing the following properties from type 'Aliased<string>': sql, fieldAlias
  Overload 3 of 3, '(left: never, right: unknown): SQL<unknown>', gave the following error.
    Argument of type 'PgColumn<{ name: "timestamp"; tableName: "sessions"; dataType: "date"; columnType: "PgTimestamp"; data: Date; driverParam: string; notNull: true; hasDefault: true; enumValues: undefined; baseColumn: never; }, {}, {}>' is not assignable to parameter of type 'never'.ts(2769)

Any tips would be appreciated. Thank you!

Expected behavior

No response

Environment & setup

No response

gp27 commented 6 months ago

I am having the same issue on version 0.30.4 when i pass Date objects to queries. The last version on which passing a Date worked correctly was 0.29.3

jam-fran commented 6 months ago

@gp27 Please keep me posted if you find a solution or workaround for this. I'm surprised that more people don't appear to be experiencing this since it's such a basic use case, so I feel like I must be overlooking something obvious.

jam-fran commented 5 months ago

Quick update - I've narrowed down the source of the issue to instances where a date is passed to the sql operator, either directly (sql`${dateHereWillBreakThings}) or in a sql.placeholder(date). Doing that worked in v 0.29.x, but not in 0.30.x.

It still works to pass a date to a filter operator (e.g. gte(sessions.timestamp, startDate)).

The workaround that worked for me for now is to convert any dates used with the sql operator to strings, like sql`${date.toISOString()}.

gp27 commented 5 months ago

I tried to investigate this issue further. I think it has been introduced with this PR #1659, which fixed an issue with the postgres driver where timestamps with mode "string" that should have been returned by queries as string values, were actually parsed into Date objects.

When the parsers for timezone related types were switched, so were the serializers: https://github.com/Angelelz/drizzle-orm/blob/b29a5e1a837cbbb45be5e2caf3564c0493c28d08/drizzle-orm/src/postgres-js/driver.ts#L28

The replacements of those serializers is probably what is causing this issue and #2009 as well.

I am not sure it was necessary to replace the serialziers as well, since the issue was on the parsing side, but I may be wrong. Maybe @Angelelz who authored the fix might have some further info about it.

Angelelz commented 5 months ago

The replacement of the serializer and parsers was necessary to guarantee that the same parsing/serializer code from Drizzle will work for both postgres.js and node-PG interchangeably. Unfortunately I have very little time lately to dedicate to drizzle but if somebody puts together a quick reproduction it will make it easier to investigate/fix.

erkstruwe commented 5 months ago

Until this is solved, a temporary workaround is to create a custom type:

import { customType } from "drizzle-orm/pg-core"

export const customDate = customType<{
    data: string
    driverData: Date
}>({
    dataType() {
        return "date"
    },
    fromDriver(value: Date): string {
        return value.toISOString().slice(0, 10)
    },
})