Skip to content

Commit

Permalink
Merge pull request #1659 from Angelelz/fix-all-datetime-mappings
Browse files Browse the repository at this point in the history
[Pg] Fix: all datetime mappings
  • Loading branch information
AndriiSherman authored Jan 9, 2024
2 parents 4bdea4c + b29a5e1 commit 8c0788a
Show file tree
Hide file tree
Showing 7 changed files with 1,808 additions and 29 deletions.
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 => {
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']) {
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

0 comments on commit 8c0788a

Please sign in to comment.