Files
drizzle-team--drizzle-orm/integration-tests/tests/relational/issues-schemas/wrong-mapping/pg.test.ts
T
2026-07-13 12:10:05 +08:00

319 lines
9.3 KiB
TypeScript

import 'dotenv/config';
import Docker from 'dockerode';
import { desc, sql } from 'drizzle-orm';
import { drizzle, type NodePgDatabase } from 'drizzle-orm/node-postgres';
import getPort from 'get-port';
import pg from 'pg';
import { v4 as uuid } from 'uuid';
import { afterAll, beforeAll, beforeEach, expectTypeOf, test } from 'vitest';
import * as schema from './pg.schema.ts';
const { Client } = pg;
const ENABLE_LOGGING = false;
/*
Test cases:
- querying nested relation without PK with additional fields
*/
let pgContainer: Docker.Container;
let db: NodePgDatabase<typeof schema>;
let client: pg.Client;
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-${uuid()}`,
HostConfig: {
AutoRemove: true,
PortBindings: {
'5432/tcp': [{ HostPort: `${port}` }],
},
},
});
await pgContainer.start();
return `postgres://postgres:postgres@localhost:${port}/postgres`;
}
beforeAll(async () => {
const connectionString = process.env['PG_CONNECTION_STRING'] ?? (await createDockerDB());
const sleep = 250;
let timeLeft = 5000;
let connected = false;
let lastError: unknown | undefined;
do {
try {
client = new Client(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.error('Cannot connect to Postgres');
await client?.end().catch(console.error);
await pgContainer?.stop().catch(console.error);
throw lastError;
}
db = drizzle(client, { schema, logger: ENABLE_LOGGING });
});
afterAll(async () => {
await client?.end().catch(console.error);
await pgContainer?.stop().catch(console.error);
});
beforeEach(async () => {
await db.execute(sql`drop schema public cascade`);
await db.execute(sql`create schema public`);
await db.execute(
sql`
CREATE TABLE IF NOT EXISTS "ingredients" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
"description" text,
"image_url" text,
"in_stock" boolean DEFAULT true
);
CREATE TABLE IF NOT EXISTS "menu_item_ingredients" (
"menu_item_id" uuid NOT NULL,
"ingredient_id" uuid NOT NULL,
"order" integer DEFAULT 0
);
ALTER TABLE "menu_item_ingredients" ADD CONSTRAINT "menu_item_ingredients_menu_item_id_ingredient_id_order" PRIMARY KEY("menu_item_id","ingredient_id","order");
CREATE TABLE IF NOT EXISTS "menu_item_modifier_groups" (
"menu_item_id" uuid NOT NULL,
"modifier_group_id" uuid NOT NULL,
"order" integer DEFAULT 0
);
ALTER TABLE "menu_item_modifier_groups" ADD CONSTRAINT "menu_item_modifier_groups_menu_item_id_modifier_group_id_order" PRIMARY KEY("menu_item_id","modifier_group_id","order");
CREATE TABLE IF NOT EXISTS "menu_items" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL
);
CREATE TABLE IF NOT EXISTS "modifier_group_modifiers" (
"modifier_group_id" uuid NOT NULL,
"modifier_id" uuid NOT NULL,
"order" integer DEFAULT 0
);
ALTER TABLE "modifier_group_modifiers" ADD CONSTRAINT "modifier_group_modifiers_modifier_group_id_modifier_id_order" PRIMARY KEY("modifier_group_id","modifier_id","order");
CREATE TABLE IF NOT EXISTS "modifier_groups" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL
);
CREATE TABLE IF NOT EXISTS "modifiers" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"ingredient_id" uuid,
"item_id" uuid
);
DO $$ BEGIN
ALTER TABLE "menu_item_ingredients" ADD CONSTRAINT "menu_item_ingredients_menu_item_id_menu_items_id_fk" FOREIGN KEY ("menu_item_id") REFERENCES "menu_items"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "menu_item_ingredients" ADD CONSTRAINT "menu_item_ingredients_ingredient_id_ingredients_id_fk" FOREIGN KEY ("ingredient_id") REFERENCES "ingredients"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "menu_item_modifier_groups" ADD CONSTRAINT "menu_item_modifier_groups_menu_item_id_menu_items_id_fk" FOREIGN KEY ("menu_item_id") REFERENCES "menu_items"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "menu_item_modifier_groups" ADD CONSTRAINT "menu_item_modifier_groups_modifier_group_id_modifier_groups_id_fk" FOREIGN KEY ("modifier_group_id") REFERENCES "modifier_groups"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "modifier_group_modifiers" ADD CONSTRAINT "modifier_group_modifiers_modifier_group_id_modifier_groups_id_fk" FOREIGN KEY ("modifier_group_id") REFERENCES "modifier_groups"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "modifier_group_modifiers" ADD CONSTRAINT "modifier_group_modifiers_modifier_id_modifiers_id_fk" FOREIGN KEY ("modifier_id") REFERENCES "modifiers"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "modifiers" ADD CONSTRAINT "modifiers_ingredient_id_ingredients_id_fk" FOREIGN KEY ("ingredient_id") REFERENCES "ingredients"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "modifiers" ADD CONSTRAINT "modifiers_item_id_menu_items_id_fk" FOREIGN KEY ("item_id") REFERENCES "menu_items"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
`,
);
});
test('Simple case from GH', async () => {
const firstMenuItemId = uuid();
const secondMenuItemId = uuid();
const firstModGroupsId = uuid();
const secondModGroupsId = uuid();
await db.insert(schema.menuItems).values([{ id: firstMenuItemId }, { id: secondMenuItemId }]);
await db.insert(schema.modifierGroups).values([{ id: firstModGroupsId }, { id: secondModGroupsId }]);
await db.insert(schema.menuItemModifierGroups).values([{
modifierGroupId: firstModGroupsId,
menuItemId: firstMenuItemId,
}, {
modifierGroupId: firstModGroupsId,
menuItemId: secondMenuItemId,
}, {
modifierGroupId: secondModGroupsId,
menuItemId: firstMenuItemId,
}]);
const firstIngredientId = uuid();
const secondIngredientId = uuid();
await db.insert(schema.ingredients).values([{
id: firstIngredientId,
name: 'first',
}, {
id: secondIngredientId,
name: 'second',
}]);
const firstModifierId = uuid();
const secondModifierId = uuid();
await db.insert(schema.modifiers).values([{
id: firstModifierId,
ingredientId: firstIngredientId,
itemId: firstMenuItemId,
}, {
id: secondModifierId,
ingredientId: secondIngredientId,
itemId: secondMenuItemId,
}]);
await db.insert(schema.modifierGroupModifiers).values([
{
modifierGroupId: firstModGroupsId,
modifierId: firstModifierId,
},
{
modifierGroupId: secondModGroupsId,
modifierId: secondModifierId,
},
]);
const response = await db.query.menuItems
.findMany({
with: {
modifierGroups: {
with: {
modifierGroup: {
with: {
modifiers: {
with: {
modifier: {
with: {
ingredient: true,
item: true,
},
},
},
orderBy: desc(schema.modifierGroupModifiers.order),
},
},
},
},
orderBy: schema.menuItemModifierGroups.order,
},
},
});
expectTypeOf(response).toEqualTypeOf<
{
id: string;
modifierGroups: {
menuItemId: string;
modifierGroupId: string;
order: number | null;
modifierGroup: {
id: string;
modifiers: {
modifierGroupId: string;
order: number | null;
modifierId: string;
modifier: {
id: string;
ingredientId: string | null;
itemId: string | null;
ingredient: {
id: string;
name: string;
description: string | null;
imageUrl: string | null;
inStock: boolean | null;
} | null;
item: {
id: string;
} | null;
};
}[];
};
}[];
}[]
>();
// TODO: don't rely on items order
// expect(response.length).eq(2);
// expect(response[0]?.modifierGroups.length).eq(1);
// expect(response[0]?.modifierGroups[0]?.modifierGroup.modifiers.length).eq(1);
// TODO: add correct IDs
// expect(response[0]?.modifierGroups[0]?.modifierGroup.modifiers[0]?.modifier.ingredient?.id).eq(
// '0b2b9abc-5975-4a1d-ba3d-6fc3b3149902',
// );
// expect(response[0]?.modifierGroups[0]?.modifierGroup.modifiers[0]?.modifier.item?.id).eq(
// 'a867133e-60b7-4003-aaa0-deeefad7e518',
// );
});