Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Pg] Fix: all datetime mappings #1659

Merged
merged 12 commits into from
Jan 9, 2024
4 changes: 2 additions & 2 deletions drizzle-orm/src/pg-core/columns/timestamp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@ export class PgTimestamp<T extends ColumnBaseConfig<'date', 'PgTimestamp'>> exte
return `timestamp${precision}${this.withTimezone ? ' with time zone' : ''}`;
}

override mapFromDriverValue = (value: string): Date => {
override mapFromDriverValue = (value: string): Date | null => {
Angelelz marked this conversation as resolved.
Show resolved Hide resolved
return new Date(this.withTimezone ? value : value + '+0000');
};

override mapToDriverValue = (value: Date): string => {
return this.withTimezone ? value.toUTCString() : value.toISOString();
return value.toISOString();
};
}

Expand Down
8 changes: 8 additions & 0 deletions drizzle-orm/src/postgres-js/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ export function drizzle<TSchema extends Record<string, unknown> = Record<string,
client: Sql,
config: DrizzleConfig<TSchema> = {},
): PostgresJsDatabase<TSchema> {
const transparentParser = (val: any) => val;

// Override postgres.js default date parsers: https://github.com/porsager/postgres/discussions/761
for (const type of ['1184', '1082', '1083', '1114']) {
AndriiSherman marked this conversation as resolved.
Show resolved Hide resolved
client.options.parsers[type as any] = transparentParser;
client.options.serializers[type as any] = transparentParser;
}

const dialect = new PgDialect();
let logger;
if (config.logger === true) {
Expand Down
273 changes: 272 additions & 1 deletion integration-tests/tests/awsdatapi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,22 @@ import { asc, eq, name, placeholder, sql, TransactionRollbackError } from 'drizz
import type { AwsDataApiPgDatabase } from 'drizzle-orm/aws-data-api/pg';
import { drizzle } from 'drizzle-orm/aws-data-api/pg';
import { migrate } from 'drizzle-orm/aws-data-api/pg/migrator';
import { alias, boolean, integer, jsonb, pgTable, pgTableCreator, serial, text, timestamp } from 'drizzle-orm/pg-core';
import {
alias,
boolean,
date,
integer,
interval,
jsonb,
pgTable,
pgTableCreator,
serial,
text,
time,
timestamp,
} from 'drizzle-orm/pg-core';
import type { Equal } from './utils';
import { Expect } from './utils';

dotenv.config();

Expand Down Expand Up @@ -858,6 +873,262 @@ test.serial('select from raw sql with mapped values', async (t) => {
]);
});

test.serial('all date and time columns', async (t) => {
const { db } = t.context;

const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
dateString: date('date_string', { mode: 'string' }).notNull(),
time: time('time', { precision: 3 }).notNull(),
datetime: timestamp('datetime').notNull(),
datetimeWTZ: timestamp('datetime_wtz', { withTimezone: true }).notNull(),
datetimeString: timestamp('datetime_string', { mode: 'string' }).notNull(),
datetimeFullPrecision: timestamp('datetime_full_precision', { precision: 6, mode: 'string' }).notNull(),
datetimeWTZString: timestamp('datetime_wtz_string', { withTimezone: true, mode: 'string' }).notNull(),
interval: interval('interval').notNull(),
});

await db.execute(sql`drop table if exists ${table}`);

await db.execute(sql`
create table ${table} (
id serial primary key,
date_string date not null,
time time(3) not null,
datetime timestamp not null,
datetime_wtz timestamp with time zone not null,
datetime_string timestamp not null,
datetime_full_precision timestamp(6) not null,
datetime_wtz_string timestamp with time zone not null,
interval interval not null
)
`);

const someDatetime = new Date('2022-01-01T00:00:00.123Z');
const fullPrecision = '2022-01-01T00:00:00.123456';
const someTime = '23:23:12.432';

await db.insert(table).values({
dateString: '2022-01-01',
time: someTime,
datetime: someDatetime,
datetimeWTZ: someDatetime,
datetimeString: '2022-01-01T00:00:00.123Z',
datetimeFullPrecision: fullPrecision,
datetimeWTZString: '2022-01-01T00:00:00.123Z',
interval: '1 day',
});

const result = await db.select().from(table);

Expect<
Equal<{
id: number;
dateString: string;
time: string;
datetime: Date;
datetimeWTZ: Date;
datetimeString: string;
datetimeFullPrecision: string;
datetimeWTZString: string;
interval: string;
}[], typeof result>
>;

Expect<
Equal<{
dateString: string;
time: string;
datetime: Date;
datetimeWTZ: Date;
datetimeString: string;
datetimeFullPrecision: string;
datetimeWTZString: string;
interval: string;
id?: number | undefined;
}, typeof table.$inferInsert>
>;

t.deepEqual(result, [
{
id: 1,
dateString: '2022-01-01',
time: someTime,
datetime: someDatetime,
datetimeWTZ: someDatetime,
datetimeString: '2022-01-01 00:00:00.123',
datetimeFullPrecision: fullPrecision.replace('T', ' ').replace('Z', ''),
datetimeWTZString: '2022-01-01 00:00:00.123+00',
interval: '1 day',
},
]);

await db.execute(sql`drop table if exists ${table}`);
});

test.serial('all date and time columns with timezone', async (t) => {
const { db } = t.context;

const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(),
timestampAsDate: timestamp('timestamp_date', { withTimezone: true, precision: 3 }).notNull(),
timestampTimeZones: timestamp('timestamp_date_2', { withTimezone: true, precision: 3 }).notNull(),
});

await db.execute(sql`drop table if exists ${table}`);

await db.execute(sql`
create table ${table} (
id serial primary key,
timestamp_string timestamp(6) with time zone not null,
timestamp_date timestamp(3) with time zone not null,
timestamp_date_2 timestamp(3) with time zone not null
)
`);

const timestampString = '2022-01-01 00:00:00.123456-0200';
const timestampDate = new Date();
const timestampDateWTZ = new Date('2022-01-01 00:00:00.123 +0500');

const timestampString2 = '2022-01-01 00:00:00.123456-0400';
const timestampDate2 = new Date();
const timestampDateWTZ2 = new Date('2022-01-01 00:00:00.123 +0200');

await db.insert(table).values([
{ timestamp: timestampString, timestampAsDate: timestampDate, timestampTimeZones: timestampDateWTZ },
{ timestamp: timestampString2, timestampAsDate: timestampDate2, timestampTimeZones: timestampDateWTZ2 },
]);

const result = await db.select().from(table);
const result2 = await db.execute<{
id: number;
timestamp_string: string;
timestamp_date: string;
timestamp_date_2: string;
}>(sql`select * from ${table}`);

// Whatever you put in, you get back when you're using the date mode
// But when using the string mode, postgres returns a string transformed into UTC
t.deepEqual(result, [
{
id: 1,
timestamp: '2022-01-01 02:00:00.123456+00',
timestampAsDate: timestampDate,
timestampTimeZones: timestampDateWTZ,
},
{
id: 2,
timestamp: '2022-01-01 04:00:00.123456+00',
timestampAsDate: timestampDate2,
timestampTimeZones: timestampDateWTZ2,
},
]);

t.deepEqual(result2.records, [
{
id: 1,
timestamp_string: '2022-01-01 02:00:00.123456+00',
timestamp_date: timestampDate.toISOString().replace('T', ' ').replace('Z', '') + '+00',
timestamp_date_2: timestampDateWTZ.toISOString().replace('T', ' ').replace('Z', '') + '+00',
},
{
id: 2,
timestamp_string: '2022-01-01 04:00:00.123456+00',
timestamp_date: timestampDate2.toISOString().replace('T', ' ').replace('Z', '') + '+00',
timestamp_date_2: timestampDateWTZ2.toISOString().replace('T', ' ').replace('Z', '') + '+00',
},
]);

t.deepEqual(
result[0]?.timestampTimeZones.getTime(),
new Date((result2.records?.[0] as any).timestamp_date_2 as any).getTime(),
);

await db.execute(sql`drop table if exists ${table}`);
});

test.serial('all date and time columns without timezone', async (t) => {
const { db } = t.context;

const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestampString: timestamp('timestamp_string', { mode: 'string', precision: 6 }).notNull(),
timestampString2: timestamp('timestamp_string2', { precision: 3, mode: 'string' }).notNull(),
timestampDate: timestamp('timestamp_date', { precision: 3 }).notNull(),
});

await db.execute(sql`drop table if exists ${table}`);

await db.execute(sql`
create table ${table} (
id serial primary key,
timestamp_string timestamp(6) not null,
timestamp_string2 timestamp(3) not null,
timestamp_date timestamp(3) not null
)
`);

const timestampString = '2022-01-01 00:00:00.123456';
const timestampString2 = '2022-01-02 00:00:00.123 -0300';
const timestampDate = new Date('2022-01-01 00:00:00.123Z');

const timestampString_2 = '2022-01-01 00:00:00.123456';
const timestampString2_2 = '2022-01-01 00:00:00.123 -0300';
const timestampDate2 = new Date('2022-01-01 00:00:00.123 +0200');

await db.insert(table).values([
{ timestampString, timestampString2, timestampDate },
{ timestampString: timestampString_2, timestampString2: timestampString2_2, timestampDate: timestampDate2 },
]);

const result = await db.select().from(table);
const result2 = await db.execute<{
id: number;
timestamp_string: string;
timestamp_string2: string;
timestamp_date: string;
}>(sql`select * from ${table}`);

// Whatever you put in, you get back when you're using the date mode
// But when using the string mode, postgres returns a string transformed into UTC
t.deepEqual(result, [
{
id: 1,
timestampString: timestampString,
timestampString2: '2022-01-02 00:00:00.123',
timestampDate: timestampDate,
},
{
id: 2,
timestampString: timestampString_2,
timestampString2: '2022-01-01 00:00:00.123',
timestampDate: timestampDate2,
},
]);

t.deepEqual(result2.records, [
{
id: 1,
timestamp_string: timestampString,
timestamp_string2: '2022-01-02 00:00:00.123',
timestamp_date: timestampDate.toISOString().replace('T', ' ').replace('Z', ''),
},
{
id: 2,
timestamp_string: timestampString_2,
timestamp_string2: '2022-01-01 00:00:00.123',
timestamp_date: timestampDate2.toISOString().replace('T', ' ').replace('Z', ''),
},
]);

t.deepEqual((result2.records?.[0] as any).timestamp_string, '2022-01-01 00:00:00.123456');
// need to add the 'Z', otherwise javascript assumes it's in local time
t.deepEqual(new Date((result2.records?.[0] as any).timestamp_date + 'Z' as any).getTime(), timestampDate.getTime());

await db.execute(sql`drop table if exists ${table}`);
});

test.after.always(async (t) => {
const ctx = t.context;
await ctx.db.execute(sql`drop table "users"`);
Expand Down
Loading