chore: import upstream snapshot with attribution
This commit is contained in:
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' }]);
|
||||
});
|
||||
@@ -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();
|
||||
@@ -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' }]);
|
||||
});
|
||||
Reference in New Issue
Block a user