chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:10:05 +08:00
commit d37d8d293b
1388 changed files with 484182 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,69 @@
import { neon, type NeonQueryFunction } from '@neondatabase/serverless';
import { drizzle, type NeonHttpDatabase } from 'drizzle-orm/neon-http';
import { beforeAll, beforeEach, expect, test } from 'vitest';
import {
commentLikesConfig,
commentsConfig,
commentsTable,
groupsConfig,
groupsTable,
postsConfig,
postsTable,
usersConfig,
usersTable,
usersToGroupsConfig,
usersToGroupsTable,
} from './neon-http-batch';
import { TestCache, TestGlobalCache } from './pg-common-cache';
const ENABLE_LOGGING = false;
export const schema = {
usersTable,
postsTable,
commentsTable,
usersToGroupsTable,
groupsTable,
commentLikesConfig,
commentsConfig,
postsConfig,
usersToGroupsConfig,
groupsConfig,
usersConfig,
};
let db: NeonHttpDatabase<typeof schema>;
let client: NeonQueryFunction<false, true>;
let dbGlobalCached: NeonHttpDatabase;
let cachedDb: NeonHttpDatabase;
beforeAll(async () => {
const connectionString = process.env['NEON_HTTP_CONNECTION_STRING'];
if (!connectionString) {
throw new Error('NEON_HTTP_CONNECTION_STRING is not defined');
}
client = neon(connectionString);
db = drizzle(client, { schema, logger: ENABLE_LOGGING });
cachedDb = drizzle(client, {
logger: ENABLE_LOGGING,
cache: new TestCache(),
});
dbGlobalCached = drizzle(client, {
logger: ENABLE_LOGGING,
cache: new TestGlobalCache(),
});
});
beforeEach((ctx) => {
ctx.neonPg = {
db,
};
ctx.cachedPg = {
db: cachedDb,
dbGlobalCached,
};
});
test('skip', async () => {
expect(1).toBe(1);
});
@@ -0,0 +1,556 @@
import Docker from 'dockerode';
import type { InferSelectModel } from 'drizzle-orm';
import { eq, relations, sql } from 'drizzle-orm';
import type { NeonHttpQueryResult } from 'drizzle-orm/neon-http';
import { integer, pgTable, primaryKey, serial, text, timestamp } from 'drizzle-orm/pg-core';
import type { AnyPgColumn } from 'drizzle-orm/pg-core';
import getPort from 'get-port';
import { v4 as uuidV4 } from 'uuid';
import { afterAll, beforeEach, describe, expect, expectTypeOf, test } from 'vitest';
export const usersTable = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
verified: integer('verified').notNull().default(0),
invitedBy: integer('invited_by').references((): AnyPgColumn => usersTable.id),
});
export const usersConfig = relations(usersTable, ({ one, many }) => ({
invitee: one(usersTable, {
fields: [usersTable.invitedBy],
references: [usersTable.id],
}),
usersToGroups: many(usersToGroupsTable),
posts: many(postsTable),
}));
export const groupsTable = pgTable('groups', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
description: text('description'),
});
export const groupsConfig = relations(groupsTable, ({ many }) => ({
usersToGroups: many(usersToGroupsTable),
}));
export const usersToGroupsTable = pgTable(
'users_to_groups',
{
id: serial('id'),
userId: integer('user_id').notNull().references(() => usersTable.id),
groupId: integer('group_id').notNull().references(() => groupsTable.id),
},
(t) => ({
pk: primaryKey({ columns: [t.userId, t.groupId] }),
}),
);
export const usersToGroupsConfig = relations(usersToGroupsTable, ({ one }) => ({
group: one(groupsTable, {
fields: [usersToGroupsTable.groupId],
references: [groupsTable.id],
}),
user: one(usersTable, {
fields: [usersToGroupsTable.userId],
references: [usersTable.id],
}),
}));
export const postsTable = pgTable('posts', {
id: serial('id').primaryKey(),
content: text('content').notNull(),
ownerId: integer('owner_id').references(() => usersTable.id),
createdAt: timestamp('created_at').notNull().defaultNow(),
});
export const postsConfig = relations(postsTable, ({ one, many }) => ({
author: one(usersTable, {
fields: [postsTable.ownerId],
references: [usersTable.id],
}),
comments: many(commentsTable),
}));
export const commentsTable = pgTable('comments', {
id: serial('id').primaryKey(),
content: text('content').notNull(),
creator: integer('creator').references(() => usersTable.id),
postId: integer('post_id').references(() => postsTable.id),
createdAt: timestamp('created_at').notNull().defaultNow(),
});
export const commentsConfig = relations(commentsTable, ({ one, many }) => ({
post: one(postsTable, {
fields: [commentsTable.postId],
references: [postsTable.id],
}),
author: one(usersTable, {
fields: [commentsTable.creator],
references: [usersTable.id],
}),
likes: many(commentLikesTable),
}));
export const commentLikesTable = pgTable('comment_likes', {
id: serial('id').primaryKey(),
creator: integer('creator').references(() => usersTable.id),
commentId: integer('comment_id').references(() => commentsTable.id),
createdAt: timestamp('created_at').notNull().defaultNow(),
});
export const commentLikesConfig = relations(commentLikesTable, ({ one }) => ({
comment: one(commentsTable, {
fields: [commentLikesTable.commentId],
references: [commentsTable.id],
}),
author: one(usersTable, {
fields: [commentLikesTable.creator],
references: [usersTable.id],
}),
}));
let pgContainer: Docker.Container;
export async function createDockerDB(): Promise<string> {
const docker = new Docker();
const port = await getPort({ port: 5432 });
const image = 'postgres:14';
const pullStream = await docker.pull(image);
await new Promise((resolve, reject) =>
docker.modem.followProgress(pullStream, (err) => (err ? reject(err) : resolve(err)))
);
pgContainer = await docker.createContainer({
Image: image,
Env: ['POSTGRES_PASSWORD=postgres', 'POSTGRES_USER=postgres', 'POSTGRES_DB=postgres'],
name: `drizzle-integration-tests-${uuidV4()}`,
HostConfig: {
AutoRemove: true,
PortBindings: {
'5432/tcp': [{ HostPort: `${port}` }],
},
},
});
await pgContainer.start();
return `postgres://postgres:postgres@localhost:${port}/postgres`;
}
afterAll(async () => {
await pgContainer?.stop().catch(console.error);
});
export function tests() {
describe('common', () => {
beforeEach(async (ctx) => {
const { db } = ctx.pg;
await db.execute(sql`drop schema if exists public cascade`);
await db.execute(sql`drop schema if exists mySchema cascade`);
await db.execute(
sql`
create table users (
id serial primary key,
name text not null,
verified int not null default 0,
invited_by int references users(id)
)
`,
);
await db.execute(
sql`
create table groups (
id serial primary key,
name text not null,
description text
)
`,
);
await db.execute(
sql`
create table users_to_groups (
id serial,
user_id int not null references users(id),
group_id int not null references groups(id),
primary key (user_id, group_id)
)
`,
);
await db.execute(
sql`
create table posts (
id serial primary key,
content text not null,
owner_id int references users(id),
created_at timestamp not null default now()
)
`,
);
await db.execute(
sql`
create table comments (
id serial primary key,
content text not null,
creator int references users(id),
post_id int references posts(id),
created_at timestamp not null default now()
)
`,
);
await db.execute(
sql`
create table comment_likes (
id serial primary key,
creator int references users(id),
comment_id int references comments(id),
created_at timestamp not null default now()
)
`,
);
});
test('batch api example', async (ctx) => {
const { db } = ctx.neonPg;
const batchResponse = await db.batch([
db.insert(usersTable).values({ id: 1, name: 'John' }).returning({
id: usersTable.id,
invitedBy: usersTable.invitedBy,
}),
db.insert(usersTable).values({ id: 2, name: 'Dan' }),
db.select().from(usersTable),
]);
expectTypeOf(batchResponse).toEqualTypeOf<[
{
id: number;
invitedBy: number | null;
}[],
NeonHttpQueryResult<never>,
{
id: number;
name: string;
verified: number;
invitedBy: number | null;
}[],
]>();
expect(batchResponse.length).eq(3);
expect(batchResponse[0]).toEqual([{
id: 1,
invitedBy: null,
}]);
expect(batchResponse[1]).toMatchObject({ rows: [], rowCount: 1 });
expect(batchResponse[2]).toEqual([
{ id: 1, name: 'John', verified: 0, invitedBy: null },
{ id: 2, name: 'Dan', verified: 0, invitedBy: null },
]);
});
// batch api only relational many
test('insert + findMany', async (ctx) => {
const { db } = ctx.neonPg;
const batchResponse = await db.batch([
db.insert(usersTable).values({ id: 1, name: 'John' }).returning({ id: usersTable.id }),
db.insert(usersTable).values({ id: 2, name: 'Dan' }),
db.query.usersTable.findMany({}),
]);
expectTypeOf(batchResponse).toEqualTypeOf<[
{
id: number;
}[],
NeonHttpQueryResult<never>,
{
id: number;
name: string;
verified: number;
invitedBy: number | null;
}[],
]>();
expect(batchResponse.length).eq(3);
expect(batchResponse[0]).toEqual([{
id: 1,
}]);
expect(batchResponse[1]).toMatchObject({ rows: [], rowCount: 1 });
expect(batchResponse[2]).toEqual([
{ id: 1, name: 'John', verified: 0, invitedBy: null },
{ id: 2, name: 'Dan', verified: 0, invitedBy: null },
]);
});
// batch api relational many + one
test('insert + findMany + findFirst', async (ctx) => {
const { db } = ctx.neonPg;
const batchResponse = await db.batch([
db.insert(usersTable).values({ id: 1, name: 'John' }).returning({ id: usersTable.id }),
db.insert(usersTable).values({ id: 2, name: 'Dan' }),
db.query.usersTable.findMany({}),
db.query.usersTable.findFirst({}),
]);
expectTypeOf(batchResponse).toEqualTypeOf<[
{
id: number;
}[],
NeonHttpQueryResult<never>,
{
id: number;
name: string;
verified: number;
invitedBy: number | null;
}[],
{
id: number;
name: string;
verified: number;
invitedBy: number | null;
} | undefined,
]>();
expect(batchResponse.length).eq(4);
expect(batchResponse[0]).toEqual([{
id: 1,
}]);
expect(batchResponse[1]).toMatchObject({ rows: [], rowCount: 1 });
expect(batchResponse[2]).toEqual([
{ id: 1, name: 'John', verified: 0, invitedBy: null },
{ id: 2, name: 'Dan', verified: 0, invitedBy: null },
]);
expect(batchResponse[3]).toEqual(
{ id: 1, name: 'John', verified: 0, invitedBy: null },
);
});
test('insert + db.execute', async (ctx) => {
const { db } = ctx.neonPg;
const batchResponse = await db.batch([
db.insert(usersTable).values({ id: 1, name: 'John' }).returning({ id: usersTable.id }),
db.execute(sql`insert into users (id, name) values (2, 'Dan')`),
]);
expectTypeOf(batchResponse).toEqualTypeOf<[
{
id: number;
}[],
NeonHttpQueryResult<Record<string, unknown>>,
]>();
expect(batchResponse.length).eq(2);
expect(batchResponse[0]).toEqual([{
id: 1,
}]);
expect(batchResponse[1]).toMatchObject({ rowAsArray: false, rows: [], rowCount: 1 });
});
// batch api combined rqb + raw call
test('insert + findManyWith + db.all', async (ctx) => {
const { db } = ctx.neonPg;
const batchResponse = await db.batch([
db.insert(usersTable).values({ id: 1, name: 'John' }).returning({ id: usersTable.id }),
db.insert(usersTable).values({ id: 2, name: 'Dan' }),
db.query.usersTable.findMany({}),
db.execute<typeof usersTable.$inferSelect>(sql`select * from users`),
]);
expectTypeOf(batchResponse).toEqualTypeOf<[
{
id: number;
}[],
NeonHttpQueryResult<never>,
{
id: number;
name: string;
verified: number;
invitedBy: number | null;
}[],
NeonHttpQueryResult<{
id: number;
name: string;
verified: number;
invitedBy: number | null;
}>,
]>();
expect(batchResponse.length).eq(4);
expect(batchResponse[0]).toEqual([{
id: 1,
}]);
expect(batchResponse[1]).toMatchObject({ rowAsArray: true, rows: [], rowCount: 1 });
expect(batchResponse[2]).toEqual([
{ id: 1, name: 'John', verified: 0, invitedBy: null },
{ id: 2, name: 'Dan', verified: 0, invitedBy: null },
]);
expect(batchResponse[3]).toMatchObject({
rows: [
{ id: 1, name: 'John', verified: 0, invited_by: null },
{ id: 2, name: 'Dan', verified: 0, invited_by: null },
],
});
});
// batch api for insert + update + select
test('insert + update + select + select partial', async (ctx) => {
const { db } = ctx.neonPg;
const batchResponse = await db.batch([
db.insert(usersTable).values({ id: 1, name: 'John' }).returning({ id: usersTable.id }),
db.update(usersTable).set({ name: 'Dan' }).where(eq(usersTable.id, 1)),
db.query.usersTable.findMany({}),
db.select().from(usersTable).where(eq(usersTable.id, 1)),
db.select({ id: usersTable.id, invitedBy: usersTable.invitedBy }).from(usersTable),
]);
expectTypeOf(batchResponse).toEqualTypeOf<[
{
id: number;
}[],
NeonHttpQueryResult<never>,
{
id: number;
name: string;
verified: number;
invitedBy: number | null;
}[],
{
id: number;
name: string;
verified: number;
invitedBy: number | null;
}[],
{
id: number;
invitedBy: number | null;
}[],
]>();
expect(batchResponse.length).eq(5);
expect(batchResponse[0]).toEqual([{
id: 1,
}]);
expect(batchResponse[1]).toMatchObject({ rows: [], rowCount: 1 });
expect(batchResponse[2]).toEqual([
{ id: 1, name: 'Dan', verified: 0, invitedBy: null },
]);
expect(batchResponse[3]).toEqual([
{ id: 1, name: 'Dan', verified: 0, invitedBy: null },
]);
expect(batchResponse[4]).toEqual([
{ id: 1, invitedBy: null },
]);
});
// batch api for insert + delete + select
test('insert + delete + select + select partial', async (ctx) => {
const { db } = ctx.neonPg;
const batchResponse = await db.batch([
db.insert(usersTable).values({ id: 1, name: 'John' }).returning({ id: usersTable.id }),
db.insert(usersTable).values({ id: 2, name: 'Dan' }),
db.delete(usersTable).where(eq(usersTable.id, 1)).returning({
id: usersTable.id,
invitedBy: usersTable.invitedBy,
}),
db.query.usersTable.findFirst({
columns: {
id: true,
invitedBy: true,
},
}),
]);
expectTypeOf(batchResponse).toEqualTypeOf<[
{
id: number;
}[],
NeonHttpQueryResult<never>,
{
id: number;
invitedBy: number | null;
}[],
{
id: number;
invitedBy: number | null;
} | undefined,
]>();
expect(batchResponse.length).eq(4);
expect(batchResponse[0]).toEqual([{
id: 1,
}]);
expect(batchResponse[1]).toMatchObject({ rows: [], rowCount: 1 });
expect(batchResponse[2]).toEqual([
{ id: 1, invitedBy: null },
]);
expect(batchResponse[3]).toEqual(
{ id: 2, invitedBy: null },
);
});
test('select raw', async (ctx) => {
const { db } = ctx.neonPg;
await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Dan' }]);
const batchResponse = await db.batch([
db.execute<InferSelectModel<typeof usersTable, { dbColumnNames: true }>>(sql`select * from users`),
db.execute<InferSelectModel<typeof usersTable, { dbColumnNames: true }>>(sql`select * from users where id = 1`),
]);
expectTypeOf(batchResponse).toEqualTypeOf<[
NeonHttpQueryResult<{
id: number;
name: string;
verified: number;
invited_by: number | null;
}>,
NeonHttpQueryResult<{
id: number;
name: string;
verified: number;
invited_by: number | null;
}>,
]>();
expect(batchResponse.length).eq(2);
expect(batchResponse[0]).toMatchObject({
rows: [
{ id: 1, name: 'John', verified: 0, invited_by: null },
{ id: 2, name: 'Dan', verified: 0, invited_by: null },
],
});
expect(batchResponse[1]).toMatchObject({
rows: [
{ id: 1, name: 'John', verified: 0, invited_by: null },
],
});
});
});
}
@@ -0,0 +1,789 @@
import { neon, neonConfig, type NeonQueryFunction } from '@neondatabase/serverless';
import { eq, sql } from 'drizzle-orm';
import { drizzle, type NeonHttpDatabase } from 'drizzle-orm/neon-http';
import { migrate } from 'drizzle-orm/neon-http/migrator';
import { pgMaterializedView, pgTable, serial, timestamp } from 'drizzle-orm/pg-core';
import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
import { skipTests } from '~/common';
import { randomString } from '~/utils';
import { tests, usersMigratorTable, usersTable } from './pg-common';
import { TestCache, TestGlobalCache, tests as cacheTests } from './pg-common-cache';
const ENABLE_LOGGING = false;
let db: NeonHttpDatabase;
let dbGlobalCached: NeonHttpDatabase;
let cachedDb: NeonHttpDatabase;
beforeAll(async () => {
const connectionString = process.env['NEON_HTTP_CONNECTION_STRING'];
if (!connectionString) {
throw new Error('NEON_CONNECTION_STRING is not defined');
}
neonConfig.fetchEndpoint = (host) => {
const [protocol, port] = host === 'db.localtest.me' ? ['http', 4444] : ['https', 443];
return `${protocol}://${host}:${port}/sql`;
};
const client = neon(connectionString);
db = drizzle(client, { logger: ENABLE_LOGGING });
cachedDb = drizzle(client, {
logger: ENABLE_LOGGING,
cache: new TestCache(),
});
dbGlobalCached = drizzle(client, {
logger: ENABLE_LOGGING,
cache: new TestGlobalCache(),
});
});
beforeEach((ctx) => {
ctx.pg = {
db,
};
ctx.cachedPg = {
db: cachedDb,
dbGlobalCached,
};
});
test('migrator : default migration strategy', async () => {
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg' });
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table "drizzle"."__drizzle_migrations"`);
});
test('migrator : migrate with custom schema', async () => {
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: 'custom_migrations' });
// test if the custom migrations table was created
const { rowCount } = await db.execute(sql`select * from custom_migrations."__drizzle_migrations";`);
expect(rowCount && rowCount > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table custom_migrations."__drizzle_migrations"`);
});
test('migrator : migrate with custom table', async () => {
const customTable = randomString();
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable });
// test if the custom migrations table was created
const { rowCount } = await db.execute(sql`select * from "drizzle".${sql.identifier(customTable)};`);
expect(rowCount && rowCount > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table "drizzle".${sql.identifier(customTable)}`);
});
test('migrator : migrate with custom table and custom schema', async () => {
const customTable = randomString();
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, {
migrationsFolder: './drizzle2/pg',
migrationsTable: customTable,
migrationsSchema: 'custom_migrations',
});
// test if the custom migrations table was created
const { rowCount } = await db.execute(
sql`select * from custom_migrations.${sql.identifier(customTable)};`,
);
expect(rowCount && rowCount > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table custom_migrations.${sql.identifier(customTable)}`);
});
test('all date and time columns without timezone first case mode string', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).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
)
`);
// 1. Insert date in string format without timezone in it
await db.insert(table).values([
{ timestamp: '2022-01-01 02:00:00.123456' },
]);
// 2, Select in string format and check that values are the same
const result = await db.select().from(table);
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456' }]);
// 3. Select as raw query and check that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('all date and time columns without timezone second case mode string', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).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
)
`);
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: '2022-01-01T02:00:00.123456-02' },
]);
// 2, Select as raw query and check that values are the same
const result = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('all date and time columns without timezone third case mode date', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: '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(3) not null
)
`);
const insertedDate = new Date('2022-01-01 20:00:00.123+04');
// 1. Insert date as new date
await db.insert(table).values([
{ timestamp: insertedDate },
]);
// 2, Select as raw query as string
const result = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3. Compare both dates using orm mapping - Need to add 'Z' to tell JS that it is UTC
expect(new Date(result.rows[0]!.timestamp_string + 'Z').getTime()).toBe(insertedDate.getTime());
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-0200';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode date for timestamp with timezone', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'date', 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(3) with time zone not null
)
`);
const timestampString = new Date('2022-01-01 00:00:00.456-0200');
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: timestampString }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.456+00' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone in UTC timezone', async () => {
// get current timezone from db
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
// set timezone to UTC
await db.execute(sql`set time zone 'UTC'`);
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-0200';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
await db.execute(sql`drop table if exists ${table}`);
});
test.skip('test mode string for timestamp with timezone in different timezone', async () => {
// get current timezone from db
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
// set timezone to HST (UTC - 10)
await db.execute(sql`set time zone 'HST'`);
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-1000';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 00:00:00.123456-10' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 00:00:00.123456+00' }]);
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
await db.execute(sql`drop table if exists ${table}`);
});
skipTests([
'migrator : default migration strategy',
'migrator : migrate with custom schema',
'migrator : migrate with custom table',
'migrator : migrate with custom table and custom schema',
'insert via db.execute + select via db.execute',
'insert via db.execute + returning',
'insert via db.execute w/ query builder',
'all date and time columns without timezone first case mode string',
'all date and time columns without timezone third case mode date',
'test mode string for timestamp with timezone',
'test mode date for timestamp with timezone',
'test mode string for timestamp with timezone in UTC timezone',
'nested transaction rollback',
'transaction rollback',
'nested transaction',
'transaction',
'timestamp timezone',
'test $onUpdateFn and $onUpdate works as $default',
]);
tests();
cacheTests();
beforeEach(async () => {
await db.execute(sql`drop schema if exists public cascade`);
await db.execute(sql`create schema public`);
await db.execute(
sql`
create table users (
id serial primary key,
name text not null,
verified boolean not null default false,
jsonb jsonb,
created_at timestamptz not null default now()
)
`,
);
});
test('insert via db.execute + select via db.execute', async () => {
await db.execute(
sql`insert into ${usersTable} (${sql.identifier(usersTable.name.name)}) values (${'John'})`,
);
const result = await db.execute<{ id: number; name: string }>(
sql`select id, name from "users"`,
);
expect(result.rows).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute + returning', async () => {
const inserted = await db.execute<{ id: number; name: string }>(
sql`insert into ${usersTable} (${
sql.identifier(
usersTable.name.name,
)
}) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`,
);
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute w/ query builder', async () => {
const inserted = await db.execute<Pick<typeof usersTable.$inferSelect, 'id' | 'name'>>(
db
.insert(usersTable)
.values({ name: 'John' })
.returning({ id: usersTable.id, name: usersTable.name }),
);
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
});
describe('$withAuth tests', (it) => {
const client = vi.fn();
const db = drizzle({
client: client as any as NeonQueryFunction<any, any>,
schema: {
usersTable,
},
});
it('$count', async () => {
await db.$withAuth('$count').$count(usersTable).catch(() => null);
expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: false, fullResults: true, authToken: '$count' });
});
it('delete', async () => {
await db.$withAuth('delete').delete(usersTable).catch(() => null);
expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: false, fullResults: true, authToken: 'delete' });
});
it('select', async () => {
await db.$withAuth('select').select().from(usersTable).catch(() => null);
expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: true, fullResults: true, authToken: 'select' });
});
it('selectDistinct', async () => {
await db.$withAuth('selectDistinct').selectDistinct().from(usersTable).catch(() => null);
expect(client.mock.lastCall?.[2]).toStrictEqual({
arrayMode: true,
fullResults: true,
authToken: 'selectDistinct',
});
});
it('selectDistinctOn', async () => {
await db.$withAuth('selectDistinctOn').selectDistinctOn([usersTable.name]).from(usersTable).catch(() => null);
expect(client.mock.lastCall?.[2]).toStrictEqual({
arrayMode: true,
fullResults: true,
authToken: 'selectDistinctOn',
});
});
it('update', async () => {
await db.$withAuth('update').update(usersTable).set({
name: 'CHANGED',
}).where(eq(usersTable.name, 'TARGET')).catch(() => null);
expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: false, fullResults: true, authToken: 'update' });
});
it('insert', async () => {
await db.$withAuth('insert').insert(usersTable).values({
name: 'WITHAUTHUSER',
}).catch(() => null);
expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: false, fullResults: true, authToken: 'insert' });
});
it('with', async () => {
await db.$withAuth('with').with(db.$with('WITH').as((qb) => qb.select().from(usersTable))).select().from(usersTable)
.catch(() => null);
expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: true, fullResults: true, authToken: 'with' });
});
it('rqb', async () => {
await db.$withAuth('rqb').query.usersTable.findFirst().catch(() => null);
expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: true, fullResults: true, authToken: 'rqb' });
});
it('exec', async () => {
await db.$withAuth('exec').execute(`SELECT 1`).catch(() => null);
expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: false, fullResults: true, authToken: 'exec' });
});
it('prepared', async () => {
const prep = db.$withAuth('prepared').select().from(usersTable).prepare('withAuthPrepared');
await prep.execute().catch(() => null);
expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: true, fullResults: true, authToken: 'prepared' });
});
it('refreshMaterializedView', async () => {
const johns = pgMaterializedView('johns')
.as((qb) => qb.select().from(usersTable).where(eq(usersTable.name, 'John')));
await db.$withAuth('refreshMaterializedView').refreshMaterializedView(johns);
expect(client.mock.lastCall?.[2]).toStrictEqual({
arrayMode: false,
fullResults: true,
authToken: 'refreshMaterializedView',
});
});
});
describe('$withAuth callback tests', (it) => {
const client = vi.fn();
const db = drizzle({
client: client as any as NeonQueryFunction<any, any>,
schema: {
usersTable,
},
});
const auth = (token: string) => () => token;
it('$count', async () => {
await db.$withAuth(auth('$count')).$count(usersTable).catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('$count');
});
it('delete', async () => {
await db.$withAuth(auth('delete')).delete(usersTable).catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('delete');
});
it('select', async () => {
await db.$withAuth(auth('select')).select().from(usersTable).catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('select');
});
it('selectDistinct', async () => {
await db.$withAuth(auth('selectDistinct')).selectDistinct().from(usersTable).catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('selectDistinct');
});
it('selectDistinctOn', async () => {
await db.$withAuth(auth('selectDistinctOn')).selectDistinctOn([usersTable.name]).from(usersTable).catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('selectDistinctOn');
});
it('update', async () => {
await db.$withAuth(auth('update')).update(usersTable).set({
name: 'CHANGED',
}).where(eq(usersTable.name, 'TARGET')).catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('update');
});
it('insert', async () => {
await db.$withAuth(auth('insert')).insert(usersTable).values({
name: 'WITHAUTHUSER',
}).catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('insert');
});
it('with', async () => {
await db.$withAuth(auth('with')).with(db.$with('WITH').as((qb) => qb.select().from(usersTable))).select().from(
usersTable,
)
.catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('with');
});
it('rqb', async () => {
await db.$withAuth(auth('rqb')).query.usersTable.findFirst().catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('rqb');
});
it('exec', async () => {
await db.$withAuth(auth('exec')).execute(`SELECT 1`).catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('exec');
});
it('prepared', async () => {
const prep = db.$withAuth(auth('prepared')).select().from(usersTable).prepare('withAuthPrepared');
await prep.execute().catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('prepared');
});
it('refreshMaterializedView', async () => {
const johns = pgMaterializedView('johns')
.as((qb) => qb.select().from(usersTable).where(eq(usersTable.name, 'John')));
await db.$withAuth(auth('refreshMaterializedView')).refreshMaterializedView(johns);
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('refreshMaterializedView');
});
});
describe('$withAuth async callback tests', (it) => {
const client = vi.fn();
const db = drizzle({
client: client as any as NeonQueryFunction<any, any>,
schema: {
usersTable,
},
});
const auth = (token: string) => async () => token;
it('$count', async () => {
await db.$withAuth(auth('$count')).$count(usersTable).catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('$count');
});
it('delete', async () => {
await db.$withAuth(auth('delete')).delete(usersTable).catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('delete');
});
it('select', async () => {
await db.$withAuth(auth('select')).select().from(usersTable).catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('select');
});
it('selectDistinct', async () => {
await db.$withAuth(auth('selectDistinct')).selectDistinct().from(usersTable).catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('selectDistinct');
});
it('selectDistinctOn', async () => {
await db.$withAuth(auth('selectDistinctOn')).selectDistinctOn([usersTable.name]).from(usersTable).catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('selectDistinctOn');
});
it('update', async () => {
await db.$withAuth(auth('update')).update(usersTable).set({
name: 'CHANGED',
}).where(eq(usersTable.name, 'TARGET')).catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('update');
});
it('insert', async () => {
await db.$withAuth(auth('insert')).insert(usersTable).values({
name: 'WITHAUTHUSER',
}).catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('insert');
});
it('with', async () => {
await db.$withAuth(auth('with')).with(db.$with('WITH').as((qb) => qb.select().from(usersTable))).select().from(
usersTable,
)
.catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('with');
});
it('rqb', async () => {
await db.$withAuth(auth('rqb')).query.usersTable.findFirst().catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('rqb');
});
it('exec', async () => {
await db.$withAuth(auth('exec')).execute(`SELECT 1`).catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('exec');
});
it('prepared', async () => {
const prep = db.$withAuth(auth('prepared')).select().from(usersTable).prepare('withAuthPrepared');
await prep.execute().catch(() => null);
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('prepared');
});
it('refreshMaterializedView', async () => {
const johns = pgMaterializedView('johns')
.as((qb) => qb.select().from(usersTable).where(eq(usersTable.name, 'John')));
await db.$withAuth(auth('refreshMaterializedView')).refreshMaterializedView(johns);
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('refreshMaterializedView');
});
});
@@ -0,0 +1,585 @@
import { neonConfig, Pool } from '@neondatabase/serverless';
import { eq, sql } from 'drizzle-orm';
import { drizzle, type NeonDatabase } from 'drizzle-orm/neon-serverless';
import { migrate } from 'drizzle-orm/neon-serverless/migrator';
import { pgTable, serial, timestamp } from 'drizzle-orm/pg-core';
import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest';
import ws from 'ws';
import { skipTests } from '~/common';
import { randomString } from '~/utils';
import { mySchema, tests, usersMigratorTable, usersMySchemaTable, usersTable } from './pg-common';
import { TestCache, TestGlobalCache, tests as cacheTests } from './pg-common-cache';
const ENABLE_LOGGING = false;
let db: NeonDatabase;
let dbGlobalCached: NeonDatabase;
let cachedDb: NeonDatabase;
let client: Pool;
neonConfig.wsProxy = (host) => `${host}:5446/v1`;
neonConfig.useSecureWebSocket = false;
neonConfig.pipelineTLS = false;
neonConfig.pipelineConnect = false;
neonConfig.webSocketConstructor = ws;
beforeAll(async () => {
const connectionString = process.env['NEON_SERVERLESS_CONNECTION_STRING'];
if (!connectionString) {
throw new Error('NEON_SERVERLESS_CONNECTION_STRING is not defined');
}
client = new Pool({ connectionString });
db = drizzle(client, { logger: ENABLE_LOGGING });
cachedDb = drizzle(client, {
logger: ENABLE_LOGGING,
cache: new TestCache(),
});
dbGlobalCached = drizzle(client, {
logger: ENABLE_LOGGING,
cache: new TestGlobalCache(),
});
});
afterAll(async () => {
await client?.end();
});
beforeEach((ctx) => {
ctx.pg = {
db,
};
ctx.cachedPg = {
db: cachedDb,
dbGlobalCached,
};
});
test('migrator : default migration strategy', async () => {
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg' });
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table "drizzle"."__drizzle_migrations"`);
});
test('migrator : migrate with custom schema', async () => {
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: 'custom_migrations' });
// test if the custom migrations table was created
const { rowCount } = await db.execute(sql`select * from custom_migrations."__drizzle_migrations";`);
expect(rowCount && rowCount > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table custom_migrations."__drizzle_migrations"`);
});
test('migrator : migrate with custom table', async () => {
const customTable = randomString();
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable });
// test if the custom migrations table was created
const { rowCount } = await db.execute(sql`select * from "drizzle".${sql.identifier(customTable)};`);
expect(rowCount && rowCount > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table "drizzle".${sql.identifier(customTable)}`);
});
test('migrator : migrate with custom table and custom schema', async () => {
const customTable = randomString();
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, {
migrationsFolder: './drizzle2/pg',
migrationsTable: customTable,
migrationsSchema: 'custom_migrations',
});
// test if the custom migrations table was created
const { rowCount } = await db.execute(
sql`select * from custom_migrations.${sql.identifier(customTable)};`,
);
expect(rowCount && rowCount > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table custom_migrations.${sql.identifier(customTable)}`);
});
test('all date and time columns without timezone first case mode string', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).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
)
`);
// 1. Insert date in string format without timezone in it
await db.insert(table).values([
{ timestamp: '2022-01-01 02:00:00.123456' },
]);
// 2, Select in string format and check that values are the same
const result = await db.select().from(table);
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456' }]);
// 3. Select as raw query and check that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('all date and time columns without timezone second case mode string', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).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
)
`);
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: '2022-01-01T02:00:00.123456-02' },
]);
// 2, Select as raw query and check that values are the same
const result = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('all date and time columns without timezone third case mode date', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: '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(3) not null
)
`);
const insertedDate = new Date('2022-01-01 20:00:00.123+04');
// 1. Insert date as new date
await db.insert(table).values([
{ timestamp: insertedDate },
]);
// 2, Select as raw query as string
const result = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3. Compare both dates using orm mapping - Need to add 'Z' to tell JS that it is UTC
expect(new Date(result.rows[0]!.timestamp_string + 'Z').getTime()).toBe(insertedDate.getTime());
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-0200';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode date for timestamp with timezone', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'date', 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(3) with time zone not null
)
`);
const timestampString = new Date('2022-01-01 00:00:00.456-0200');
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: timestampString }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.456+00' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone in UTC timezone', async () => {
// get current timezone from db
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
// set timezone to UTC
await db.execute(sql`set time zone 'UTC'`);
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-0200';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
await db.execute(sql`drop table if exists ${table}`);
});
test.skip('test mode string for timestamp with timezone in different timezone', async () => {
// get current timezone from db
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
// set timezone to HST (UTC - 10)
await db.execute(sql`set time zone 'HST'`);
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-1000';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 00:00:00.123456-10' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 00:00:00.123456+00' }]);
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
await db.execute(sql`drop table if exists ${table}`);
});
test('select all fields', async (ctx) => {
const { db } = ctx.pg;
const now = Date.now();
await db.insert(usersTable).values({ name: 'John' });
const result = await db.select().from(usersTable);
expect(result[0]!.createdAt).toBeInstanceOf(Date);
expect(Math.abs(result[0]!.createdAt.getTime() - now)).toBeLessThan(3000);
expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]);
});
test('update with returning all fields', async (ctx) => {
const { db } = ctx.pg;
const now = Date.now();
await db.insert(usersTable).values({ name: 'John' });
const users = await db
.update(usersTable)
.set({ name: 'Jane' })
.where(eq(usersTable.name, 'John'))
.returning();
expect(users[0]!.createdAt).toBeInstanceOf(Date);
expect(Math.abs(users[0]!.createdAt.getTime() - now)).toBeLessThan(3000);
expect(users).toEqual([
{ id: 1, name: 'Jane', verified: false, jsonb: null, createdAt: users[0]!.createdAt },
]);
});
test('delete with returning all fields', async (ctx) => {
const { db } = ctx.pg;
const now = Date.now();
await db.insert(usersTable).values({ name: 'John' });
const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')).returning();
expect(users[0]!.createdAt).toBeInstanceOf(Date);
expect(Math.abs(users[0]!.createdAt.getTime() - now)).toBeLessThan(3000);
expect(users).toEqual([
{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: users[0]!.createdAt },
]);
});
test('mySchema :: select all fields', async (ctx) => {
const { db } = ctx.pg;
const now = Date.now();
await db.insert(usersMySchemaTable).values({ name: 'John' });
const result = await db.select().from(usersMySchemaTable);
expect(result[0]!.createdAt).toBeInstanceOf(Date);
expect(Math.abs(result[0]!.createdAt.getTime() - now)).toBeLessThan(3000);
expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]);
});
test('mySchema :: delete with returning all fields', async (ctx) => {
const { db } = ctx.pg;
const now = Date.now();
await db.insert(usersMySchemaTable).values({ name: 'John' });
const users = await db.delete(usersMySchemaTable).where(eq(usersMySchemaTable.name, 'John')).returning();
expect(users[0]!.createdAt).toBeInstanceOf(Date);
expect(Math.abs(users[0]!.createdAt.getTime() - now)).toBeLessThan(3000);
expect(users).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: users[0]!.createdAt }]);
});
skipTests([
'migrator : default migration strategy',
'migrator : migrate with custom schema',
'migrator : migrate with custom table',
'migrator : migrate with custom table and custom schema',
'insert via db.execute + select via db.execute',
'insert via db.execute + returning',
'insert via db.execute w/ query builder',
'all date and time columns without timezone first case mode string',
'all date and time columns without timezone third case mode date',
'test mode string for timestamp with timezone',
'test mode date for timestamp with timezone',
'test mode string for timestamp with timezone in UTC timezone',
'nested transaction rollback',
'transaction rollback',
'nested transaction',
'transaction',
'timestamp timezone',
'test $onUpdateFn and $onUpdate works as $default',
'select all fields',
'update with returning all fields',
'delete with returning all fields',
'mySchema :: select all fields',
'mySchema :: delete with returning all fields',
]);
tests();
cacheTests();
beforeEach(async () => {
await db.execute(sql`drop schema if exists public cascade`);
await db.execute(sql`drop schema if exists ${mySchema} cascade`);
await db.execute(sql`create schema public`);
await db.execute(sql`create schema ${mySchema}`);
await db.execute(
sql`
create table users (
id serial primary key,
name text not null,
verified boolean not null default false,
jsonb jsonb,
created_at timestamptz not null default now()
)
`,
);
await db.execute(
sql`
create table ${usersMySchemaTable} (
id serial primary key,
name text not null,
verified boolean not null default false,
jsonb jsonb,
created_at timestamptz not null default now()
)
`,
);
});
test('insert via db.execute + select via db.execute', async () => {
await db.execute(
sql`insert into ${usersTable} (${sql.identifier(usersTable.name.name)}) values (${'John'})`,
);
const result = await db.execute<{ id: number; name: string }>(
sql`select id, name from "users"`,
);
expect(result.rows).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute + returning', async () => {
const inserted = await db.execute<{ id: number; name: string }>(
sql`insert into ${usersTable} (${
sql.identifier(
usersTable.name.name,
)
}) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`,
);
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute w/ query builder', async () => {
const inserted = await db.execute<Pick<typeof usersTable.$inferSelect, 'id' | 'name'>>(
db
.insert(usersTable)
.values({ name: 'John' })
.returning({ id: usersTable.id, name: usersTable.name }),
);
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
});
@@ -0,0 +1,458 @@
import { sql } from 'drizzle-orm';
import { drizzle, type NetlifyDbDatabase } from 'drizzle-orm/netlify-db';
import { migrate } from 'drizzle-orm/netlify-db/migrator';
import { pgTable, serial, timestamp } from 'drizzle-orm/pg-core';
import { beforeAll, beforeEach, expect, test } from 'vitest';
import { skipTests } from '~/common';
import { randomString } from '~/utils';
import { tests, usersMigratorTable, usersTable } from './pg-common';
import { TestCache, TestGlobalCache, tests as cacheTests } from './pg-common-cache';
const ENABLE_LOGGING = false;
let db: NetlifyDbDatabase;
let dbGlobalCached: NetlifyDbDatabase;
let cachedDb: NetlifyDbDatabase;
beforeAll(async () => {
const connectionString = process.env['NETLIFY_DB_URL'];
if (!connectionString) {
throw new Error('NETLIFY_DB_URL is not defined');
}
db = drizzle(connectionString, { logger: ENABLE_LOGGING });
cachedDb = drizzle(connectionString, {
logger: ENABLE_LOGGING,
cache: new TestCache(),
});
dbGlobalCached = drizzle(connectionString, {
logger: ENABLE_LOGGING,
cache: new TestGlobalCache(),
});
});
beforeEach((ctx) => {
ctx.pg = {
db,
};
ctx.cachedPg = {
db: cachedDb,
dbGlobalCached,
};
});
test('migrator : default migration strategy', async () => {
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg' });
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table "drizzle"."__drizzle_migrations"`);
});
test('migrator : migrate with custom schema', async () => {
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: 'custom_migrations' });
// test if the custom migrations table was created
const { rowCount } = await db.execute(sql`select * from custom_migrations."__drizzle_migrations";`);
expect(rowCount && rowCount > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table custom_migrations."__drizzle_migrations"`);
});
test('migrator : migrate with custom table', async () => {
const customTable = randomString();
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable });
// test if the custom migrations table was created
const { rowCount } = await db.execute(sql`select * from "drizzle".${sql.identifier(customTable)};`);
expect(rowCount && rowCount > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table "drizzle".${sql.identifier(customTable)}`);
});
test('migrator : migrate with custom table and custom schema', async () => {
const customTable = randomString();
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, {
migrationsFolder: './drizzle2/pg',
migrationsTable: customTable,
migrationsSchema: 'custom_migrations',
});
// test if the custom migrations table was created
const { rowCount } = await db.execute(
sql`select * from custom_migrations.${sql.identifier(customTable)};`,
);
expect(rowCount && rowCount > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table custom_migrations.${sql.identifier(customTable)}`);
});
test('all date and time columns without timezone first case mode string', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).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
)
`);
// 1. Insert date in string format without timezone in it
await db.insert(table).values([
{ timestamp: '2022-01-01 02:00:00.123456' },
]);
// 2, Select in string format and check that values are the same
const result = await db.select().from(table);
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456' }]);
// 3. Select as raw query and check that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('all date and time columns without timezone second case mode string', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).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
)
`);
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: '2022-01-01T02:00:00.123456-02' },
]);
// 2, Select as raw query and check that values are the same
const result = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('all date and time columns without timezone third case mode date', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: '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(3) not null
)
`);
const insertedDate = new Date('2022-01-01 20:00:00.123+04');
// 1. Insert date as new date
await db.insert(table).values([
{ timestamp: insertedDate },
]);
// 2, Select as raw query as string
const result = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3. Compare both dates using orm mapping - Need to add 'Z' to tell JS that it is UTC
expect(new Date(result.rows[0]!.timestamp_string + 'Z').getTime()).toBe(insertedDate.getTime());
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-0200';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode date for timestamp with timezone', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'date', 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(3) with time zone not null
)
`);
const timestampString = new Date('2022-01-01 00:00:00.456-0200');
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: timestampString }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.456+00' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone in UTC timezone', async () => {
// get current timezone from db
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
// set timezone to UTC
await db.execute(sql`set time zone 'UTC'`);
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-0200';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
await db.execute(sql`drop table if exists ${table}`);
});
test.skip('test mode string for timestamp with timezone in different timezone', async () => {
// get current timezone from db
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
// set timezone to HST (UTC - 10)
await db.execute(sql`set time zone 'HST'`);
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-1000';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 00:00:00.123456-10' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 00:00:00.123456+00' }]);
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
await db.execute(sql`drop table if exists ${table}`);
});
skipTests([]);
tests();
cacheTests();
beforeEach(async () => {
await db.execute(sql`drop schema if exists public cascade`);
await db.execute(sql`create schema public`);
await db.execute(
sql`
create table users (
id serial primary key,
name text not null,
verified boolean not null default false,
jsonb jsonb,
created_at timestamptz not null default now()
)
`,
);
});
test('insert via db.execute + select via db.execute', async () => {
await db.execute(
sql`insert into ${usersTable} (${sql.identifier(usersTable.name.name)}) values (${'John'})`,
);
const result = await db.execute<{ id: number; name: string }>(
sql`select id, name from "users"`,
);
expect(result.rows).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute + returning', async () => {
const inserted = await db.execute<{ id: number; name: string }>(
sql`insert into ${usersTable} (${
sql.identifier(
usersTable.name.name,
)
}) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`,
);
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute w/ query builder', async () => {
const inserted = await db.execute<Pick<typeof usersTable.$inferSelect, 'id' | 'name'>>(
db
.insert(usersTable)
.values({ name: 'John' })
.returning({ id: usersTable.id, name: usersTable.name }),
);
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
});
@@ -0,0 +1,492 @@
import retry from 'async-retry';
import { sql } from 'drizzle-orm';
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
import { drizzle } from 'drizzle-orm/node-postgres';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { pgTable, serial, timestamp } from 'drizzle-orm/pg-core';
import { Client } from 'pg';
import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest';
import { skipTests } from '~/common';
import { randomString } from '~/utils';
import { createDockerDB, tests, usersMigratorTable, usersTable } from './pg-common';
import { TestCache, TestGlobalCache, tests as cacheTests } from './pg-common-cache';
const ENABLE_LOGGING = false;
let db: NodePgDatabase;
let client: Client;
let dbGlobalCached: NodePgDatabase;
let cachedDb: NodePgDatabase;
beforeAll(async () => {
let connectionString;
if (process.env['PG_CONNECTION_STRING']) {
connectionString = process.env['PG_CONNECTION_STRING'];
} else {
const { connectionString: conStr } = await createDockerDB();
connectionString = conStr;
}
client = await retry(async () => {
client = new Client(connectionString);
await client.connect();
return client;
}, {
retries: 20,
factor: 1,
minTimeout: 250,
maxTimeout: 250,
randomize: false,
onRetry() {
client?.end();
},
});
db = drizzle(client, { logger: ENABLE_LOGGING });
cachedDb = drizzle(client, { logger: ENABLE_LOGGING, cache: new TestCache() });
dbGlobalCached = drizzle(client, { logger: ENABLE_LOGGING, cache: new TestGlobalCache() });
});
afterAll(async () => {
await client?.end();
});
beforeEach((ctx) => {
ctx.pg = {
db,
};
ctx.cachedPg = {
db: cachedDb,
dbGlobalCached,
};
});
test('migrator : default migration strategy', async () => {
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg' });
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table "drizzle"."__drizzle_migrations"`);
});
test('migrator : migrate with custom schema', async () => {
const customSchema = randomString();
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: customSchema });
// test if the custom migrations table was created
const { rowCount } = await db.execute(sql`select * from ${sql.identifier(customSchema)}."__drizzle_migrations";`);
expect(rowCount && rowCount > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table ${sql.identifier(customSchema)}."__drizzle_migrations"`);
});
test('migrator : migrate with custom table', async () => {
const customTable = randomString();
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable });
// test if the custom migrations table was created
const { rowCount } = await db.execute(sql`select * from "drizzle".${sql.identifier(customTable)};`);
expect(rowCount && rowCount > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table "drizzle".${sql.identifier(customTable)}`);
});
test('migrator : migrate with custom table and custom schema', async () => {
const customTable = randomString();
const customSchema = randomString();
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, {
migrationsFolder: './drizzle2/pg',
migrationsTable: customTable,
migrationsSchema: customSchema,
});
// test if the custom migrations table was created
const { rowCount } = await db.execute(
sql`select * from ${sql.identifier(customSchema)}.${sql.identifier(customTable)};`,
);
expect(rowCount && rowCount > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table ${sql.identifier(customSchema)}.${sql.identifier(customTable)}`);
});
test('all date and time columns without timezone first case mode string', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).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
)
`);
// 1. Insert date in string format without timezone in it
await db.insert(table).values([
{ timestamp: '2022-01-01 02:00:00.123456' },
]);
// 2, Select in string format and check that values are the same
const result = await db.select().from(table);
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456' }]);
// 3. Select as raw query and check that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('all date and time columns without timezone second case mode string', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).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
)
`);
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: '2022-01-01T02:00:00.123456-02' },
]);
// 2, Select as raw query and check that values are the same
const result = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('all date and time columns without timezone third case mode date', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: '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(3) not null
)
`);
const insertedDate = new Date('2022-01-01 20:00:00.123+04');
// 1. Insert date as new date
await db.insert(table).values([
{ timestamp: insertedDate },
]);
// 2, Select as raw query as string
const result = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3. Compare both dates using orm mapping - Need to add 'Z' to tell JS that it is UTC
expect(new Date(result.rows[0]!.timestamp_string + 'Z').getTime()).toBe(insertedDate.getTime());
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-0200';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode date for timestamp with timezone', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'date', 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(3) with time zone not null
)
`);
const timestampString = new Date('2022-01-01 00:00:00.456-0200');
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: timestampString }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.456+00' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone in UTC timezone', async () => {
// get current timezone from db
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
// set timezone to UTC
await db.execute(sql`set time zone 'UTC'`);
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-0200';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone in different timezone', async () => {
// get current timezone from db
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
// set timezone to HST (UTC - 10)
await db.execute(sql`set time zone '-10'`);
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-1000';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 00:00:00.123456-10' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 00:00:00.123456-10' }]);
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
await db.execute(sql`drop table if exists ${table}`);
});
skipTests([
'migrator : default migration strategy',
'migrator : migrate with custom schema',
'migrator : migrate with custom table',
'migrator : migrate with custom table and custom schema',
'insert via db.execute + select via db.execute',
'insert via db.execute + returning',
'insert via db.execute w/ query builder',
'all date and time columns without timezone first case mode string',
'all date and time columns without timezone third case mode date',
'test mode string for timestamp with timezone',
'test mode date for timestamp with timezone',
'test mode string for timestamp with timezone in UTC timezone',
'test mode string for timestamp with timezone in different timezone',
]);
tests();
cacheTests();
beforeEach(async () => {
await db.execute(sql`drop schema if exists public cascade`);
await db.execute(sql`create schema public`);
await db.execute(
sql`
create table users (
id serial primary key,
name text not null,
verified boolean not null default false,
jsonb jsonb,
created_at timestamptz not null default now()
)
`,
);
});
test('insert via db.execute + select via db.execute', async () => {
await db.execute(
sql`insert into ${usersTable} (${sql.identifier(usersTable.name.name)}) values (${'John'})`,
);
const result = await db.execute<{ id: number; name: string }>(
sql`select id, name from "users"`,
);
expect(result.rows).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute + returning', async () => {
const inserted = await db.execute<{ id: number; name: string }>(
sql`insert into ${usersTable} (${
sql.identifier(
usersTable.name.name,
)
}) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`,
);
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute w/ query builder', async () => {
const inserted = await db.execute<Pick<typeof usersTable.$inferSelect, 'id' | 'name'>>(
db
.insert(usersTable)
.values({ name: 'John' })
.returning({ id: usersTable.id, name: usersTable.name }),
);
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
});
@@ -0,0 +1,400 @@
import type Docker from 'dockerode';
import { eq, getTableName, is, sql, Table } from 'drizzle-orm';
import type { MutationOption } from 'drizzle-orm/cache/core';
import { Cache } from 'drizzle-orm/cache/core';
import type { CacheConfig } from 'drizzle-orm/cache/core/types';
import type { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core';
import { alias, boolean, integer, jsonb, pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
import Keyv from 'keyv';
import { afterAll, beforeEach, describe, expect, test, vi } from 'vitest';
// eslint-disable-next-line drizzle-internal/require-entity-kind
export class TestGlobalCache extends Cache {
private globalTtl: number = 1000;
private usedTablesPerKey: Record<string, string[]> = {};
constructor(private kv: Keyv = new Keyv()) {
super();
}
override strategy(): 'explicit' | 'all' {
return 'all';
}
override async get(key: string, _tables: string[], _isTag: boolean): Promise<any[] | undefined> {
const res = await this.kv.get(key) ?? undefined;
return res;
}
override async put(
key: string,
response: any,
tables: string[],
isTag: boolean,
config?: CacheConfig,
): Promise<void> {
await this.kv.set(key, response, config ? config.ex : this.globalTtl);
for (const table of tables) {
const keys = this.usedTablesPerKey[table];
if (keys === undefined) {
this.usedTablesPerKey[table] = [key];
} else {
keys.push(key);
}
}
}
override async onMutate(params: MutationOption): Promise<void> {
const tagsArray = params.tags ? Array.isArray(params.tags) ? params.tags : [params.tags] : [];
const tablesArray = params.tables ? Array.isArray(params.tables) ? params.tables : [params.tables] : [];
const keysToDelete = new Set<string>();
for (const table of tablesArray) {
const tableName = is(table, Table) ? getTableName(table) : table as string;
const keys = this.usedTablesPerKey[tableName] ?? [];
for (const key of keys) keysToDelete.add(key);
}
if (keysToDelete.size > 0 || tagsArray.length > 0) {
for (const tag of tagsArray) {
await this.kv.delete(tag);
}
for (const key of keysToDelete) {
await this.kv.delete(key);
for (const table of tablesArray) {
const tableName = is(table, Table) ? getTableName(table) : table as string;
this.usedTablesPerKey[tableName] = [];
}
}
}
}
}
// eslint-disable-next-line drizzle-internal/require-entity-kind
export class TestCache extends TestGlobalCache {
override strategy(): 'explicit' | 'all' {
return 'explicit';
}
}
declare module 'vitest' {
interface TestContext {
cachedPg: {
db: PgDatabase<PgQueryResultHKT>;
dbGlobalCached: PgDatabase<PgQueryResultHKT>;
};
}
}
const usersTable = pgTable('users', {
id: serial().primaryKey(),
name: text().notNull(),
verified: boolean().notNull().default(false),
jsonb: jsonb().$type<string[]>(),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
});
const postsTable = pgTable('posts', {
id: serial().primaryKey(),
description: text().notNull(),
userId: integer('city_id').references(() => usersTable.id),
});
let pgContainer: Docker.Container;
afterAll(async () => {
await pgContainer?.stop().catch(console.error);
});
export function tests() {
describe('common', () => {
beforeEach(async (ctx) => {
const { db, dbGlobalCached } = ctx.cachedPg;
await db.execute(sql`drop schema if exists public cascade`);
await db.$cache?.invalidate({ tables: 'users' });
await dbGlobalCached.$cache?.invalidate({ tables: 'users' });
await db.execute(sql`create schema public`);
// public users
await db.execute(
sql`
create table users (
id serial primary key,
name text not null,
verified boolean not null default false,
jsonb jsonb,
created_at timestamptz not null default now()
)
`,
);
});
test('test force invalidate', async (ctx) => {
const { db } = ctx.cachedPg;
const spyInvalidate = vi.spyOn(db.$cache, 'invalidate');
await db.$cache?.invalidate({ tables: 'users' });
expect(spyInvalidate).toHaveBeenCalledTimes(1);
});
test('default global config - no cache should be hit', async (ctx) => {
const { db } = ctx.cachedPg;
// @ts-expect-error
const spyPut = vi.spyOn(db.$cache, 'put');
// @ts-expect-error
const spyGet = vi.spyOn(db.$cache, 'get');
// @ts-expect-error
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
await db.select().from(usersTable);
expect(spyPut).toHaveBeenCalledTimes(0);
expect(spyGet).toHaveBeenCalledTimes(0);
expect(spyInvalidate).toHaveBeenCalledTimes(0);
});
test('default global config + enable cache on select: get, put', async (ctx) => {
const { db } = ctx.cachedPg;
// @ts-expect-error
const spyPut = vi.spyOn(db.$cache, 'put');
// @ts-expect-error
const spyGet = vi.spyOn(db.$cache, 'get');
// @ts-expect-error
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
await db.select().from(usersTable).$withCache();
expect(spyPut).toHaveBeenCalledTimes(1);
expect(spyGet).toHaveBeenCalledTimes(1);
expect(spyInvalidate).toHaveBeenCalledTimes(0);
});
test('default global config + enable cache on select + write: get, put, onMutate', async (ctx) => {
const { db } = ctx.cachedPg;
// @ts-expect-error
const spyPut = vi.spyOn(db.$cache, 'put');
// @ts-expect-error
const spyGet = vi.spyOn(db.$cache, 'get');
// @ts-expect-error
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
await db.select().from(usersTable).$withCache({ config: { ex: 1 } });
expect(spyPut).toHaveBeenCalledTimes(1);
expect(spyGet).toHaveBeenCalledTimes(1);
expect(spyInvalidate).toHaveBeenCalledTimes(0);
spyPut.mockClear();
spyGet.mockClear();
spyInvalidate.mockClear();
await db.insert(usersTable).values({ name: 'John' });
expect(spyPut).toHaveBeenCalledTimes(0);
expect(spyGet).toHaveBeenCalledTimes(0);
expect(spyInvalidate).toHaveBeenCalledTimes(1);
});
test('default global config + enable cache on select + disable invalidate: get, put', async (ctx) => {
const { db } = ctx.cachedPg;
// @ts-expect-error
const spyPut = vi.spyOn(db.$cache, 'put');
// @ts-expect-error
const spyGet = vi.spyOn(db.$cache, 'get');
// @ts-expect-error
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
await db.select().from(usersTable).$withCache({ tag: 'custom', autoInvalidate: false, config: { ex: 1 } });
expect(spyPut).toHaveBeenCalledTimes(1);
expect(spyGet).toHaveBeenCalledTimes(1);
expect(spyInvalidate).toHaveBeenCalledTimes(0);
await db.insert(usersTable).values({ name: 'John' });
// invalidate force
await db.$cache?.invalidate({ tags: ['custom'] });
});
test('global: true + disable cache', async (ctx) => {
const { dbGlobalCached: db } = ctx.cachedPg;
// @ts-expect-error
const spyPut = vi.spyOn(db.$cache, 'put');
// @ts-expect-error
const spyGet = vi.spyOn(db.$cache, 'get');
// @ts-expect-error
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
await db.select().from(usersTable).$withCache(false);
expect(spyPut).toHaveBeenCalledTimes(0);
expect(spyGet).toHaveBeenCalledTimes(0);
expect(spyInvalidate).toHaveBeenCalledTimes(0);
});
test('global: true - cache should be hit', async (ctx) => {
const { dbGlobalCached: db } = ctx.cachedPg;
// @ts-expect-error
const spyPut = vi.spyOn(db.$cache, 'put');
// @ts-expect-error
const spyGet = vi.spyOn(db.$cache, 'get');
// @ts-expect-error
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
await db.select().from(usersTable);
expect(spyPut).toHaveBeenCalledTimes(1);
expect(spyGet).toHaveBeenCalledTimes(1);
expect(spyInvalidate).toHaveBeenCalledTimes(0);
});
test('global: true - cache: false on select - no cache hit', async (ctx) => {
const { dbGlobalCached: db } = ctx.cachedPg;
// @ts-expect-error
const spyPut = vi.spyOn(db.$cache, 'put');
// @ts-expect-error
const spyGet = vi.spyOn(db.$cache, 'get');
// @ts-expect-error
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
await db.select().from(usersTable).$withCache(false);
expect(spyPut).toHaveBeenCalledTimes(0);
expect(spyGet).toHaveBeenCalledTimes(0);
expect(spyInvalidate).toHaveBeenCalledTimes(0);
});
test('global: true - disable invalidate - cache hit + no invalidate', async (ctx) => {
const { dbGlobalCached: db } = ctx.cachedPg;
// @ts-expect-error
const spyPut = vi.spyOn(db.$cache, 'put');
// @ts-expect-error
const spyGet = vi.spyOn(db.$cache, 'get');
// @ts-expect-error
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
await db.select().from(usersTable).$withCache({ autoInvalidate: false });
expect(spyPut).toHaveBeenCalledTimes(1);
expect(spyGet).toHaveBeenCalledTimes(1);
expect(spyInvalidate).toHaveBeenCalledTimes(0);
spyPut.mockClear();
spyGet.mockClear();
spyInvalidate.mockClear();
await db.insert(usersTable).values({ name: 'John' });
expect(spyPut).toHaveBeenCalledTimes(0);
expect(spyGet).toHaveBeenCalledTimes(0);
expect(spyInvalidate).toHaveBeenCalledTimes(1);
});
test('global: true - with custom tag', async (ctx) => {
const { dbGlobalCached: db } = ctx.cachedPg;
// @ts-expect-error
const spyPut = vi.spyOn(db.$cache, 'put');
// @ts-expect-error
const spyGet = vi.spyOn(db.$cache, 'get');
// @ts-expect-error
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
await db.select().from(usersTable).$withCache({ tag: 'custom', autoInvalidate: false });
expect(spyPut).toHaveBeenCalledTimes(1);
expect(spyGet).toHaveBeenCalledTimes(1);
expect(spyInvalidate).toHaveBeenCalledTimes(0);
await db.insert(usersTable).values({ name: 'John' });
// invalidate force
await db.$cache?.invalidate({ tags: ['custom'] });
});
test('global: true - with custom tag + with autoinvalidate', async (ctx) => {
const { dbGlobalCached: db } = ctx.cachedPg;
// @ts-expect-error
const spyPut = vi.spyOn(db.$cache, 'put');
// @ts-expect-error
const spyGet = vi.spyOn(db.$cache, 'get');
// @ts-expect-error
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
await db.select().from(usersTable).$withCache({ tag: 'custom' });
expect(spyPut).toHaveBeenCalledTimes(1);
expect(spyGet).toHaveBeenCalledTimes(1);
await db.insert(usersTable).values({ name: 'John' });
expect(spyInvalidate).toHaveBeenCalledTimes(1);
// invalidate force
await db.$cache?.invalidate({ tags: ['custom'] });
});
// check select used tables
test('check simple select used tables', (ctx) => {
const { db } = ctx.cachedPg;
// @ts-expect-error
expect(db.select().from(usersTable).getUsedTables()).toStrictEqual(['users']);
// @ts-expect-error
expect(db.select().from(sql`${usersTable}`).getUsedTables()).toStrictEqual(['users']);
});
// check select+join used tables
test('select+join', (ctx) => {
const { db } = ctx.cachedPg;
// @ts-expect-error
expect(db.select().from(usersTable).leftJoin(postsTable, eq(usersTable.id, postsTable.userId)).getUsedTables())
.toStrictEqual(['users', 'posts']);
expect(
// @ts-expect-error
db.select().from(sql`${usersTable}`).leftJoin(postsTable, eq(usersTable.id, postsTable.userId)).getUsedTables(),
).toStrictEqual(['users', 'posts']);
});
// check select+2join used tables
test('select+2joins', (ctx) => {
const { db } = ctx.cachedPg;
expect(
db.select().from(usersTable).leftJoin(
postsTable,
eq(usersTable.id, postsTable.userId),
).leftJoin(
alias(postsTable, 'post2'),
eq(usersTable.id, postsTable.userId),
)
// @ts-expect-error
.getUsedTables(),
)
.toStrictEqual(['users', 'posts']);
expect(
db.select().from(sql`${usersTable}`).leftJoin(postsTable, eq(usersTable.id, postsTable.userId)).leftJoin(
alias(postsTable, 'post2'),
eq(usersTable.id, postsTable.userId),
// @ts-expect-error
).getUsedTables(),
).toStrictEqual(['users', 'posts']);
});
// select subquery used tables
test('select+join', (ctx) => {
const { db } = ctx.cachedPg;
const sq = db.select().from(usersTable).where(eq(usersTable.id, 42)).as('sq');
db.select().from(sq);
// @ts-expect-error
expect(db.select().from(sq).getUsedTables()).toStrictEqual(['users']);
});
});
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,798 @@
import retry from 'async-retry';
import type Docker from 'dockerode';
import { asc, eq, sql } from 'drizzle-orm';
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
import { drizzle } from 'drizzle-orm/node-postgres';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { alias, customType, pgTable, pgTableCreator, serial, text } from 'drizzle-orm/pg-core';
import { Client } from 'pg';
import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest';
import { randomString } from '~/utils';
import { createDockerDB } from './pg-common';
const ENABLE_LOGGING = false;
let db: NodePgDatabase;
let client: Client;
let container: Docker.Container | undefined;
beforeAll(async () => {
let connectionString;
if (process.env['PG_CONNECTION_STRING']) {
connectionString = process.env['PG_CONNECTION_STRING'];
} else {
const { connectionString: conStr, container: contrainerObj } = await createDockerDB();
connectionString = conStr;
container = contrainerObj;
}
client = await retry(async () => {
client = new Client(connectionString);
await client.connect();
return client;
}, {
retries: 20,
factor: 1,
minTimeout: 250,
maxTimeout: 250,
randomize: false,
onRetry() {
client?.end();
},
});
db = drizzle(client, { logger: ENABLE_LOGGING });
});
afterAll(async () => {
await client?.end();
await container?.stop().catch(console.error);
});
beforeEach((ctx) => {
ctx.pg = {
db,
};
});
const customSerial = customType<{ data: number; notNull: true; default: true }>({
dataType() {
return 'serial';
},
});
const customText = customType<{ data: string }>({
dataType() {
return 'text';
},
});
const customBoolean = customType<{ data: boolean }>({
dataType() {
return 'boolean';
},
});
const customJsonb = <TData>(name: string) =>
customType<{ data: TData; driverData: string }>({
dataType() {
return 'jsonb';
},
toDriver(value: TData): string {
return JSON.stringify(value);
},
})(name);
const customTimestamp = customType<
{ data: Date; driverData: string; config: { withTimezone: boolean; precision?: number } }
>({
dataType(config) {
const precision = config?.precision === undefined ? '' : ` (${config.precision})`;
return `timestamp${precision}${config?.withTimezone ? ' with time zone' : ''}`;
},
fromDriver(value: string): Date {
return new Date(value);
},
});
const usersTable = pgTable('users', {
id: customSerial('id').primaryKey(),
name: customText('name').notNull(),
verified: customBoolean('verified').notNull().default(false),
jsonb: customJsonb<string[]>('jsonb'),
createdAt: customTimestamp('created_at', { withTimezone: true }).notNull().default(sql`now()`),
});
const usersMigratorTable = pgTable('users12', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull(),
});
beforeEach(async (ctx) => {
const { db } = ctx.pg;
await db.execute(sql`drop schema if exists public cascade`);
await db.execute(sql`create schema public`);
await db.execute(
sql`
create table users (
id serial primary key,
name text not null,
verified boolean not null default false,
jsonb jsonb,
created_at timestamptz not null default now()
)
`,
);
});
test('select all fields', async (ctx) => {
const { db } = ctx.pg;
const now = Date.now();
await db.insert(usersTable).values({ name: 'John' });
const result = await db.select().from(usersTable);
expect(result[0]!.createdAt).toBeInstanceOf(Date);
expect(Math.abs(result[0]!.createdAt.getTime() - now)).toBeLessThan(100);
expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]);
});
test('select sql', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values({ name: 'John' });
const users = await db.select({
name: sql`upper(${usersTable.name})`,
}).from(usersTable);
expect(users).toEqual([{ name: 'JOHN' }]);
});
test('select typed sql', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values({ name: 'John' });
const users = await db.select({
name: sql<string>`upper(${usersTable.name})`,
}).from(usersTable);
expect(users).toEqual([{ name: 'JOHN' }]);
});
test('insert returning sql', async (ctx) => {
const { db } = ctx.pg;
const users = await db.insert(usersTable).values({ name: 'John' }).returning({
name: sql`upper(${usersTable.name})`,
});
expect(users).toEqual([{ name: 'JOHN' }]);
});
test('delete returning sql', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values({ name: 'John' });
const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')).returning({
name: sql`upper(${usersTable.name})`,
});
expect(users).toEqual([{ name: 'JOHN' }]);
});
test('update returning sql', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values({ name: 'John' });
const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning({
name: sql`upper(${usersTable.name})`,
});
expect(users).toEqual([{ name: 'JANE' }]);
});
test('update with returning all fields', async (ctx) => {
const { db } = ctx.pg;
const now = Date.now();
await db.insert(usersTable).values({ name: 'John' });
const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning();
expect(users[0]!.createdAt).toBeInstanceOf(Date);
expect(Math.abs(users[0]!.createdAt.getTime() - now)).toBeLessThan(100);
expect(users).toEqual([{ id: 1, name: 'Jane', verified: false, jsonb: null, createdAt: users[0]!.createdAt }]);
});
test('update with returning partial', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values({ name: 'John' });
const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning({
id: usersTable.id,
name: usersTable.name,
});
expect(users).toEqual([{ id: 1, name: 'Jane' }]);
});
test('delete with returning all fields', async (ctx) => {
const { db } = ctx.pg;
const now = Date.now();
await db.insert(usersTable).values({ name: 'John' });
const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')).returning();
expect(users[0]!.createdAt).toBeInstanceOf(Date);
expect(Math.abs(users[0]!.createdAt.getTime() - now)).toBeLessThan(100);
expect(users).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: users[0]!.createdAt }]);
});
test('delete with returning partial', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values({ name: 'John' });
const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')).returning({
id: usersTable.id,
name: usersTable.name,
});
expect(users).toEqual([{ id: 1, name: 'John' }]);
});
test('insert + select', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values({ name: 'John' });
const result = await db.select().from(usersTable);
expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]);
await db.insert(usersTable).values({ name: 'Jane' });
const result2 = await db.select().from(usersTable);
expect(result2).toEqual([
{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result2[0]!.createdAt },
{ id: 2, name: 'Jane', verified: false, jsonb: null, createdAt: result2[1]!.createdAt },
]);
});
test('json insert', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values({ name: 'John', jsonb: ['foo', 'bar'] });
const result = await db.select({
id: usersTable.id,
name: usersTable.name,
jsonb: usersTable.jsonb,
}).from(usersTable);
expect(result).toEqual([{ id: 1, name: 'John', jsonb: ['foo', 'bar'] }]);
});
test('insert with overridden default values', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values({ name: 'John', verified: true });
const result = await db.select().from(usersTable);
expect(result).toEqual([{ id: 1, name: 'John', verified: true, jsonb: null, createdAt: result[0]!.createdAt }]);
});
test('insert many', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values([
{ name: 'John' },
{ name: 'Bruce', jsonb: ['foo', 'bar'] },
{ name: 'Jane' },
{ name: 'Austin', verified: true },
]);
const result = await db.select({
id: usersTable.id,
name: usersTable.name,
jsonb: usersTable.jsonb,
verified: usersTable.verified,
}).from(usersTable);
expect(result).toEqual([
{ id: 1, name: 'John', jsonb: null, verified: false },
{ id: 2, name: 'Bruce', jsonb: ['foo', 'bar'], verified: false },
{ id: 3, name: 'Jane', jsonb: null, verified: false },
{ id: 4, name: 'Austin', jsonb: null, verified: true },
]);
});
test('insert many with returning', async (ctx) => {
const { db } = ctx.pg;
const result = await db.insert(usersTable).values([
{ name: 'John' },
{ name: 'Bruce', jsonb: ['foo', 'bar'] },
{ name: 'Jane' },
{ name: 'Austin', verified: true },
])
.returning({
id: usersTable.id,
name: usersTable.name,
jsonb: usersTable.jsonb,
verified: usersTable.verified,
});
expect(result).toEqual([
{ id: 1, name: 'John', jsonb: null, verified: false },
{ id: 2, name: 'Bruce', jsonb: ['foo', 'bar'], verified: false },
{ id: 3, name: 'Jane', jsonb: null, verified: false },
{ id: 4, name: 'Austin', jsonb: null, verified: true },
]);
});
test('select with group by as field', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
const result = await db.select({ name: usersTable.name }).from(usersTable)
.groupBy(usersTable.name);
expect(result).toEqual([{ name: 'Jane' }, { name: 'John' }]);
});
test('select with group by as sql', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
const result = await db.select({ name: usersTable.name }).from(usersTable)
.groupBy(sql`${usersTable.name}`);
expect(result).toEqual([{ name: 'Jane' }, { name: 'John' }]);
});
test('select with group by as sql + column', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
const result = await db.select({ name: usersTable.name }).from(usersTable)
.groupBy(sql`${usersTable.name}`, usersTable.id);
expect(result).toEqual([{ name: 'Jane' }, { name: 'Jane' }, { name: 'John' }]);
});
test('select with group by as column + sql', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
const result = await db.select({ name: usersTable.name }).from(usersTable)
.groupBy(usersTable.id, sql`${usersTable.name}`);
expect(result).toEqual([{ name: 'Jane' }, { name: 'Jane' }, { name: 'John' }]);
});
test('select with group by complex query', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
const result = await db.select({ name: usersTable.name }).from(usersTable)
.groupBy(usersTable.id, sql`${usersTable.name}`)
.orderBy(asc(usersTable.name))
.limit(1);
expect(result).toEqual([{ name: 'Jane' }]);
});
test('build query', async (ctx) => {
const { db } = ctx.pg;
const query = db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable)
.groupBy(usersTable.id, usersTable.name)
.toSQL();
expect(query).toEqual({
sql: 'select "id", "name" from "users" group by "users"."id", "users"."name"',
params: [],
});
});
test('insert sql', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values({ name: sql`${'John'}` });
const result = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable);
expect(result).toEqual([{ id: 1, name: 'John' }]);
});
test('partial join with alias', async (ctx) => {
const { db } = ctx.pg;
const customerAlias = alias(usersTable, 'customer');
await db.insert(usersTable).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]);
const result = await db
.select({
user: {
id: usersTable.id,
name: usersTable.name,
},
customer: {
id: customerAlias.id,
name: customerAlias.name,
},
}).from(usersTable)
.leftJoin(customerAlias, eq(customerAlias.id, 11))
.where(eq(usersTable.id, 10));
expect(result).toEqual([{
user: { id: 10, name: 'Ivan' },
customer: { id: 11, name: 'Hans' },
}]);
});
test('full join with alias', async (ctx) => {
const { db } = ctx.pg;
const pgTable = pgTableCreator((name) => `prefixed_${name}`);
const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
});
await db.execute(sql`drop table if exists ${users}`);
await db.execute(sql`create table ${users} (id serial primary key, name text not null)`);
const customers = alias(users, 'customer');
await db.insert(users).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]);
const result = await db
.select().from(users)
.leftJoin(customers, eq(customers.id, 11))
.where(eq(users.id, 10));
expect(result).toEqual([{
users: {
id: 10,
name: 'Ivan',
},
customer: {
id: 11,
name: 'Hans',
},
}]);
await db.execute(sql`drop table ${users}`);
});
test('insert with spaces', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values({ name: sql`'Jo h n'` });
const result = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable);
expect(result).toEqual([{ id: 1, name: 'Jo h n' }]);
});
test('prepared statement', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values({ name: 'John' });
const statement = db.select({
id: usersTable.id,
name: usersTable.name,
}).from(usersTable)
.prepare('statement1');
const result = await statement.execute();
expect(result).toEqual([{ id: 1, name: 'John' }]);
});
test('prepared statement reuse', async (ctx) => {
const { db } = ctx.pg;
const stmt = db.insert(usersTable).values({
verified: true,
name: sql.placeholder('name'),
}).prepare('stmt2');
for (let i = 0; i < 10; i++) {
await stmt.execute({ name: `John ${i}` });
}
const result = await db.select({
id: usersTable.id,
name: usersTable.name,
verified: usersTable.verified,
}).from(usersTable);
expect(result).toEqual([
{ id: 1, name: 'John 0', verified: true },
{ id: 2, name: 'John 1', verified: true },
{ id: 3, name: 'John 2', verified: true },
{ id: 4, name: 'John 3', verified: true },
{ id: 5, name: 'John 4', verified: true },
{ id: 6, name: 'John 5', verified: true },
{ id: 7, name: 'John 6', verified: true },
{ id: 8, name: 'John 7', verified: true },
{ id: 9, name: 'John 8', verified: true },
{ id: 10, name: 'John 9', verified: true },
]);
});
test('prepared statement with placeholder in .where', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values({ name: 'John' });
const stmt = db.select({
id: usersTable.id,
name: usersTable.name,
}).from(usersTable)
.where(eq(usersTable.id, sql.placeholder('id')))
.prepare('stmt3');
const result = await stmt.execute({ id: 1 });
expect(result).toEqual([{ id: 1, name: 'John' }]);
});
test('prepared statement with placeholder in .limit', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values({ name: 'John' });
const stmt = db
.select({
id: usersTable.id,
name: usersTable.name,
})
.from(usersTable)
.where(eq(usersTable.id, sql.placeholder('id')))
.limit(sql.placeholder('limit'))
.prepare('stmt_limit');
const result = await stmt.execute({ id: 1, limit: 1 });
expect(result).toEqual([{ id: 1, name: 'John' }]);
expect(result).toHaveLength(1);
});
test('prepared statement with placeholder in .offset', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable).values([{ name: 'John' }, { name: 'John1' }]);
const stmt = db
.select({
id: usersTable.id,
name: usersTable.name,
})
.from(usersTable)
.offset(sql.placeholder('offset'))
.prepare('stmt_offset');
const result = await stmt.execute({ offset: 1 });
expect(result).toEqual([{ id: 2, name: 'John1' }]);
});
test('migrator : default migration strategy', async () => {
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg' });
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table "drizzle"."__drizzle_migrations"`);
});
test('migrator : migrate with custom schema', async () => {
const customSchema = randomString();
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: customSchema });
// test if the custom migrations table was created
const { rowCount } = await db.execute(sql`select * from ${sql.identifier(customSchema)}."__drizzle_migrations";`);
expect(rowCount! > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table ${sql.identifier(customSchema)}."__drizzle_migrations"`);
});
test('migrator : migrate with custom table', async () => {
const customTable = randomString();
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable });
// test if the custom migrations table was created
const { rowCount } = await db.execute(sql`select * from "drizzle".${sql.identifier(customTable)};`);
expect(rowCount! > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table "drizzle".${sql.identifier(customTable)}`);
});
test('migrator : migrate with custom table and custom schema', async () => {
const customTable = randomString();
const customSchema = randomString();
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, {
migrationsFolder: './drizzle2/pg',
migrationsTable: customTable,
migrationsSchema: customSchema,
});
// test if the custom migrations table was created
const { rowCount } = await db.execute(
sql`select * from ${sql.identifier(customSchema)}.${sql.identifier(customTable)};`,
);
expect(rowCount! > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table ${sql.identifier(customSchema)}.${sql.identifier(customTable)}`);
});
test('insert via db.execute + select via db.execute', async () => {
await db.execute(sql`insert into ${usersTable} (${sql.identifier(usersTable.name.name)}) values (${'John'})`);
const result = await db.execute<{ id: number; name: string }>(sql`select id, name from "users"`);
expect(result.rows).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute + returning', async () => {
const inserted = await db.execute<{ id: number; name: string }>(
sql`insert into ${usersTable} (${
sql.identifier(usersTable.name.name)
}) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`,
);
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute w/ query builder', async () => {
const inserted = await db.execute<Pick<typeof usersTable.$inferSelect, 'id' | 'name'>>(
db.insert(usersTable).values({ name: 'John' }).returning({ id: usersTable.id, name: usersTable.name }),
);
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
});
test('build query insert with onConflict do update', async (ctx) => {
const { db } = ctx.pg;
const query = db.insert(usersTable)
.values({ name: 'John', jsonb: ['foo', 'bar'] })
.onConflictDoUpdate({ target: usersTable.id, set: { name: 'John1' } })
.toSQL();
expect(query).toEqual({
sql:
'insert into "users" ("id", "name", "verified", "jsonb", "created_at") values (default, $1, default, $2, default) on conflict ("id") do update set "name" = $3',
params: ['John', '["foo","bar"]', 'John1'],
});
});
test('build query insert with onConflict do update / multiple columns', async (ctx) => {
const { db } = ctx.pg;
const query = db.insert(usersTable)
.values({ name: 'John', jsonb: ['foo', 'bar'] })
.onConflictDoUpdate({ target: [usersTable.id, usersTable.name], set: { name: 'John1' } })
.toSQL();
expect(query).toEqual({
sql:
'insert into "users" ("id", "name", "verified", "jsonb", "created_at") values (default, $1, default, $2, default) on conflict ("id","name") do update set "name" = $3',
params: ['John', '["foo","bar"]', 'John1'],
});
});
test('build query insert with onConflict do nothing', async (ctx) => {
const { db } = ctx.pg;
const query = db.insert(usersTable)
.values({ name: 'John', jsonb: ['foo', 'bar'] })
.onConflictDoNothing()
.toSQL();
expect(query).toEqual({
sql:
'insert into "users" ("id", "name", "verified", "jsonb", "created_at") values (default, $1, default, $2, default) on conflict do nothing',
params: ['John', '["foo","bar"]'],
});
});
test('build query insert with onConflict do nothing + target', async (ctx) => {
const { db } = ctx.pg;
const query = db.insert(usersTable)
.values({ name: 'John', jsonb: ['foo', 'bar'] })
.onConflictDoNothing({ target: usersTable.id })
.toSQL();
expect(query).toEqual({
sql:
'insert into "users" ("id", "name", "verified", "jsonb", "created_at") values (default, $1, default, $2, default) on conflict ("id") do nothing',
params: ['John', '["foo","bar"]'],
});
});
test('insert with onConflict do update', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable)
.values({ name: 'John' });
await db.insert(usersTable)
.values({ id: 1, name: 'John' })
.onConflictDoUpdate({ target: usersTable.id, set: { name: 'John1' } });
const res = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where(
eq(usersTable.id, 1),
);
expect(res).toEqual([{ id: 1, name: 'John1' }]);
});
test('insert with onConflict do nothing', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable)
.values({ name: 'John' });
await db.insert(usersTable)
.values({ id: 1, name: 'John' })
.onConflictDoNothing();
const res = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where(
eq(usersTable.id, 1),
);
expect(res).toEqual([{ id: 1, name: 'John' }]);
});
test('insert with onConflict do nothing + target', async (ctx) => {
const { db } = ctx.pg;
await db.insert(usersTable)
.values({ name: 'John' });
await db.insert(usersTable)
.values({ id: 1, name: 'John' })
.onConflictDoNothing({ target: usersTable.id });
const res = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where(
eq(usersTable.id, 1),
);
expect(res).toEqual([{ id: 1, name: 'John' }]);
});
+512
View File
@@ -0,0 +1,512 @@
import retry from 'async-retry';
import { sql } from 'drizzle-orm';
import { pgTable, serial, timestamp } from 'drizzle-orm/pg-core';
import type { PgRemoteDatabase } from 'drizzle-orm/pg-proxy';
import { drizzle as proxyDrizzle } from 'drizzle-orm/pg-proxy';
import { migrate } from 'drizzle-orm/pg-proxy/migrator';
import * as pg from 'pg';
import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest';
import { skipTests } from '~/common';
import { createDockerDB, tests, usersMigratorTable, usersTable } from './pg-common';
import { TestCache, TestGlobalCache, tests as cacheTests } from './pg-common-cache';
// eslint-disable-next-line drizzle-internal/require-entity-kind
class ServerSimulator {
constructor(private db: pg.Client) {
const { types } = pg;
types.setTypeParser(types.builtins.TIMESTAMPTZ, (val) => val);
types.setTypeParser(types.builtins.TIMESTAMP, (val) => val);
types.setTypeParser(types.builtins.DATE, (val) => val);
types.setTypeParser(types.builtins.INTERVAL, (val) => val);
types.setTypeParser(1231, (val) => val);
types.setTypeParser(1115, (val) => val);
types.setTypeParser(1185, (val) => val);
types.setTypeParser(1187, (val) => val);
types.setTypeParser(1182, (val) => val);
}
async query(sql: string, params: any[], method: 'all' | 'execute') {
if (method === 'all') {
try {
const result = await this.db.query({
text: sql,
values: params,
rowMode: 'array',
});
return { data: result.rows as any };
} catch (e: any) {
return { error: e };
}
} else if (method === 'execute') {
try {
const result = await this.db.query({
text: sql,
values: params,
});
return { data: result.rows as any };
} catch (e: any) {
return { error: e };
}
} else {
return { error: 'Unknown method value' };
}
}
async migrations(queries: string[]) {
await this.db.query('BEGIN');
try {
for (const query of queries) {
await this.db.query(query);
}
await this.db.query('COMMIT');
} catch (e) {
await this.db.query('ROLLBACK');
throw e;
}
return {};
}
}
const ENABLE_LOGGING = false;
let db: PgRemoteDatabase;
let dbGlobalCached: PgRemoteDatabase;
let cachedDb: PgRemoteDatabase;
let client: pg.Client;
let serverSimulator: ServerSimulator;
beforeAll(async () => {
let connectionString;
if (process.env['PG_CONNECTION_STRING']) {
connectionString = process.env['PG_CONNECTION_STRING'];
} else {
const { connectionString: conStr } = await createDockerDB();
connectionString = conStr;
}
client = await retry(async () => {
client = new pg.Client(connectionString);
await client.connect();
return client;
}, {
retries: 20,
factor: 1,
minTimeout: 250,
maxTimeout: 250,
randomize: false,
onRetry() {
client?.end();
},
});
serverSimulator = new ServerSimulator(client);
const proxyHandler = async (sql: string, params: any[], method: any) => {
try {
const response = await serverSimulator.query(sql, params, method);
if (response.error !== undefined) {
throw response.error;
}
return { rows: response.data };
} catch (e: any) {
console.error('Error from pg proxy server:', e.message);
throw e;
}
};
db = proxyDrizzle(proxyHandler, {
logger: ENABLE_LOGGING,
});
cachedDb = proxyDrizzle(proxyHandler, { logger: ENABLE_LOGGING, cache: new TestCache() });
dbGlobalCached = proxyDrizzle(proxyHandler, { logger: ENABLE_LOGGING, cache: new TestGlobalCache() });
});
afterAll(async () => {
await client?.end();
});
beforeEach((ctx) => {
ctx.pg = {
db,
};
ctx.cachedPg = {
db: cachedDb,
dbGlobalCached,
};
});
test('migrator : default migration strategy', async () => {
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
// './drizzle2/pg-proxy/first' ??
await migrate(db, async (queries) => {
try {
await serverSimulator.migrations(queries);
} catch (e) {
console.error(e);
throw new Error('Proxy server cannot run migrations');
}
}, { migrationsFolder: './drizzle2/pg' });
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table "drizzle"."__drizzle_migrations"`);
});
test('all date and time columns without timezone first case mode string', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).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
)
`);
// 1. Insert date in string format without timezone in it
await db.insert(table).values([
{ timestamp: '2022-01-01 02:00:00.123456' },
]);
// 2, Select in string format and check that values are the same
const result = await db.select().from(table);
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456' }]);
// 3. Select as raw query and check that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result2).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('all date and time columns without timezone second case mode string', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).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
)
`);
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: '2022-01-01T02:00:00.123456-02' },
]);
// 2, Select as raw query and check that values are the same
const result = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('all date and time columns without timezone third case mode date', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: '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(3) not null
)
`);
const insertedDate = new Date('2022-01-01 20:00:00.123+04');
// 1. Insert date as new date
await db.insert(table).values([
{ timestamp: insertedDate },
]);
// 2, Select as raw query as string
const result = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3. Compare both dates using orm mapping - Need to add 'Z' to tell JS that it is UTC
expect(new Date(result[0]!.timestamp_string + 'Z').getTime()).toBe(insertedDate.getTime());
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-0200';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode date for timestamp with timezone', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'date', 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(3) with time zone not null
)
`);
const timestampString = new Date('2022-01-01 00:00:00.456-0200');
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: timestampString }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.456+00' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone in UTC timezone', async () => {
// get current timezone from db
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
// set timezone to UTC
await db.execute(sql`set time zone 'UTC'`);
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-0200';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
await db.execute(sql`set time zone '${sql.raw(timezone[0]!.TimeZone)}'`);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone in different timezone', async () => {
// get current timezone from db
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
// set timezone to HST (UTC - 10)
await db.execute(sql`set time zone '-10'`);
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-1000';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 00:00:00.123456-10' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result2).toEqual([{ id: 1, timestamp_string: '2022-01-01 00:00:00.123456-10' }]);
await db.execute(sql`set time zone '${sql.raw(timezone[0]!.TimeZone)}'`);
await db.execute(sql`drop table if exists ${table}`);
});
skipTests([
'migrator : default migration strategy',
'migrator : migrate with custom schema',
'migrator : migrate with custom table',
'migrator : migrate with custom table and custom schema',
'insert via db.execute + select via db.execute',
'insert via db.execute + returning',
'insert via db.execute w/ query builder',
'all date and time columns without timezone first case mode string',
'all date and time columns without timezone third case mode date',
'test mode string for timestamp with timezone',
'test mode date for timestamp with timezone',
'test mode string for timestamp with timezone in UTC timezone',
'test mode string for timestamp with timezone in different timezone',
'transaction',
'transaction rollback',
'nested transaction',
'nested transaction rollback',
'test $onUpdateFn and $onUpdate works updating',
]);
beforeEach(async () => {
await db.execute(sql`drop schema if exists public cascade`);
await db.execute(sql`create schema public`);
await db.execute(
sql`
create table users (
id serial primary key,
name text not null,
verified boolean not null default false,
jsonb jsonb,
created_at timestamptz not null default now()
)
`,
);
});
test('insert via db.execute + select via db.execute', async () => {
await db.execute(
sql`insert into ${usersTable} (${sql.identifier(usersTable.name.name)}) values (${'John'})`,
);
const result = await db.execute<{ id: number; name: string }>(
sql`select id, name from "users"`,
);
expect(result).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute + returning', async () => {
const inserted = await db.execute<{ id: number; name: string }>(
sql`insert into ${usersTable} (${
sql.identifier(
usersTable.name.name,
)
}) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`,
);
expect(inserted).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute w/ query builder', async () => {
const inserted = await db.execute<Pick<typeof usersTable.$inferSelect, 'id' | 'name'>>(
db
.insert(usersTable)
.values({ name: 'John' })
.returning({ id: usersTable.id, name: usersTable.name }),
);
expect(inserted).toEqual([{ id: 1, name: 'John' }]);
});
tests();
cacheTests();
+129
View File
@@ -0,0 +1,129 @@
import { PGlite } from '@electric-sql/pglite';
import { Name, sql } from 'drizzle-orm';
import { drizzle, type PgliteDatabase } from 'drizzle-orm/pglite';
import { migrate } from 'drizzle-orm/pglite/migrator';
import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest';
import { skipTests } from '~/common';
import { tests, usersMigratorTable, usersTable } from './pg-common';
import { TestCache, TestGlobalCache, tests as cacheTests } from './pg-common-cache';
const ENABLE_LOGGING = false;
let db: PgliteDatabase;
let dbGlobalCached: PgliteDatabase;
let cachedDb: PgliteDatabase;
let client: PGlite;
beforeAll(async () => {
client = new PGlite();
db = drizzle(client, { logger: ENABLE_LOGGING });
cachedDb = drizzle(client, {
logger: ENABLE_LOGGING,
cache: new TestCache(),
});
dbGlobalCached = drizzle(client, {
logger: ENABLE_LOGGING,
cache: new TestGlobalCache(),
});
});
afterAll(async () => {
await client?.close();
});
beforeEach((ctx) => {
ctx.pg = {
db,
};
ctx.cachedPg = {
db: cachedDb,
dbGlobalCached,
};
});
test('migrator : default migration strategy', async () => {
await db.execute(sql`drop table if exists all_columns`);
await db.execute(
sql`drop table if exists users12`,
);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg' });
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table "drizzle"."__drizzle_migrations"`);
});
test('insert via db.execute + select via db.execute', async () => {
await db.execute(sql`insert into ${usersTable} (${new Name(usersTable.name.name)}) values (${'John'})`);
const result = await db.execute<{ id: number; name: string }>(sql`select id, name from "users"`);
expect(Array.prototype.slice.call(result.rows)).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute + returning', async () => {
const result = await db.execute<{ id: number; name: string }>(
sql`insert into ${usersTable} (${new Name(
usersTable.name.name,
)}) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`,
);
expect(Array.prototype.slice.call(result.rows)).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute w/ query builder', async () => {
const result = await db.execute<Pick<typeof usersTable.$inferSelect, 'id' | 'name'>>(
db.insert(usersTable).values({ name: 'John' }).returning({ id: usersTable.id, name: usersTable.name }),
);
expect(Array.prototype.slice.call(result.rows)).toEqual([{ id: 1, name: 'John' }]);
});
skipTests([
'migrator : default migration strategy',
'migrator : migrate with custom schema',
'migrator : migrate with custom table',
'migrator : migrate with custom table and custom schema',
'insert via db.execute + select via db.execute',
'insert via db.execute + returning',
'insert via db.execute w/ query builder',
'all date and time columns without timezone first case mode string',
'all date and time columns without timezone third case mode date',
'test mode string for timestamp with timezone',
'test mode date for timestamp with timezone',
'test mode string for timestamp with timezone in UTC timezone',
'test mode string for timestamp with timezone in different timezone',
'view',
'materialized view',
'subquery with view',
'mySchema :: materialized view',
'select count()',
// not working in 0.2.12
'select with group by as sql + column',
'select with group by as column + sql',
'mySchema :: select with group by as column + sql',
]);
tests();
cacheTests();
beforeEach(async () => {
await db.execute(sql`drop schema if exists public cascade`);
await db.execute(sql`create schema public`);
await db.execute(
sql`
create table users (
id serial primary key,
name text not null,
verified boolean not null default false,
jsonb jsonb,
created_at timestamptz not null default now()
)
`,
);
});
@@ -0,0 +1,490 @@
import retry from 'async-retry';
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres, { type Sql } from 'postgres';
import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest';
import { Name, sql } from 'drizzle-orm';
import { pgTable, serial, timestamp } from 'drizzle-orm/pg-core';
import { migrate } from 'drizzle-orm/postgres-js/migrator';
import { skipTests } from '~/common';
import { randomString } from '~/utils';
import { createDockerDB, tests, usersMigratorTable, usersTable } from './pg-common';
import { TestCache, TestGlobalCache, tests as cacheTests } from './pg-common-cache';
const ENABLE_LOGGING = false;
let db: PostgresJsDatabase;
let dbGlobalCached: PostgresJsDatabase;
let cachedDb: PostgresJsDatabase;
let client: Sql;
beforeAll(async () => {
let connectionString;
if (process.env['PG_CONNECTION_STRING']) {
connectionString = process.env['PG_CONNECTION_STRING'];
} else {
const { connectionString: conStr } = await createDockerDB();
connectionString = conStr;
}
client = await retry(async () => {
client = postgres(connectionString, {
max: 1,
onnotice: () => {
// disable notices
},
});
await client`select 1`;
return client;
}, {
retries: 20,
factor: 1,
minTimeout: 250,
maxTimeout: 250,
randomize: false,
onRetry() {
client?.end();
},
});
db = drizzle(client, { logger: ENABLE_LOGGING });
cachedDb = drizzle(client, { logger: ENABLE_LOGGING, cache: new TestCache() });
dbGlobalCached = drizzle(client, { logger: ENABLE_LOGGING, cache: new TestGlobalCache() });
});
afterAll(async () => {
await client?.end();
});
beforeEach((ctx) => {
ctx.pg = {
db,
};
ctx.cachedPg = {
db: cachedDb,
dbGlobalCached,
};
});
test('migrator : default migration strategy', async () => {
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg' });
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table "drizzle"."__drizzle_migrations"`);
});
test('migrator : migrate with custom schema', async () => {
const customSchema = randomString();
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: customSchema });
// test if the custom migrations table was created
const { count } = await db.execute(sql`select * from ${sql.identifier(customSchema)}."__drizzle_migrations";`);
expect(count > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table ${sql.identifier(customSchema)}."__drizzle_migrations"`);
});
test('migrator : migrate with custom table', async () => {
const customTable = randomString();
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable });
// test if the custom migrations table was created
const { count } = await db.execute(sql`select * from "drizzle".${sql.identifier(customTable)};`);
expect(count > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table "drizzle".${sql.identifier(customTable)}`);
});
test('migrator : migrate with custom table and custom schema', async () => {
const customTable = randomString();
const customSchema = randomString();
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, {
migrationsFolder: './drizzle2/pg',
migrationsTable: customTable,
migrationsSchema: customSchema,
});
// test if the custom migrations table was created
const { count } = await db.execute(
sql`select * from ${sql.identifier(customSchema)}.${sql.identifier(customTable)};`,
);
expect(count > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table ${sql.identifier(customSchema)}.${sql.identifier(customTable)}`);
});
test('all date and time columns without timezone first case mode string', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).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
)
`);
// 1. Insert date in string format without timezone in it
await db.insert(table).values([
{ timestamp: '2022-01-01 02:00:00.123456' },
]);
// 2, Select in string format and check that values are the same
const result = await db.select().from(table);
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456' }]);
// 3. Select as raw query and check that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect([...result2]).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('all date and time columns without timezone second case mode string', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).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
)
`);
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: '2022-01-01T02:00:00.123456-02' },
]);
// 2, Select as raw query and check that values are the same
const result = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect([...result]).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('all date and time columns without timezone third case mode date', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: '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(3) not null
)
`);
const insertedDate = new Date('2022-01-01 20:00:00.123+04');
// 1. Insert date as new date
await db.insert(table).values([
{ timestamp: insertedDate },
]);
// 2, Select as raw query as string
const result = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3. Compare both dates using orm mapping - Need to add 'Z' to tell JS that it is UTC
expect(new Date(result[0]!.timestamp_string + 'Z').getTime()).toBe(insertedDate.getTime());
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-0200';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect([...result2]).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode date for timestamp with timezone', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'date', 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(3) with time zone not null
)
`);
const timestampString = new Date('2022-01-01 00:00:00.456-0200');
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: timestampString }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect([...result2]).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.456+00' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone in UTC timezone', async () => {
// get current timezone from db
const [timezone] = await db.execute<{ TimeZone: string }>(sql`show timezone`);
// set timezone to UTC
await db.execute(sql`set time zone 'UTC'`);
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-0200';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect([...result2]).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
await db.execute(sql`set time zone '${sql.raw(timezone!.TimeZone)}'`);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone in different timezone', async () => {
// get current timezone from db
const [timezone] = await db.execute<{ TimeZone: string }>(sql`show timezone`);
// set timezone to HST (UTC - 10)
await db.execute(sql`set time zone '-10'`);
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-1000';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 00:00:00.123456-10' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect([...result2]).toEqual([{ id: 1, timestamp_string: '2022-01-01 00:00:00.123456-10' }]);
await db.execute(sql`set time zone '${sql.raw(timezone!.TimeZone)}'`);
await db.execute(sql`drop table if exists ${table}`);
});
skipTests([
'migrator : default migration strategy',
'migrator : migrate with custom schema',
'migrator : migrate with custom table',
'migrator : migrate with custom table and custom schema',
'insert via db.execute + select via db.execute',
'insert via db.execute + returning',
'insert via db.execute w/ query builder',
'all date and time columns without timezone first case mode string',
'all date and time columns without timezone third case mode date',
'test mode string for timestamp with timezone',
'test mode date for timestamp with timezone',
'test mode string for timestamp with timezone in UTC timezone',
'test mode string for timestamp with timezone in different timezone',
]);
tests();
cacheTests();
beforeEach(async () => {
await db.execute(sql`drop schema if exists public cascade`);
await db.execute(sql`create schema public`);
await db.execute(
sql`
create table users (
id serial primary key,
name text not null,
verified boolean not null default false,
jsonb jsonb,
created_at timestamptz not null default now()
)
`,
);
});
test('insert via db.execute + select via db.execute', async () => {
await db.execute(sql`insert into ${usersTable} (${new Name(usersTable.name.name)}) values (${'John'})`);
const result = await db.execute<{ id: number; name: string }>(sql`select id, name from "users"`);
expect(Array.prototype.slice.call(result)).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute + returning', async () => {
const result = await db.execute<{ id: number; name: string }>(
sql`insert into ${usersTable} (${new Name(
usersTable.name.name,
)}) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`,
);
expect(Array.prototype.slice.call(result)).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute w/ query builder', async () => {
const result = await db.execute<Pick<typeof usersTable.$inferSelect, 'id' | 'name'>>(
db.insert(usersTable).values({ name: 'John' }).returning({ id: usersTable.id, name: usersTable.name }),
);
expect(Array.prototype.slice.call(result)).toEqual([{ id: 1, name: 'John' }]);
});
@@ -0,0 +1,16 @@
import { crudPolicy } from 'drizzle-orm/neon';
import { getTableConfig, integer, pgPolicy, pgRole, pgTable } from 'drizzle-orm/pg-core';
import { test } from 'vitest';
test.skip('getTableConfig: policies', async () => {
const schema = pgTable('hhh', {
id: integer(),
}, () => [
pgPolicy('name'),
crudPolicy({ role: pgRole('users'), read: true, modify: true }),
]);
const tc = getTableConfig(schema);
console.log(tc.policies);
});
@@ -0,0 +1,500 @@
import { createClient, type VercelClient } from '@vercel/postgres';
import { sql } from 'drizzle-orm';
import { pgTable, serial, timestamp } from 'drizzle-orm/pg-core';
import { drizzle, type VercelPgDatabase } from 'drizzle-orm/vercel-postgres';
import { migrate } from 'drizzle-orm/vercel-postgres/migrator';
import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest';
import { skipTests } from '~/common';
import { randomString } from '~/utils';
import { createDockerDB, tests, tests as cacheTests, usersMigratorTable, usersTable } from './pg-common';
import { TestCache, TestGlobalCache } from './pg-common-cache';
const ENABLE_LOGGING = false;
let db: VercelPgDatabase;
let dbGlobalCached: VercelPgDatabase;
let cachedDb: VercelPgDatabase;
let client: VercelClient;
beforeAll(async () => {
let connectionString;
if (process.env['PG_CONNECTION_STRING']) {
connectionString = process.env['PG_CONNECTION_STRING'];
} else {
const { connectionString: conStr } = await createDockerDB();
connectionString = conStr;
}
const sleep = 250;
let timeLeft = 5000;
let connected = false;
let lastError: unknown | undefined;
do {
try {
client = createClient({ connectionString });
await client.connect();
connected = true;
break;
} catch (e) {
lastError = e;
await new Promise((resolve) => setTimeout(resolve, sleep));
timeLeft -= sleep;
}
} while (timeLeft > 0);
if (!connected) {
console.log(connectionString);
console.error('Cannot connect to Postgres');
await client?.end().catch(console.error);
// await pgContainer?.stop().catch(console.error);
throw lastError;
}
db = drizzle(client, { logger: ENABLE_LOGGING });
cachedDb = drizzle(client, { logger: ENABLE_LOGGING, cache: new TestCache() });
dbGlobalCached = drizzle(client, { logger: ENABLE_LOGGING, cache: new TestGlobalCache() });
});
afterAll(async () => {
await client?.end();
});
beforeEach((ctx) => {
ctx.pg = {
db,
};
ctx.cachedPg = {
db: cachedDb,
dbGlobalCached,
};
});
test('migrator : default migration strategy', async () => {
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg' });
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table "drizzle"."__drizzle_migrations"`);
});
test('migrator : migrate with custom schema', async () => {
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: 'custom_migrations' });
// test if the custom migrations table was created
const { rowCount } = await db.execute(sql`select * from custom_migrations."__drizzle_migrations";`);
expect(rowCount && rowCount > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table custom_migrations."__drizzle_migrations"`);
});
test('migrator : migrate with custom table', async () => {
const customTable = randomString();
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable });
// test if the custom migrations table was created
const { rowCount } = await db.execute(sql`select * from "drizzle".${sql.identifier(customTable)};`);
expect(rowCount && rowCount > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table "drizzle".${sql.identifier(customTable)}`);
});
test('migrator : migrate with custom table and custom schema', async () => {
const customTable = randomString();
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, {
migrationsFolder: './drizzle2/pg',
migrationsTable: customTable,
migrationsSchema: 'custom_migrations',
});
// test if the custom migrations table was created
const { rowCount } = await db.execute(
sql`select * from custom_migrations.${sql.identifier(customTable)};`,
);
expect(rowCount && rowCount > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table custom_migrations.${sql.identifier(customTable)}`);
});
test('all date and time columns without timezone first case mode string', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).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
)
`);
// 1. Insert date in string format without timezone in it
await db.insert(table).values([
{ timestamp: '2022-01-01 02:00:00.123456' },
]);
// 2, Select in string format and check that values are the same
const result = await db.select().from(table);
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456' }]);
// 3. Select as raw query and check that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('all date and time columns without timezone second case mode string', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).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
)
`);
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: '2022-01-01T02:00:00.123456-02' },
]);
// 2, Select as raw query and check that values are the same
const result = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('all date and time columns without timezone third case mode date', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: '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(3) not null
)
`);
const insertedDate = new Date('2022-01-01 20:00:00.123+04');
// 1. Insert date as new date
await db.insert(table).values([
{ timestamp: insertedDate },
]);
// 2, Select as raw query as string
const result = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3. Compare both dates using orm mapping - Need to add 'Z' to tell JS that it is UTC
expect(new Date(result.rows[0]!.timestamp_string + 'Z').getTime()).toBe(insertedDate.getTime());
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-0200';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode date for timestamp with timezone', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'date', 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(3) with time zone not null
)
`);
const timestampString = new Date('2022-01-01 00:00:00.456-0200');
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: timestampString }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.456+00' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone in UTC timezone', async () => {
// get current timezone from db
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
// set timezone to UTC
await db.execute(sql`set time zone 'UTC'`);
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-0200';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone in different timezone', async () => {
// get current timezone from db
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
// set timezone to HST (UTC - 10)
await db.execute(sql`set time zone 'HST'`);
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-1000';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 00:00:00.123456-10' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 00:00:00.123456-10' }]);
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
await db.execute(sql`drop table if exists ${table}`);
});
skipTests([
'migrator : default migration strategy',
'migrator : migrate with custom schema',
'migrator : migrate with custom table',
'migrator : migrate with custom table and custom schema',
'insert via db.execute + select via db.execute',
'insert via db.execute + returning',
'insert via db.execute w/ query builder',
'all date and time columns without timezone first case mode string',
'all date and time columns without timezone third case mode date',
'test mode string for timestamp with timezone',
'test mode date for timestamp with timezone',
'test mode string for timestamp with timezone in UTC timezone',
'test mode string for timestamp with timezone in different timezone',
'build query insert with onConflict do nothing + target', //
'select from tables with same name from different schema using alias', //
]);
tests();
cacheTests();
beforeEach(async () => {
await db.execute(sql`drop schema if exists public cascade`);
await db.execute(sql`create schema public`);
await db.execute(
sql`
create table users (
id serial primary key,
name text not null,
verified boolean not null default false,
jsonb jsonb,
created_at timestamptz not null default now()
)
`,
);
});
test('insert via db.execute + select via db.execute', async () => {
await db.execute(
sql`insert into ${usersTable} (${sql.identifier(usersTable.name.name)}) values (${'John'})`,
);
const result = await db.execute<{ id: number; name: string }>(
sql`select id, name from "users"`,
);
expect(result.rows).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute + returning', async () => {
const inserted = await db.execute<{ id: number; name: string }>(
sql`insert into ${usersTable} (${
sql.identifier(
usersTable.name.name,
)
}) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`,
);
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute w/ query builder', async () => {
const inserted = await db.execute<Pick<typeof usersTable.$inferSelect, 'id' | 'name'>>(
db
.insert(usersTable)
.values({ name: 'John' })
.returning({ id: usersTable.id, name: usersTable.name }),
);
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
});
@@ -0,0 +1,435 @@
import retry from 'async-retry';
import { sql } from 'drizzle-orm';
import { pgTable, serial, timestamp } from 'drizzle-orm/pg-core';
import { drizzle } from 'drizzle-orm/xata-http';
import type { XataHttpClient, XataHttpDatabase } from 'drizzle-orm/xata-http';
import { migrate } from 'drizzle-orm/xata-http/migrator';
import { beforeAll, beforeEach, expect, test } from 'vitest';
import { skipTests } from '~/common';
import { randomString } from '~/utils';
import { getXataClient } from '../xata/xata.ts';
import { tests, tests as cacheTests, usersMigratorTable, usersTable } from './pg-common';
import { TestCache, TestGlobalCache } from './pg-common-cache.ts';
const ENABLE_LOGGING = false;
let db: XataHttpDatabase;
let dbGlobalCached: XataHttpDatabase;
let cachedDb: XataHttpDatabase;
let client: XataHttpClient;
beforeAll(async () => {
const apiKey = process.env['XATA_API_KEY'];
if (!apiKey) {
throw new Error('XATA_API_KEY is not defined');
}
client = await retry(async () => {
client = getXataClient();
return client;
}, {
retries: 20,
factor: 1,
minTimeout: 250,
maxTimeout: 250,
randomize: false,
});
db = drizzle(client, { logger: ENABLE_LOGGING });
cachedDb = drizzle(client, { logger: ENABLE_LOGGING, cache: new TestCache() });
dbGlobalCached = drizzle(client, { logger: ENABLE_LOGGING, cache: new TestGlobalCache() });
});
beforeEach((ctx) => {
ctx.pg = {
db,
};
ctx.cachedPg = {
db: cachedDb,
dbGlobalCached,
};
});
test('migrator : default migration strategy', async () => {
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg' });
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table "drizzle"."__drizzle_migrations"`);
});
test('migrator : migrate with custom table', async () => {
const customTable = randomString();
await db.execute(sql`drop table if exists all_columns`);
await db.execute(sql`drop table if exists users12`);
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable });
// test if the custom migrations table was created
const { records } = await db.execute(sql`select * from "drizzle".${sql.identifier(customTable)};`);
expect(records && records.length > 0).toBeTruthy();
// test if the migrated table are working as expected
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
const result = await db.select().from(usersMigratorTable);
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
await db.execute(sql`drop table all_columns`);
await db.execute(sql`drop table users12`);
await db.execute(sql`drop table "drizzle".${sql.identifier(customTable)}`);
});
test('all date and time columns without timezone first case mode string', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).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
)
`);
// 1. Insert date in string format without timezone in it
await db.insert(table).values([
{ timestamp: '2022-01-01 02:00:00.123456' },
]);
// 2, Select in string format and check that values are the same
const result = await db.select().from(table);
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456' }]);
// 3. Select as raw query and check that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result2.records).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('all date and time columns without timezone second case mode string', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).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
)
`);
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: '2022-01-01T02:00:00.123456-02' },
]);
// 2, Select as raw query and check that values are the same
const result = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result.records).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('all date and time columns without timezone third case mode date', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: '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(3) not null
)
`);
const insertedDate = new Date('2022-01-01 20:00:00.123+04');
// 1. Insert date as new date
await db.insert(table).values([
{ timestamp: insertedDate },
]);
// 2, Select as raw query as string
const result = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3. Compare both dates using orm mapping - Need to add 'Z' to tell JS that it is UTC
expect(new Date(result.records[0]!.timestamp_string + 'Z').getTime()).toBe(insertedDate.getTime());
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-0200';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2.records).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode date for timestamp with timezone', async () => {
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'date', 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(3) with time zone not null
)
`);
const timestampString = new Date('2022-01-01 00:00:00.456-0200');
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: timestampString }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2.records).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.456+00' }]);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone in UTC timezone', async () => {
// get current timezone from db
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
// set timezone to UTC
await db.execute(sql`set time zone 'UTC'`);
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-0200';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
expect(result2.records).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
await db.execute(sql`set time zone '${sql.raw(timezone.records[0]!.TimeZone)}'`);
await db.execute(sql`drop table if exists ${table}`);
});
test('test mode string for timestamp with timezone in different timezone', async () => {
// get current timezone from db
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
// set timezone to HST (UTC - 10)
await db.execute(sql`set time zone 'HST'`);
const table = pgTable('all_columns', {
id: serial('id').primaryKey(),
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).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
)
`);
const timestampString = '2022-01-01 00:00:00.123456-1000';
// 1. Insert date in string format with timezone in it
await db.insert(table).values([
{ timestamp: timestampString },
]);
// 2. Select date in string format and check that the values are the same
const result = await db.select().from(table);
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 00:00:00.123456-10' }]);
// 3. Select as raw query and checke that values are the same
const result2 = await db.execute<{
id: number;
timestamp_string: string;
}>(sql`select * from ${table}`);
expect(result2.records).toEqual([{ id: 1, timestamp_string: '2022-01-01 00:00:00.123456-10' }]);
await db.execute(sql`set time zone '${sql.raw(timezone.records[0]!.TimeZone)}'`);
await db.execute(sql`drop table if exists ${table}`);
});
skipTests([
'migrator : default migration strategy',
'migrator : migrate with custom schema',
'migrator : migrate with custom table',
'migrator : migrate with custom table and custom schema',
'insert via db.execute + select via db.execute',
'insert via db.execute + returning',
'insert via db.execute w/ query builder',
'all date and time columns without timezone first case mode string',
'all date and time columns without timezone third case mode date',
'test mode string for timestamp with timezone',
'test mode date for timestamp with timezone',
'test mode string for timestamp with timezone in UTC timezone',
'test mode string for timestamp with timezone in different timezone',
'view',
'materialized view',
'select from enum',
'subquery with view',
]);
tests();
cacheTests();
beforeEach(async () => {
await db.execute(sql`drop schema if exists public cascade`);
await db.execute(sql`create schema public`);
await db.execute(
sql`
create table users (
id serial primary key,
name text not null,
verified boolean not null default false,
jsonb jsonb,
created_at timestamptz not null default now()
)
`,
);
});
test('insert via db.execute + select via db.execute', async () => {
await db.execute(
sql`insert into ${usersTable} (${sql.identifier(usersTable.name.name)}) values (${'John'})`,
);
const result = await db.execute<{ id: number; name: string }>(
sql`select id, name from "users"`,
);
expect(result.records).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute + returning', async () => {
const inserted = await db.execute<{ id: number; name: string }>(
sql`insert into ${usersTable} (${
sql.identifier(
usersTable.name.name,
)
}) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`,
);
expect(inserted.records).toEqual([{ id: 1, name: 'John' }]);
});
test('insert via db.execute w/ query builder', async () => {
const inserted = await db.execute<Pick<typeof usersTable.$inferSelect, 'id' | 'name'>>(
db
.insert(usersTable)
.values({ name: 'John' })
.returning({ id: usersTable.id, name: usersTable.name }),
);
expect(inserted.records).toEqual([{ id: 1, name: 'John' }]);
});