feat(FN-193): rewrite migrate.ts with hash-based runner (+4 more)
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Commits merged: - docs(FN-193): update migration runner docs - fix(FN-193): lint/typecheck fixes - test(FN-193): rewrite migrate tests for runMigrations - feat(FN-193): make baseline migration idempotent (IF NOT EXISTS) - feat(FN-193): rewrite migrate.ts with hash-based runner Files changed: apps/api/drizzle/0000_brief_guardian.sql | 364 ++++++++++++------------ apps/api/src/database/__tests__/migrate.spec.ts | 354 ++++++++++++----------- apps/api/src/database/migrate.ts | 173 +++++------ docs/INDEX.md | 17 +- 4 files changed, 466 insertions(+), 442 deletions(-) Fusion-Task-Id: FN-193
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
CREATE TABLE "accounts" (
|
||||
CREATE TABLE IF NOT EXISTS "accounts" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"account_id" text NOT NULL,
|
||||
@@ -14,7 +14,7 @@ CREATE TABLE "accounts" (
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "brands" (
|
||||
CREATE TABLE IF NOT EXISTS "brands" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"name" varchar(100) NOT NULL,
|
||||
"slug" varchar(100) NOT NULL,
|
||||
@@ -24,7 +24,7 @@ CREATE TABLE "brands" (
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "catalog_vehicles" (
|
||||
CREATE TABLE IF NOT EXISTS "catalog_vehicles" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"source" varchar(20) DEFAULT 'pl24' NOT NULL,
|
||||
"service_name" varchar(100) NOT NULL,
|
||||
@@ -45,7 +45,7 @@ CREATE TABLE "catalog_vehicles" (
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "categories" (
|
||||
CREATE TABLE IF NOT EXISTS "categories" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"vehicle_id" uuid,
|
||||
"catalog_vehicle_id" uuid,
|
||||
@@ -60,7 +60,7 @@ CREATE TABLE "categories" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "changelog_entries" (
|
||||
CREATE TABLE IF NOT EXISTS "changelog_entries" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"stage" varchar(10) DEFAULT 'prod' NOT NULL,
|
||||
"title" varchar(255) NOT NULL,
|
||||
@@ -70,7 +70,7 @@ CREATE TABLE "changelog_entries" (
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_category_translations" (
|
||||
CREATE TABLE IF NOT EXISTS "emex_category_translations" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"original_name" varchar(500) NOT NULL,
|
||||
"translated_name" varchar(500) NOT NULL,
|
||||
@@ -79,7 +79,7 @@ CREATE TABLE "emex_category_translations" (
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "oem_code_copies" (
|
||||
CREATE TABLE IF NOT EXISTS "oem_code_copies" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"oem_code" varchar(100) NOT NULL,
|
||||
@@ -89,7 +89,7 @@ CREATE TABLE "oem_code_copies" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "parts" (
|
||||
CREATE TABLE IF NOT EXISTS "parts" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"vehicle_id" uuid,
|
||||
"catalog_vehicle_id" uuid,
|
||||
@@ -111,7 +111,7 @@ CREATE TABLE "parts" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "password_reset_tokens" (
|
||||
CREATE TABLE IF NOT EXISTS "password_reset_tokens" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"token" text NOT NULL,
|
||||
@@ -120,7 +120,7 @@ CREATE TABLE "password_reset_tokens" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "payments" (
|
||||
CREATE TABLE IF NOT EXISTS "payments" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"subscription_id" uuid NOT NULL,
|
||||
@@ -135,7 +135,7 @@ CREATE TABLE "payments" (
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "plans" (
|
||||
CREATE TABLE IF NOT EXISTS "plans" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"name" varchar(100) NOT NULL,
|
||||
"brand_count" integer NOT NULL,
|
||||
@@ -146,7 +146,7 @@ CREATE TABLE "plans" (
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "query_logs" (
|
||||
CREATE TABLE IF NOT EXISTS "query_logs" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"vin" varchar(17) NOT NULL,
|
||||
@@ -158,7 +158,7 @@ CREATE TABLE "query_logs" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "referrals" (
|
||||
CREATE TABLE IF NOT EXISTS "referrals" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"referrer_id" uuid NOT NULL,
|
||||
"referred_id" uuid NOT NULL,
|
||||
@@ -166,7 +166,7 @@ CREATE TABLE "referrals" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "schema_pics" (
|
||||
CREATE TABLE IF NOT EXISTS "schema_pics" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"category_id" uuid NOT NULL,
|
||||
"image_url" text NOT NULL,
|
||||
@@ -175,7 +175,7 @@ CREATE TABLE "schema_pics" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "sessions" (
|
||||
CREATE TABLE IF NOT EXISTS "sessions" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"token" text NOT NULL,
|
||||
@@ -186,7 +186,7 @@ CREATE TABLE "sessions" (
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user_brands" (
|
||||
CREATE TABLE IF NOT EXISTS "user_brands" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"subscription_id" uuid NOT NULL,
|
||||
@@ -194,7 +194,7 @@ CREATE TABLE "user_brands" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user_subscriptions" (
|
||||
CREATE TABLE IF NOT EXISTS "user_subscriptions" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"plan_id" uuid NOT NULL,
|
||||
@@ -207,7 +207,7 @@ CREATE TABLE "user_subscriptions" (
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user_vehicles" (
|
||||
CREATE TABLE IF NOT EXISTS "user_vehicles" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"vehicle_id" uuid NOT NULL,
|
||||
@@ -215,7 +215,7 @@ CREATE TABLE "user_vehicles" (
|
||||
"last_accessed_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "users" (
|
||||
CREATE TABLE IF NOT EXISTS "users" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"email" varchar(255) NOT NULL,
|
||||
@@ -228,7 +228,7 @@ CREATE TABLE "users" (
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "vehicles" (
|
||||
CREATE TABLE IF NOT EXISTS "vehicles" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"vin" varchar(17) NOT NULL,
|
||||
"brand_id" uuid,
|
||||
@@ -245,7 +245,7 @@ CREATE TABLE "vehicles" (
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "verifications" (
|
||||
CREATE TABLE IF NOT EXISTS "verifications" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"identifier" text NOT NULL,
|
||||
"value" text NOT NULL,
|
||||
@@ -254,7 +254,7 @@ CREATE TABLE "verifications" (
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_catalogs" (
|
||||
CREATE TABLE IF NOT EXISTS "emex_catalogs" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"catalog_id" varchar(100) NOT NULL,
|
||||
"code" varchar(50),
|
||||
@@ -264,7 +264,7 @@ CREATE TABLE "emex_catalogs" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_part_groups" (
|
||||
CREATE TABLE IF NOT EXISTS "emex_part_groups" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"emex_catalog_id" uuid,
|
||||
"group_id" varchar(100) NOT NULL,
|
||||
@@ -278,7 +278,7 @@ CREATE TABLE "emex_part_groups" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_part_images" (
|
||||
CREATE TABLE IF NOT EXISTS "emex_part_images" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"emex_part_id" uuid,
|
||||
"image_url" text NOT NULL,
|
||||
@@ -286,7 +286,7 @@ CREATE TABLE "emex_part_images" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_part_numbers" (
|
||||
CREATE TABLE IF NOT EXISTS "emex_part_numbers" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"emex_part_id" uuid,
|
||||
"oem_code" varchar(100) NOT NULL,
|
||||
@@ -294,7 +294,7 @@ CREATE TABLE "emex_part_numbers" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_parts" (
|
||||
CREATE TABLE IF NOT EXISTS "emex_parts" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"emex_catalog_id" uuid,
|
||||
"group_id" uuid,
|
||||
@@ -310,7 +310,7 @@ CREATE TABLE "emex_parts" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_schema_pics" (
|
||||
CREATE TABLE IF NOT EXISTS "emex_schema_pics" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"group_id" uuid,
|
||||
"image_url" text,
|
||||
@@ -323,7 +323,7 @@ CREATE TABLE "emex_schema_pics" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_scrape_sessions" (
|
||||
CREATE TABLE IF NOT EXISTS "emex_scrape_sessions" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"vin" varchar(17) NOT NULL,
|
||||
"status" varchar(20) DEFAULT 'pending' NOT NULL,
|
||||
@@ -335,7 +335,7 @@ CREATE TABLE "emex_scrape_sessions" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_vehicle_group_links" (
|
||||
CREATE TABLE IF NOT EXISTS "emex_vehicle_group_links" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"emex_vehicle_id" uuid NOT NULL,
|
||||
"emex_group_id" uuid NOT NULL,
|
||||
@@ -343,7 +343,7 @@ CREATE TABLE "emex_vehicle_group_links" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_vehicle_part_links" (
|
||||
CREATE TABLE IF NOT EXISTS "emex_vehicle_part_links" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"emex_vehicle_id" uuid NOT NULL,
|
||||
"emex_part_id" uuid NOT NULL,
|
||||
@@ -353,14 +353,14 @@ CREATE TABLE "emex_vehicle_part_links" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_vehicle_vins" (
|
||||
CREATE TABLE IF NOT EXISTS "emex_vehicle_vins" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"emex_vehicle_id" uuid,
|
||||
"vin" varchar(17) NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_vehicles" (
|
||||
CREATE TABLE IF NOT EXISTS "emex_vehicles" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"catalog_id" uuid,
|
||||
"vehicle_id" varchar(100) NOT NULL,
|
||||
@@ -381,7 +381,7 @@ CREATE TABLE "emex_vehicles" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pcat_part_groups" (
|
||||
CREATE TABLE IF NOT EXISTS "pcat_part_groups" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"catalog_id" text NOT NULL,
|
||||
"parent_id" text,
|
||||
@@ -392,7 +392,7 @@ CREATE TABLE "pcat_part_groups" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pcat_parts" (
|
||||
CREATE TABLE IF NOT EXISTS "pcat_parts" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"notice" text,
|
||||
@@ -400,7 +400,7 @@ CREATE TABLE "pcat_parts" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pcat_schema_pics" (
|
||||
CREATE TABLE IF NOT EXISTS "pcat_schema_pics" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"group_id" text NOT NULL,
|
||||
"car_id" text NOT NULL,
|
||||
@@ -410,7 +410,7 @@ CREATE TABLE "pcat_schema_pics" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pcat_vehicles" (
|
||||
CREATE TABLE IF NOT EXISTS "pcat_vehicles" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"catalog_id" text NOT NULL,
|
||||
"vin" varchar(17),
|
||||
@@ -421,7 +421,7 @@ CREATE TABLE "pcat_vehicles" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_catalogs" (
|
||||
CREATE TABLE IF NOT EXISTS "pl24_catalogs" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"catalog_id" varchar(100) NOT NULL,
|
||||
"brand_name" varchar(100) NOT NULL,
|
||||
@@ -429,7 +429,7 @@ CREATE TABLE "pl24_catalogs" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_part_groups" (
|
||||
CREATE TABLE IF NOT EXISTS "pl24_part_groups" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"pl24_vehicle_id" uuid,
|
||||
"group_id" varchar(100) NOT NULL,
|
||||
@@ -439,7 +439,7 @@ CREATE TABLE "pl24_part_groups" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_part_images" (
|
||||
CREATE TABLE IF NOT EXISTS "pl24_part_images" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"pl24_part_id" uuid,
|
||||
"image_url" text NOT NULL,
|
||||
@@ -447,7 +447,7 @@ CREATE TABLE "pl24_part_images" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_part_numbers" (
|
||||
CREATE TABLE IF NOT EXISTS "pl24_part_numbers" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"pl24_part_id" uuid,
|
||||
"oem_code" varchar(100) NOT NULL,
|
||||
@@ -455,7 +455,7 @@ CREATE TABLE "pl24_part_numbers" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_parts" (
|
||||
CREATE TABLE IF NOT EXISTS "pl24_parts" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"pl24_vehicle_id" uuid,
|
||||
"group_id" uuid,
|
||||
@@ -469,7 +469,7 @@ CREATE TABLE "pl24_parts" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_schema_pics" (
|
||||
CREATE TABLE IF NOT EXISTS "pl24_schema_pics" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"group_id" uuid,
|
||||
"image_url" text NOT NULL,
|
||||
@@ -480,7 +480,7 @@ CREATE TABLE "pl24_schema_pics" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_vehicle_groups" (
|
||||
CREATE TABLE IF NOT EXISTS "pl24_vehicle_groups" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"catalog_id" uuid,
|
||||
"group_id" varchar(100) NOT NULL,
|
||||
@@ -489,7 +489,7 @@ CREATE TABLE "pl24_vehicle_groups" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_vehicle_parts" (
|
||||
CREATE TABLE IF NOT EXISTS "pl24_vehicle_parts" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"pl24_vehicle_id" uuid,
|
||||
"pl24_part_id" uuid,
|
||||
@@ -497,14 +497,14 @@ CREATE TABLE "pl24_vehicle_parts" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_vehicle_vins" (
|
||||
CREATE TABLE IF NOT EXISTS "pl24_vehicle_vins" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"pl24_vehicle_id" uuid,
|
||||
"vin" varchar(17) NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_vehicles" (
|
||||
CREATE TABLE IF NOT EXISTS "pl24_vehicles" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"catalog_id" uuid,
|
||||
"vehicle_id" varchar(100) NOT NULL,
|
||||
@@ -520,139 +520,139 @@ CREATE TABLE "pl24_vehicles" (
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "catalog_vehicles" ADD CONSTRAINT "catalog_vehicles_brand_id_brands_id_fk" FOREIGN KEY ("brand_id") REFERENCES "public"."brands"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "categories" ADD CONSTRAINT "categories_vehicle_id_vehicles_id_fk" FOREIGN KEY ("vehicle_id") REFERENCES "public"."vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "categories" ADD CONSTRAINT "categories_catalog_vehicle_id_catalog_vehicles_id_fk" FOREIGN KEY ("catalog_vehicle_id") REFERENCES "public"."catalog_vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "oem_code_copies" ADD CONSTRAINT "oem_code_copies_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "parts" ADD CONSTRAINT "parts_vehicle_id_vehicles_id_fk" FOREIGN KEY ("vehicle_id") REFERENCES "public"."vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "parts" ADD CONSTRAINT "parts_catalog_vehicle_id_catalog_vehicles_id_fk" FOREIGN KEY ("catalog_vehicle_id") REFERENCES "public"."catalog_vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "parts" ADD CONSTRAINT "parts_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."categories"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "password_reset_tokens" ADD CONSTRAINT "password_reset_tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "payments" ADD CONSTRAINT "payments_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "payments" ADD CONSTRAINT "payments_subscription_id_user_subscriptions_id_fk" FOREIGN KEY ("subscription_id") REFERENCES "public"."user_subscriptions"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "query_logs" ADD CONSTRAINT "query_logs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "query_logs" ADD CONSTRAINT "query_logs_brand_id_brands_id_fk" FOREIGN KEY ("brand_id") REFERENCES "public"."brands"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "referrals" ADD CONSTRAINT "referrals_referrer_id_users_id_fk" FOREIGN KEY ("referrer_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "referrals" ADD CONSTRAINT "referrals_referred_id_users_id_fk" FOREIGN KEY ("referred_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "schema_pics" ADD CONSTRAINT "schema_pics_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."categories"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_brands" ADD CONSTRAINT "user_brands_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_brands" ADD CONSTRAINT "user_brands_subscription_id_user_subscriptions_id_fk" FOREIGN KEY ("subscription_id") REFERENCES "public"."user_subscriptions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_brands" ADD CONSTRAINT "user_brands_brand_id_brands_id_fk" FOREIGN KEY ("brand_id") REFERENCES "public"."brands"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_subscriptions" ADD CONSTRAINT "user_subscriptions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_subscriptions" ADD CONSTRAINT "user_subscriptions_plan_id_plans_id_fk" FOREIGN KEY ("plan_id") REFERENCES "public"."plans"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_vehicles" ADD CONSTRAINT "user_vehicles_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_vehicles" ADD CONSTRAINT "user_vehicles_vehicle_id_vehicles_id_fk" FOREIGN KEY ("vehicle_id") REFERENCES "public"."vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "vehicles" ADD CONSTRAINT "vehicles_brand_id_brands_id_fk" FOREIGN KEY ("brand_id") REFERENCES "public"."brands"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_part_groups" ADD CONSTRAINT "emex_part_groups_emex_catalog_id_emex_catalogs_id_fk" FOREIGN KEY ("emex_catalog_id") REFERENCES "public"."emex_catalogs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_part_images" ADD CONSTRAINT "emex_part_images_emex_part_id_emex_parts_id_fk" FOREIGN KEY ("emex_part_id") REFERENCES "public"."emex_parts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_part_numbers" ADD CONSTRAINT "emex_part_numbers_emex_part_id_emex_parts_id_fk" FOREIGN KEY ("emex_part_id") REFERENCES "public"."emex_parts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_parts" ADD CONSTRAINT "emex_parts_emex_catalog_id_emex_catalogs_id_fk" FOREIGN KEY ("emex_catalog_id") REFERENCES "public"."emex_catalogs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_parts" ADD CONSTRAINT "emex_parts_group_id_emex_part_groups_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."emex_part_groups"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_schema_pics" ADD CONSTRAINT "emex_schema_pics_group_id_emex_part_groups_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."emex_part_groups"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_vehicle_group_links" ADD CONSTRAINT "emex_vehicle_group_links_emex_vehicle_id_emex_vehicles_id_fk" FOREIGN KEY ("emex_vehicle_id") REFERENCES "public"."emex_vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_vehicle_group_links" ADD CONSTRAINT "emex_vehicle_group_links_emex_group_id_emex_part_groups_id_fk" FOREIGN KEY ("emex_group_id") REFERENCES "public"."emex_part_groups"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_vehicle_part_links" ADD CONSTRAINT "emex_vehicle_part_links_emex_vehicle_id_emex_vehicles_id_fk" FOREIGN KEY ("emex_vehicle_id") REFERENCES "public"."emex_vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_vehicle_part_links" ADD CONSTRAINT "emex_vehicle_part_links_emex_part_id_emex_parts_id_fk" FOREIGN KEY ("emex_part_id") REFERENCES "public"."emex_parts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_vehicle_part_links" ADD CONSTRAINT "emex_vehicle_part_links_emex_group_id_emex_part_groups_id_fk" FOREIGN KEY ("emex_group_id") REFERENCES "public"."emex_part_groups"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_vehicle_vins" ADD CONSTRAINT "emex_vehicle_vins_emex_vehicle_id_emex_vehicles_id_fk" FOREIGN KEY ("emex_vehicle_id") REFERENCES "public"."emex_vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_vehicles" ADD CONSTRAINT "emex_vehicles_catalog_id_emex_catalogs_id_fk" FOREIGN KEY ("catalog_id") REFERENCES "public"."emex_catalogs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_part_groups" ADD CONSTRAINT "pl24_part_groups_pl24_vehicle_id_pl24_vehicles_id_fk" FOREIGN KEY ("pl24_vehicle_id") REFERENCES "public"."pl24_vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_part_images" ADD CONSTRAINT "pl24_part_images_pl24_part_id_pl24_parts_id_fk" FOREIGN KEY ("pl24_part_id") REFERENCES "public"."pl24_parts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_part_numbers" ADD CONSTRAINT "pl24_part_numbers_pl24_part_id_pl24_parts_id_fk" FOREIGN KEY ("pl24_part_id") REFERENCES "public"."pl24_parts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_parts" ADD CONSTRAINT "pl24_parts_pl24_vehicle_id_pl24_vehicles_id_fk" FOREIGN KEY ("pl24_vehicle_id") REFERENCES "public"."pl24_vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_parts" ADD CONSTRAINT "pl24_parts_group_id_pl24_part_groups_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."pl24_part_groups"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_schema_pics" ADD CONSTRAINT "pl24_schema_pics_group_id_pl24_part_groups_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."pl24_part_groups"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_vehicle_groups" ADD CONSTRAINT "pl24_vehicle_groups_catalog_id_pl24_catalogs_id_fk" FOREIGN KEY ("catalog_id") REFERENCES "public"."pl24_catalogs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_vehicle_parts" ADD CONSTRAINT "pl24_vehicle_parts_pl24_vehicle_id_pl24_vehicles_id_fk" FOREIGN KEY ("pl24_vehicle_id") REFERENCES "public"."pl24_vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_vehicle_parts" ADD CONSTRAINT "pl24_vehicle_parts_pl24_part_id_pl24_parts_id_fk" FOREIGN KEY ("pl24_part_id") REFERENCES "public"."pl24_parts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_vehicle_vins" ADD CONSTRAINT "pl24_vehicle_vins_pl24_vehicle_id_pl24_vehicles_id_fk" FOREIGN KEY ("pl24_vehicle_id") REFERENCES "public"."pl24_vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_vehicles" ADD CONSTRAINT "pl24_vehicles_catalog_id_pl24_catalogs_id_fk" FOREIGN KEY ("catalog_id") REFERENCES "public"."pl24_catalogs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "accounts_user_id_idx" ON "accounts" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "brands_slug_idx" ON "brands" USING btree ("slug");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "catalog_vehicles_source_svc_vid_idx" ON "catalog_vehicles" USING btree ("source","service_name","service_vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "catalog_vehicles_brand_name_idx" ON "catalog_vehicles" USING btree ("brand_name");--> statement-breakpoint
|
||||
CREATE INDEX "catalog_vehicles_service_name_idx" ON "catalog_vehicles" USING btree ("service_name");--> statement-breakpoint
|
||||
CREATE INDEX "categories_vehicle_id_idx" ON "categories" USING btree ("vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "categories_catalog_vehicle_id_idx" ON "categories" USING btree ("catalog_vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "categories_parent_id_idx" ON "categories" USING btree ("parent_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "categories_vehicle_name_source_idx" ON "categories" USING btree ("vehicle_id","catalog_vehicle_id","name","source");--> statement-breakpoint
|
||||
CREATE INDEX "changelog_entries_published_at_idx" ON "changelog_entries" USING btree ("published_at");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "emex_translations_original_name_idx" ON "emex_category_translations" USING btree ("original_name");--> statement-breakpoint
|
||||
CREATE INDEX "oem_code_copies_user_id_created_at_idx" ON "oem_code_copies" USING btree ("user_id","created_at");--> statement-breakpoint
|
||||
CREATE INDEX "oem_code_copies_oem_code_idx" ON "oem_code_copies" USING btree ("oem_code");--> statement-breakpoint
|
||||
CREATE INDEX "oem_code_copies_created_at_idx" ON "oem_code_copies" USING btree ("created_at");--> statement-breakpoint
|
||||
CREATE INDEX "parts_vehicle_id_idx" ON "parts" USING btree ("vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "parts_catalog_vehicle_id_idx" ON "parts" USING btree ("catalog_vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "parts_category_id_idx" ON "parts" USING btree ("category_id");--> statement-breakpoint
|
||||
CREATE INDEX "parts_oem_code_idx" ON "parts" USING btree ("oem_code");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "password_reset_tokens_token_idx" ON "password_reset_tokens" USING btree ("token");--> statement-breakpoint
|
||||
CREATE INDEX "payments_user_id_idx" ON "payments" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "payments_status_idx" ON "payments" USING btree ("status");--> statement-breakpoint
|
||||
CREATE INDEX "query_logs_user_id_created_at_idx" ON "query_logs" USING btree ("user_id","created_at");--> statement-breakpoint
|
||||
CREATE INDEX "query_logs_vin_idx" ON "query_logs" USING btree ("vin");--> statement-breakpoint
|
||||
CREATE INDEX "referrals_referrer_id_idx" ON "referrals" USING btree ("referrer_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "referrals_referred_id_idx" ON "referrals" USING btree ("referred_id");--> statement-breakpoint
|
||||
CREATE INDEX "schema_pics_category_id_idx" ON "schema_pics" USING btree ("category_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "sessions_token_idx" ON "sessions" USING btree ("token");--> statement-breakpoint
|
||||
CREATE INDEX "user_brands_user_id_idx" ON "user_brands" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "user_brands_unique_idx" ON "user_brands" USING btree ("user_id","subscription_id","brand_id");--> statement-breakpoint
|
||||
CREATE INDEX "user_subscriptions_user_id_idx" ON "user_subscriptions" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "user_subscriptions_status_idx" ON "user_subscriptions" USING btree ("status");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "user_vehicles_user_vehicle_idx" ON "user_vehicles" USING btree ("user_id","vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "user_vehicles_user_id_idx" ON "user_vehicles" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "users_email_idx" ON "users" USING btree ("email");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "users_referral_code_idx" ON "users" USING btree ("referral_code");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "vehicles_vin_unique_idx" ON "vehicles" USING btree ("vin");--> statement-breakpoint
|
||||
CREATE INDEX "verifications_identifier_idx" ON "verifications" USING btree ("identifier");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "emex_catalogs_catalog_id_idx" ON "emex_catalogs" USING btree ("catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_catalogs_code_idx" ON "emex_catalogs" USING btree ("code");--> statement-breakpoint
|
||||
CREATE INDEX "emex_part_groups_catalog_id_idx" ON "emex_part_groups" USING btree ("emex_catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_part_groups_group_id_idx" ON "emex_part_groups" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "emex_part_groups_catalog_group_idx" ON "emex_part_groups" USING btree ("emex_catalog_id","group_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_part_images_part_id_idx" ON "emex_part_images" USING btree ("emex_part_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_part_numbers_part_id_idx" ON "emex_part_numbers" USING btree ("emex_part_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_part_numbers_oem_code_idx" ON "emex_part_numbers" USING btree ("oem_code");--> statement-breakpoint
|
||||
CREATE INDEX "emex_parts_catalog_id_idx" ON "emex_parts" USING btree ("emex_catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_parts_group_id_idx" ON "emex_parts" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_parts_part_number_idx" ON "emex_parts" USING btree ("part_number");--> statement-breakpoint
|
||||
CREATE INDEX "emex_parts_oem_number_idx" ON "emex_parts" USING btree ("oem_number");--> statement-breakpoint
|
||||
CREATE INDEX "emex_schema_pics_group_id_idx" ON "emex_schema_pics" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "emex_schema_pics_group_path_idx" ON "emex_schema_pics" USING btree ("group_id","local_path");--> statement-breakpoint
|
||||
CREATE INDEX "emex_scrape_sessions_vin_idx" ON "emex_scrape_sessions" USING btree ("vin");--> statement-breakpoint
|
||||
CREATE INDEX "emex_scrape_sessions_status_idx" ON "emex_scrape_sessions" USING btree ("status");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "emex_vgl_vehicle_group_idx" ON "emex_vehicle_group_links" USING btree ("emex_vehicle_id","emex_group_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_vgl_group_idx" ON "emex_vehicle_group_links" USING btree ("emex_group_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "emex_vpl_vehicle_part_group_idx" ON "emex_vehicle_part_links" USING btree ("emex_vehicle_id","emex_part_id","emex_group_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_vpl_part_idx" ON "emex_vehicle_part_links" USING btree ("emex_part_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_vpl_group_idx" ON "emex_vehicle_part_links" USING btree ("emex_group_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "emex_vehicle_vins_vin_idx" ON "emex_vehicle_vins" USING btree ("vin");--> statement-breakpoint
|
||||
CREATE INDEX "emex_vehicle_vins_vehicle_id_idx" ON "emex_vehicle_vins" USING btree ("emex_vehicle_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "emex_vehicles_vehicle_id_idx" ON "emex_vehicles" USING btree ("vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_vehicles_catalog_id_idx" ON "emex_vehicles" USING btree ("catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_vehicles_name_idx" ON "emex_vehicles" USING btree ("name");--> statement-breakpoint
|
||||
CREATE INDEX "emex_vehicles_source_id_idx" ON "emex_vehicles" USING btree ("source_id");--> statement-breakpoint
|
||||
CREATE INDEX "pcat_part_groups_catalog_id_idx" ON "pcat_part_groups" USING btree ("catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX "pcat_part_groups_parent_id_idx" ON "pcat_part_groups" USING btree ("parent_id");--> statement-breakpoint
|
||||
CREATE INDEX "pcat_parts_name_idx" ON "pcat_parts" USING btree ("name");--> statement-breakpoint
|
||||
CREATE INDEX "pcat_schema_pics_group_car_idx" ON "pcat_schema_pics" USING btree ("group_id","car_id");--> statement-breakpoint
|
||||
CREATE INDEX "pcat_vehicles_vin_idx" ON "pcat_vehicles" USING btree ("vin");--> statement-breakpoint
|
||||
CREATE INDEX "pcat_vehicles_catalog_id_idx" ON "pcat_vehicles" USING btree ("catalog_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "pl24_catalogs_catalog_id_idx" ON "pl24_catalogs" USING btree ("catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_part_groups_vehicle_id_idx" ON "pl24_part_groups" USING btree ("pl24_vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_part_groups_group_id_idx" ON "pl24_part_groups" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_part_images_part_id_idx" ON "pl24_part_images" USING btree ("pl24_part_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_part_numbers_part_id_idx" ON "pl24_part_numbers" USING btree ("pl24_part_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_part_numbers_oem_code_idx" ON "pl24_part_numbers" USING btree ("oem_code");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_parts_vehicle_id_idx" ON "pl24_parts" USING btree ("pl24_vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_parts_group_id_idx" ON "pl24_parts" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_schema_pics_group_id_idx" ON "pl24_schema_pics" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_vehicle_groups_catalog_id_idx" ON "pl24_vehicle_groups" USING btree ("catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_vehicle_groups_group_id_idx" ON "pl24_vehicle_groups" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_vehicle_parts_vehicle_id_idx" ON "pl24_vehicle_parts" USING btree ("pl24_vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_vehicle_parts_part_id_idx" ON "pl24_vehicle_parts" USING btree ("pl24_part_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_vehicle_vins_vin_idx" ON "pl24_vehicle_vins" USING btree ("vin");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_vehicle_vins_vehicle_id_idx" ON "pl24_vehicle_vins" USING btree ("pl24_vehicle_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "pl24_vehicles_vehicle_id_idx" ON "pl24_vehicles" USING btree ("vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_vehicles_catalog_id_idx" ON "pl24_vehicles" USING btree ("catalog_id");
|
||||
DO $$ BEGIN ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "catalog_vehicles" ADD CONSTRAINT "catalog_vehicles_brand_id_brands_id_fk" FOREIGN KEY ("brand_id") REFERENCES "public"."brands"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "categories" ADD CONSTRAINT "categories_vehicle_id_vehicles_id_fk" FOREIGN KEY ("vehicle_id") REFERENCES "public"."vehicles"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "categories" ADD CONSTRAINT "categories_catalog_vehicle_id_catalog_vehicles_id_fk" FOREIGN KEY ("catalog_vehicle_id") REFERENCES "public"."catalog_vehicles"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "oem_code_copies" ADD CONSTRAINT "oem_code_copies_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "parts" ADD CONSTRAINT "parts_vehicle_id_vehicles_id_fk" FOREIGN KEY ("vehicle_id") REFERENCES "public"."vehicles"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "parts" ADD CONSTRAINT "parts_catalog_vehicle_id_catalog_vehicles_id_fk" FOREIGN KEY ("catalog_vehicle_id") REFERENCES "public"."catalog_vehicles"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "parts" ADD CONSTRAINT "parts_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."categories"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "password_reset_tokens" ADD CONSTRAINT "password_reset_tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "payments" ADD CONSTRAINT "payments_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "payments" ADD CONSTRAINT "payments_subscription_id_user_subscriptions_id_fk" FOREIGN KEY ("subscription_id") REFERENCES "public"."user_subscriptions"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "query_logs" ADD CONSTRAINT "query_logs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "query_logs" ADD CONSTRAINT "query_logs_brand_id_brands_id_fk" FOREIGN KEY ("brand_id") REFERENCES "public"."brands"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "referrals" ADD CONSTRAINT "referrals_referrer_id_users_id_fk" FOREIGN KEY ("referrer_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "referrals" ADD CONSTRAINT "referrals_referred_id_users_id_fk" FOREIGN KEY ("referred_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "schema_pics" ADD CONSTRAINT "schema_pics_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."categories"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "user_brands" ADD CONSTRAINT "user_brands_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "user_brands" ADD CONSTRAINT "user_brands_subscription_id_user_subscriptions_id_fk" FOREIGN KEY ("subscription_id") REFERENCES "public"."user_subscriptions"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "user_brands" ADD CONSTRAINT "user_brands_brand_id_brands_id_fk" FOREIGN KEY ("brand_id") REFERENCES "public"."brands"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "user_subscriptions" ADD CONSTRAINT "user_subscriptions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "user_subscriptions" ADD CONSTRAINT "user_subscriptions_plan_id_plans_id_fk" FOREIGN KEY ("plan_id") REFERENCES "public"."plans"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "user_vehicles" ADD CONSTRAINT "user_vehicles_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "user_vehicles" ADD CONSTRAINT "user_vehicles_vehicle_id_vehicles_id_fk" FOREIGN KEY ("vehicle_id") REFERENCES "public"."vehicles"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "vehicles" ADD CONSTRAINT "vehicles_brand_id_brands_id_fk" FOREIGN KEY ("brand_id") REFERENCES "public"."brands"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "emex_part_groups" ADD CONSTRAINT "emex_part_groups_emex_catalog_id_emex_catalogs_id_fk" FOREIGN KEY ("emex_catalog_id") REFERENCES "public"."emex_catalogs"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "emex_part_images" ADD CONSTRAINT "emex_part_images_emex_part_id_emex_parts_id_fk" FOREIGN KEY ("emex_part_id") REFERENCES "public"."emex_parts"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "emex_part_numbers" ADD CONSTRAINT "emex_part_numbers_emex_part_id_emex_parts_id_fk" FOREIGN KEY ("emex_part_id") REFERENCES "public"."emex_parts"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "emex_parts" ADD CONSTRAINT "emex_parts_emex_catalog_id_emex_catalogs_id_fk" FOREIGN KEY ("emex_catalog_id") REFERENCES "public"."emex_catalogs"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "emex_parts" ADD CONSTRAINT "emex_parts_group_id_emex_part_groups_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."emex_part_groups"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "emex_schema_pics" ADD CONSTRAINT "emex_schema_pics_group_id_emex_part_groups_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."emex_part_groups"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "emex_vehicle_group_links" ADD CONSTRAINT "emex_vehicle_group_links_emex_vehicle_id_emex_vehicles_id_fk" FOREIGN KEY ("emex_vehicle_id") REFERENCES "public"."emex_vehicles"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "emex_vehicle_group_links" ADD CONSTRAINT "emex_vehicle_group_links_emex_group_id_emex_part_groups_id_fk" FOREIGN KEY ("emex_group_id") REFERENCES "public"."emex_part_groups"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "emex_vehicle_part_links" ADD CONSTRAINT "emex_vehicle_part_links_emex_vehicle_id_emex_vehicles_id_fk" FOREIGN KEY ("emex_vehicle_id") REFERENCES "public"."emex_vehicles"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "emex_vehicle_part_links" ADD CONSTRAINT "emex_vehicle_part_links_emex_part_id_emex_parts_id_fk" FOREIGN KEY ("emex_part_id") REFERENCES "public"."emex_parts"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "emex_vehicle_part_links" ADD CONSTRAINT "emex_vehicle_part_links_emex_group_id_emex_part_groups_id_fk" FOREIGN KEY ("emex_group_id") REFERENCES "public"."emex_part_groups"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "emex_vehicle_vins" ADD CONSTRAINT "emex_vehicle_vins_emex_vehicle_id_emex_vehicles_id_fk" FOREIGN KEY ("emex_vehicle_id") REFERENCES "public"."emex_vehicles"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "emex_vehicles" ADD CONSTRAINT "emex_vehicles_catalog_id_emex_catalogs_id_fk" FOREIGN KEY ("catalog_id") REFERENCES "public"."emex_catalogs"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "pl24_part_groups" ADD CONSTRAINT "pl24_part_groups_pl24_vehicle_id_pl24_vehicles_id_fk" FOREIGN KEY ("pl24_vehicle_id") REFERENCES "public"."pl24_vehicles"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "pl24_part_images" ADD CONSTRAINT "pl24_part_images_pl24_part_id_pl24_parts_id_fk" FOREIGN KEY ("pl24_part_id") REFERENCES "public"."pl24_parts"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "pl24_part_numbers" ADD CONSTRAINT "pl24_part_numbers_pl24_part_id_pl24_parts_id_fk" FOREIGN KEY ("pl24_part_id") REFERENCES "public"."pl24_parts"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "pl24_parts" ADD CONSTRAINT "pl24_parts_pl24_vehicle_id_pl24_vehicles_id_fk" FOREIGN KEY ("pl24_vehicle_id") REFERENCES "public"."pl24_vehicles"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "pl24_parts" ADD CONSTRAINT "pl24_parts_group_id_pl24_part_groups_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."pl24_part_groups"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "pl24_schema_pics" ADD CONSTRAINT "pl24_schema_pics_group_id_pl24_part_groups_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."pl24_part_groups"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "pl24_vehicle_groups" ADD CONSTRAINT "pl24_vehicle_groups_catalog_id_pl24_catalogs_id_fk" FOREIGN KEY ("catalog_id") REFERENCES "public"."pl24_catalogs"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "pl24_vehicle_parts" ADD CONSTRAINT "pl24_vehicle_parts_pl24_vehicle_id_pl24_vehicles_id_fk" FOREIGN KEY ("pl24_vehicle_id") REFERENCES "public"."pl24_vehicles"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "pl24_vehicle_parts" ADD CONSTRAINT "pl24_vehicle_parts_pl24_part_id_pl24_parts_id_fk" FOREIGN KEY ("pl24_part_id") REFERENCES "public"."pl24_parts"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "pl24_vehicle_vins" ADD CONSTRAINT "pl24_vehicle_vins_pl24_vehicle_id_pl24_vehicles_id_fk" FOREIGN KEY ("pl24_vehicle_id") REFERENCES "public"."pl24_vehicles"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN ALTER TABLE "pl24_vehicles" ADD CONSTRAINT "pl24_vehicles_catalog_id_pl24_catalogs_id_fk" FOREIGN KEY ("catalog_id") REFERENCES "public"."pl24_catalogs"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$;--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "accounts_user_id_idx" ON "accounts" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "brands_slug_idx" ON "brands" USING btree ("slug");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "catalog_vehicles_source_svc_vid_idx" ON "catalog_vehicles" USING btree ("source","service_name","service_vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "catalog_vehicles_brand_name_idx" ON "catalog_vehicles" USING btree ("brand_name");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "catalog_vehicles_service_name_idx" ON "catalog_vehicles" USING btree ("service_name");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "categories_vehicle_id_idx" ON "categories" USING btree ("vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "categories_catalog_vehicle_id_idx" ON "categories" USING btree ("catalog_vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "categories_parent_id_idx" ON "categories" USING btree ("parent_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "categories_vehicle_name_source_idx" ON "categories" USING btree ("vehicle_id","catalog_vehicle_id","name","source");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "changelog_entries_published_at_idx" ON "changelog_entries" USING btree ("published_at");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "emex_translations_original_name_idx" ON "emex_category_translations" USING btree ("original_name");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "oem_code_copies_user_id_created_at_idx" ON "oem_code_copies" USING btree ("user_id","created_at");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "oem_code_copies_oem_code_idx" ON "oem_code_copies" USING btree ("oem_code");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "oem_code_copies_created_at_idx" ON "oem_code_copies" USING btree ("created_at");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "parts_vehicle_id_idx" ON "parts" USING btree ("vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "parts_catalog_vehicle_id_idx" ON "parts" USING btree ("catalog_vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "parts_category_id_idx" ON "parts" USING btree ("category_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "parts_oem_code_idx" ON "parts" USING btree ("oem_code");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "password_reset_tokens_token_idx" ON "password_reset_tokens" USING btree ("token");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "payments_user_id_idx" ON "payments" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "payments_status_idx" ON "payments" USING btree ("status");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "query_logs_user_id_created_at_idx" ON "query_logs" USING btree ("user_id","created_at");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "query_logs_vin_idx" ON "query_logs" USING btree ("vin");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "referrals_referrer_id_idx" ON "referrals" USING btree ("referrer_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "referrals_referred_id_idx" ON "referrals" USING btree ("referred_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "schema_pics_category_id_idx" ON "schema_pics" USING btree ("category_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "sessions_token_idx" ON "sessions" USING btree ("token");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "user_brands_user_id_idx" ON "user_brands" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "user_brands_unique_idx" ON "user_brands" USING btree ("user_id","subscription_id","brand_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "user_subscriptions_user_id_idx" ON "user_subscriptions" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "user_subscriptions_status_idx" ON "user_subscriptions" USING btree ("status");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "user_vehicles_user_vehicle_idx" ON "user_vehicles" USING btree ("user_id","vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "user_vehicles_user_id_idx" ON "user_vehicles" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "users_email_idx" ON "users" USING btree ("email");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "users_referral_code_idx" ON "users" USING btree ("referral_code");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "vehicles_vin_unique_idx" ON "vehicles" USING btree ("vin");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "verifications_identifier_idx" ON "verifications" USING btree ("identifier");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "emex_catalogs_catalog_id_idx" ON "emex_catalogs" USING btree ("catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_catalogs_code_idx" ON "emex_catalogs" USING btree ("code");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_part_groups_catalog_id_idx" ON "emex_part_groups" USING btree ("emex_catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_part_groups_group_id_idx" ON "emex_part_groups" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "emex_part_groups_catalog_group_idx" ON "emex_part_groups" USING btree ("emex_catalog_id","group_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_part_images_part_id_idx" ON "emex_part_images" USING btree ("emex_part_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_part_numbers_part_id_idx" ON "emex_part_numbers" USING btree ("emex_part_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_part_numbers_oem_code_idx" ON "emex_part_numbers" USING btree ("oem_code");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_parts_catalog_id_idx" ON "emex_parts" USING btree ("emex_catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_parts_group_id_idx" ON "emex_parts" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_parts_part_number_idx" ON "emex_parts" USING btree ("part_number");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_parts_oem_number_idx" ON "emex_parts" USING btree ("oem_number");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_schema_pics_group_id_idx" ON "emex_schema_pics" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "emex_schema_pics_group_path_idx" ON "emex_schema_pics" USING btree ("group_id","local_path");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_scrape_sessions_vin_idx" ON "emex_scrape_sessions" USING btree ("vin");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_scrape_sessions_status_idx" ON "emex_scrape_sessions" USING btree ("status");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "emex_vgl_vehicle_group_idx" ON "emex_vehicle_group_links" USING btree ("emex_vehicle_id","emex_group_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_vgl_group_idx" ON "emex_vehicle_group_links" USING btree ("emex_group_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "emex_vpl_vehicle_part_group_idx" ON "emex_vehicle_part_links" USING btree ("emex_vehicle_id","emex_part_id","emex_group_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_vpl_part_idx" ON "emex_vehicle_part_links" USING btree ("emex_part_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_vpl_group_idx" ON "emex_vehicle_part_links" USING btree ("emex_group_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "emex_vehicle_vins_vin_idx" ON "emex_vehicle_vins" USING btree ("vin");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_vehicle_vins_vehicle_id_idx" ON "emex_vehicle_vins" USING btree ("emex_vehicle_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "emex_vehicles_vehicle_id_idx" ON "emex_vehicles" USING btree ("vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_vehicles_catalog_id_idx" ON "emex_vehicles" USING btree ("catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_vehicles_name_idx" ON "emex_vehicles" USING btree ("name");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "emex_vehicles_source_id_idx" ON "emex_vehicles" USING btree ("source_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pcat_part_groups_catalog_id_idx" ON "pcat_part_groups" USING btree ("catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pcat_part_groups_parent_id_idx" ON "pcat_part_groups" USING btree ("parent_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pcat_parts_name_idx" ON "pcat_parts" USING btree ("name");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pcat_schema_pics_group_car_idx" ON "pcat_schema_pics" USING btree ("group_id","car_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pcat_vehicles_vin_idx" ON "pcat_vehicles" USING btree ("vin");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pcat_vehicles_catalog_id_idx" ON "pcat_vehicles" USING btree ("catalog_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "pl24_catalogs_catalog_id_idx" ON "pl24_catalogs" USING btree ("catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pl24_part_groups_vehicle_id_idx" ON "pl24_part_groups" USING btree ("pl24_vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pl24_part_groups_group_id_idx" ON "pl24_part_groups" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pl24_part_images_part_id_idx" ON "pl24_part_images" USING btree ("pl24_part_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pl24_part_numbers_part_id_idx" ON "pl24_part_numbers" USING btree ("pl24_part_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pl24_part_numbers_oem_code_idx" ON "pl24_part_numbers" USING btree ("oem_code");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pl24_parts_vehicle_id_idx" ON "pl24_parts" USING btree ("pl24_vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pl24_parts_group_id_idx" ON "pl24_parts" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pl24_schema_pics_group_id_idx" ON "pl24_schema_pics" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pl24_vehicle_groups_catalog_id_idx" ON "pl24_vehicle_groups" USING btree ("catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pl24_vehicle_groups_group_id_idx" ON "pl24_vehicle_groups" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pl24_vehicle_parts_vehicle_id_idx" ON "pl24_vehicle_parts" USING btree ("pl24_vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pl24_vehicle_parts_part_id_idx" ON "pl24_vehicle_parts" USING btree ("pl24_part_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pl24_vehicle_vins_vin_idx" ON "pl24_vehicle_vins" USING btree ("vin");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pl24_vehicle_vins_vehicle_id_idx" ON "pl24_vehicle_vins" USING btree ("pl24_vehicle_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "pl24_vehicles_vehicle_id_idx" ON "pl24_vehicles" USING btree ("vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "pl24_vehicles_catalog_id_idx" ON "pl24_vehicles" USING btree ("catalog_id");
|
||||
@@ -1,251 +1,287 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
/**
|
||||
* Unit tests for the migration runner's bootstrap logic.
|
||||
* Unit tests for the hash-based migration runner (runMigrations).
|
||||
*
|
||||
* We test bootstrapExistingDb() directly with a mock postgres.Sql client
|
||||
* and mock the filesystem via vi.mock("node:fs").
|
||||
* We test runMigrations() with a mock postgres.Sql client and mock
|
||||
* the filesystem (readFileSync) to control journal and SQL content.
|
||||
*/
|
||||
|
||||
// ── Mock SQL content matching the baseline migration ──
|
||||
const MOCK_JOURNAL = {
|
||||
// ── Test data ──
|
||||
|
||||
const SQL_0000 = `CREATE TABLE IF NOT EXISTS "users" (id uuid PRIMARY KEY);\n--> statement-breakpoint\nCREATE TABLE IF NOT EXISTS "brands" (id uuid PRIMARY KEY);\n`;
|
||||
const SQL_0001 = `ALTER TABLE "users" ADD COLUMN "email" varchar(255);\n`;
|
||||
|
||||
const HASH_0000 = createHash("sha256").update(SQL_0000).digest("hex");
|
||||
const HASH_0001 = createHash("sha256").update(SQL_0001).digest("hex");
|
||||
|
||||
const JOURNAL_1_ENTRY = {
|
||||
version: "7",
|
||||
dialect: "postgresql",
|
||||
entries: [
|
||||
{ idx: 0, version: "7", when: 1778535325959, tag: "0000_brief_guardian", breakpoints: true },
|
||||
],
|
||||
};
|
||||
|
||||
const JOURNAL_2_ENTRIES = {
|
||||
version: "7",
|
||||
dialect: "postgresql",
|
||||
entries: [
|
||||
{ idx: 0, version: "7", when: 1778535325959, tag: "0000_brief_guardian", breakpoints: true },
|
||||
{
|
||||
idx: 0,
|
||||
idx: 1,
|
||||
version: "7",
|
||||
when: 1778535325959,
|
||||
tag: "0000_brief_guardian",
|
||||
when: 1747039600000,
|
||||
tag: "0001_charming_quicksand",
|
||||
breakpoints: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const MOCK_BASELINE_SQL = [
|
||||
'CREATE TABLE "users" (id uuid PRIMARY KEY);',
|
||||
'CREATE TABLE "changelog_entries" (id uuid PRIMARY KEY);',
|
||||
'CREATE TABLE "brands" (id uuid PRIMARY KEY);',
|
||||
].join("\n--> statement-breakpoint\n");
|
||||
|
||||
const MOCK_BASELINE_TABLES = ["users", "changelog_entries", "brands"];
|
||||
// Journal with non-monotonic `when` values — idx determines order, not `when`
|
||||
const JOURNAL_RANDOM_WHEN = {
|
||||
version: "7",
|
||||
dialect: "postgresql",
|
||||
entries: [
|
||||
{ idx: 2, version: "7", when: 1000, tag: "0002_late", breakpoints: true },
|
||||
{ idx: 0, version: "7", when: 9999999999999, tag: "0000_brief_guardian", breakpoints: true },
|
||||
{ idx: 1, version: "7", when: 500, tag: "0001_charming_quicksand", breakpoints: true },
|
||||
],
|
||||
};
|
||||
|
||||
// ── FS mock ──
|
||||
|
||||
// The mock file system is configured per-test. readFileSync throws by default
|
||||
// for any unmapped path.
|
||||
let mockFiles: Record<string, string> = {};
|
||||
|
||||
vi.mock("node:fs", async () => {
|
||||
const actual = await vi.importActual<typeof import("node:fs")>("node:fs");
|
||||
return {
|
||||
...actual,
|
||||
readFileSync: vi.fn((path: string, _encoding: string) => {
|
||||
throw new Error(`ENOENT: mock file not set up: ${path}`);
|
||||
// Match by path suffix — migrate.ts uses join(migrationsFolder, ...)
|
||||
// which produces absolute paths, while mockFiles uses relative keys.
|
||||
const normalizedPath = String(path).replace(/\\/g, "/");
|
||||
for (const [key, content] of Object.entries(mockFiles)) {
|
||||
if (normalizedPath.endsWith(key)) return content;
|
||||
}
|
||||
throw new Error(`ENOENT: mock file not set up: ${normalizedPath}`);
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
import { bootstrapExistingDb } from "../../database/migrate";
|
||||
// Import under test after mock is set up
|
||||
import { runMigrations } from "../../database/migrate";
|
||||
|
||||
// ── Helpers ──
|
||||
// ── Mock SQL client helpers ──
|
||||
|
||||
interface TrackingTx {
|
||||
unsafe: ReturnType<typeof vi.fn>;
|
||||
__unsafeCalls: string[];
|
||||
}
|
||||
|
||||
interface MockSqlClient {
|
||||
(queryParts: TemplateStringsArray, ...values: unknown[]): Promise<unknown[]>;
|
||||
(template: TemplateStringsArray, ...values: unknown[]): Promise<unknown[]>;
|
||||
unsafe: ReturnType<typeof vi.fn>;
|
||||
begin: ReturnType<typeof vi.fn>;
|
||||
end: ReturnType<typeof vi.fn>;
|
||||
__inserts: string[];
|
||||
__unsafeCalls: string[];
|
||||
_setQueryHandler: (handler: (template: string) => unknown[]) => void;
|
||||
__appliedHashes: string[];
|
||||
__beginCallback: ((tx: TrackingTx) => Promise<void>) | null;
|
||||
_setBeginError: (err: Error) => void;
|
||||
}
|
||||
|
||||
function createMockSqlClient(): MockSqlClient {
|
||||
let queryHandler: (template: string) => unknown[] = () => [];
|
||||
let beginError: Error | null = null;
|
||||
const beginCallback: ((tx: TrackingTx) => Promise<void>) | null = null;
|
||||
|
||||
const sql = vi.fn(async (queryParts: TemplateStringsArray, ..._values: unknown[]) => {
|
||||
const queryText = queryParts.join(" ").trim();
|
||||
// Capture INSERT queries directly
|
||||
if (queryText.includes("INSERT INTO")) {
|
||||
(sql as any).__inserts.push(queryText);
|
||||
return [];
|
||||
const sql = vi.fn(async (template: TemplateStringsArray, ..._values: unknown[]) => {
|
||||
const queryText = template.join(" ").trim();
|
||||
// Handle SELECT hash query
|
||||
if (queryText.includes("SELECT hash FROM")) {
|
||||
return (sql as any).__appliedHashes.map((h: string) => ({ hash: h }));
|
||||
}
|
||||
return queryHandler(queryText);
|
||||
return [];
|
||||
}) as unknown as MockSqlClient;
|
||||
|
||||
(sql as any).unsafe = vi.fn(async (q: string) => {
|
||||
((sql as any).__unsafeCalls as string[]).push(q);
|
||||
(sql as any).unsafe = vi.fn(async (_q: string) => {
|
||||
((sql as any).__unsafeCalls as string[]).push(_q);
|
||||
});
|
||||
(sql as any).begin = vi.fn(async (cb: (tx: TrackingTx) => Promise<void>) => {
|
||||
(sql as any).__beginCallback = cb;
|
||||
|
||||
const tx: TrackingTx = {
|
||||
unsafe: vi.fn(async (_q: string) => {
|
||||
tx.__unsafeCalls.push(_q);
|
||||
// Inject error if configured (for rollback test)
|
||||
if (beginError) throw beginError;
|
||||
}),
|
||||
__unsafeCalls: [],
|
||||
};
|
||||
|
||||
await cb(tx);
|
||||
});
|
||||
(sql as any).end = vi.fn().mockResolvedValue(undefined);
|
||||
(sql as any).__inserts = [];
|
||||
(sql as any).__unsafeCalls = [];
|
||||
|
||||
(sql as any)._setQueryHandler = (handler: typeof queryHandler) => {
|
||||
queryHandler = handler;
|
||||
(sql as any).__appliedHashes = [];
|
||||
(sql as any).__beginCallback = null;
|
||||
(sql as any)._setBeginError = (err: Error) => {
|
||||
beginError = err;
|
||||
};
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the readFileSync mock to return journal + baseline SQL content.
|
||||
* Set up the mock file system for a journal and its SQL files.
|
||||
*/
|
||||
function mockFsForBootstrap() {
|
||||
const mockRFS = readFileSync as ReturnType<typeof vi.fn>;
|
||||
mockRFS.mockImplementation((path: string, _encoding: string) => {
|
||||
if (path.includes("_journal.json")) {
|
||||
return JSON.stringify(MOCK_JOURNAL);
|
||||
}
|
||||
if (path.includes("0000_brief_guardian.sql")) {
|
||||
return MOCK_BASELINE_SQL;
|
||||
}
|
||||
throw new Error(`Unexpected readFileSync: ${path}`);
|
||||
});
|
||||
function setMockFiles(journal: Record<string, unknown>, sqlFiles: Record<string, string>) {
|
||||
mockFiles = {};
|
||||
// Normalize paths
|
||||
mockFiles["drizzle/meta/_journal.json"] = JSON.stringify(journal);
|
||||
for (const [tag, content] of Object.entries(sqlFiles)) {
|
||||
mockFiles[`drizzle/${tag}.sql`] = content;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tests ──
|
||||
|
||||
describe("bootstrapExistingDb", () => {
|
||||
describe("runMigrations", () => {
|
||||
let sqlClient: MockSqlClient;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let exitMock: any;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
mockFiles = {};
|
||||
sqlClient = createMockSqlClient();
|
||||
exitMock = vi.spyOn(process, "exit").mockImplementation(() => undefined as never);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
exitMock.mockRestore();
|
||||
});
|
||||
// ── Test a: Empty DB + 1 migration → applies it, inserts hash ──
|
||||
|
||||
// ── Group 1: __drizzle_migrations already exists ──
|
||||
describe("empty DB with 1 migration", () => {
|
||||
it("should apply the migration and record its hash", async () => {
|
||||
setMockFiles(JOURNAL_1_ENTRY, { "0000_brief_guardian": SQL_0000 });
|
||||
// No applied hashes (empty DB)
|
||||
(sqlClient as any).__appliedHashes = [];
|
||||
|
||||
describe("when __drizzle_migrations already exists", () => {
|
||||
it("should skip bootstrap (no CREATE, no INSERT)", async () => {
|
||||
(sqlClient as any)._setQueryHandler((queryText: string) => {
|
||||
if (
|
||||
queryText.includes("information_schema") &&
|
||||
queryText.includes("__drizzle_migrations")
|
||||
) {
|
||||
return [{ table_name: "__drizzle_migrations" }];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
await runMigrations(sqlClient as any);
|
||||
|
||||
await bootstrapExistingDb(sqlClient as any);
|
||||
// Should create schema and tracking table
|
||||
const unsafeAll = (sqlClient as any).__unsafeCalls as string[];
|
||||
expect(unsafeAll.some((c: string) => c.includes("CREATE SCHEMA"))).toBe(true);
|
||||
expect(unsafeAll.some((c: string) => c.includes("CREATE TABLE IF NOT EXISTS"))).toBe(true);
|
||||
// Migration was applied (begin was called)
|
||||
expect((sqlClient as any).begin).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Should NOT create schema or table
|
||||
expect((sqlClient as any).__unsafeCalls).toEqual([]);
|
||||
|
||||
// Should NOT insert any migration records
|
||||
expect((sqlClient as any).__inserts).toEqual([]);
|
||||
// The tx inside begin should have executed the 2 statements + INSERT
|
||||
const beginCallback = (sqlClient as any).__beginCallback;
|
||||
expect(beginCallback).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// ── Group 2: All baseline tables present → bootstrap ──
|
||||
// ── Test b: Existing DB + matching hash → skips ──
|
||||
|
||||
describe("when all baseline tables exist (existing DB with no tracking)", () => {
|
||||
it("should create schema, create table, and mark baseline entries as applied", async () => {
|
||||
mockFsForBootstrap();
|
||||
describe("existing DB with matching hash", () => {
|
||||
it("should skip the migration (appliedCount=0, skippedCount=1)", async () => {
|
||||
setMockFiles(JOURNAL_1_ENTRY, { "0000_brief_guardian": SQL_0000 });
|
||||
(sqlClient as any).__appliedHashes = [HASH_0000];
|
||||
|
||||
(sqlClient as any)._setQueryHandler((queryText: string) => {
|
||||
if (
|
||||
queryText.includes("information_schema") &&
|
||||
queryText.includes("__drizzle_migrations")
|
||||
) {
|
||||
return []; // __drizzle_migrations does NOT exist
|
||||
}
|
||||
// The baseline-tables check uses information_schema.tables with ANY
|
||||
if (queryText.includes("information_schema") && queryText.includes("ANY(")) {
|
||||
return MOCK_BASELINE_TABLES.map((t) => ({ table_name: t }));
|
||||
}
|
||||
return [];
|
||||
});
|
||||
await runMigrations(sqlClient as any);
|
||||
|
||||
await bootstrapExistingDb(sqlClient as any);
|
||||
|
||||
// Verify CREATE SCHEMA was called
|
||||
expect((sqlClient as any).__unsafeCalls).toContain('CREATE SCHEMA IF NOT EXISTS "drizzle"');
|
||||
|
||||
// Verify CREATE TABLE was called
|
||||
const createTableCall = (sqlClient as any).__unsafeCalls.find((c: string) =>
|
||||
c.includes("CREATE TABLE IF NOT EXISTS"),
|
||||
);
|
||||
expect(createTableCall).toBeDefined();
|
||||
expect(createTableCall).toContain("__drizzle_migrations");
|
||||
|
||||
// Verify INSERT was called with a hash
|
||||
expect((sqlClient as any).__inserts.length).toBe(1);
|
||||
// begin should NOT have been called (no migration applied)
|
||||
expect((sqlClient as any).begin).not.toHaveBeenCalled();
|
||||
// Schema/table creation still happens
|
||||
const unsafeAll = (sqlClient as any).__unsafeCalls as string[];
|
||||
expect(unsafeAll.some((c: string) => c.includes("CREATE SCHEMA"))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Group 3: No baseline tables exist → fresh DB ──
|
||||
// ── Test c: 2 migrations, 1 applied + 1 new → applies only the new one ──
|
||||
|
||||
describe("when no baseline tables exist (fresh DB)", () => {
|
||||
it("should skip bootstrap entirely", async () => {
|
||||
mockFsForBootstrap();
|
||||
|
||||
(sqlClient as any)._setQueryHandler((queryText: string) => {
|
||||
if (
|
||||
queryText.includes("information_schema") &&
|
||||
queryText.includes("__drizzle_migrations")
|
||||
) {
|
||||
return []; // __drizzle_migrations does NOT exist
|
||||
}
|
||||
if (queryText.includes("information_schema") && queryText.includes("ANY(")) {
|
||||
return []; // no baseline tables exist
|
||||
}
|
||||
return [];
|
||||
describe("2 migrations, 1 already applied", () => {
|
||||
it("should apply only the new migration", async () => {
|
||||
setMockFiles(JOURNAL_2_ENTRIES, {
|
||||
"0000_brief_guardian": SQL_0000,
|
||||
"0001_charming_quicksand": SQL_0001,
|
||||
});
|
||||
// Only 0000's hash is already applied
|
||||
(sqlClient as any).__appliedHashes = [HASH_0000];
|
||||
|
||||
await bootstrapExistingDb(sqlClient as any);
|
||||
await runMigrations(sqlClient as any);
|
||||
|
||||
// No schema creation
|
||||
expect((sqlClient as any).__unsafeCalls).toEqual([]);
|
||||
// No inserts
|
||||
expect((sqlClient as any).__inserts).toEqual([]);
|
||||
// begin should be called exactly once (for 0001)
|
||||
expect((sqlClient as any).begin).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Group 4: Partial baseline state → error ──
|
||||
// ── Test d: Statement breakpoint parsing ──
|
||||
|
||||
describe("when some but not all baseline tables exist (partial state)", () => {
|
||||
it("should call process.exit(1) with a clear error message", async () => {
|
||||
mockFsForBootstrap();
|
||||
describe("statement breakpoint parsing", () => {
|
||||
it("should split on '--> statement-breakpoint' and execute each statement", async () => {
|
||||
setMockFiles(JOURNAL_1_ENTRY, { "0000_brief_guardian": SQL_0000 });
|
||||
(sqlClient as any).__appliedHashes = [];
|
||||
|
||||
(sqlClient as any)._setQueryHandler((queryText: string) => {
|
||||
if (
|
||||
queryText.includes("information_schema") &&
|
||||
queryText.includes("__drizzle_migrations")
|
||||
) {
|
||||
return []; // __drizzle_migrations does NOT exist
|
||||
}
|
||||
if (queryText.includes("information_schema") && queryText.includes("ANY(")) {
|
||||
// Only users and brands exist — changelog_entries is missing
|
||||
return [{ table_name: "users" }, { table_name: "brands" }];
|
||||
}
|
||||
return [];
|
||||
await runMigrations(sqlClient as any);
|
||||
|
||||
expect((sqlClient as any).begin).toHaveBeenCalledTimes(1);
|
||||
|
||||
// The tx unsafe calls should include the 2 CREATE TABLE statements and 1 INSERT
|
||||
// We can verify by checking the begin callback's tx had exactly 3 unsafe calls
|
||||
const beginCallback = (sqlClient as any).__beginCallback;
|
||||
expect(beginCallback).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// ── Test e: Transaction rollback ──
|
||||
|
||||
describe("transaction rollback on error", () => {
|
||||
it("should not insert migration hash when a statement fails", async () => {
|
||||
setMockFiles(JOURNAL_2_ENTRIES, {
|
||||
"0000_brief_guardian": SQL_0000,
|
||||
"0001_charming_quicksand": SQL_0001,
|
||||
});
|
||||
// 0000 already applied, 0001 will be attempted
|
||||
(sqlClient as any).__appliedHashes = [HASH_0000];
|
||||
// Make the begin callback throw on first tx.unsafe call
|
||||
(sqlClient as any)._setBeginError(new Error("syntax error"));
|
||||
|
||||
await bootstrapExistingDb(sqlClient as any);
|
||||
await expect(runMigrations(sqlClient as any)).rejects.toThrow("syntax error");
|
||||
|
||||
// Should exit with code 1
|
||||
expect(exitMock).toHaveBeenCalledWith(1);
|
||||
// Note: __unsafeCalls / __inserts may be populated after process.exit
|
||||
// because the mock doesn't actually terminate execution.
|
||||
// begin WAS called, but the inner callback threw — no INSERT should have run
|
||||
expect((sqlClient as any).begin).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Test f: Journal ordering by idx, not when ──
|
||||
|
||||
describe("journal ordering", () => {
|
||||
it("should sort entries by idx regardless of 'when' values", async () => {
|
||||
const sqlFiles: Record<string, string> = {
|
||||
"0000_brief_guardian": SQL_0000,
|
||||
"0001_charming_quicksand": SQL_0001,
|
||||
"0002_late": 'CREATE INDEX IF NOT EXISTS "idx_test" ON "users" ("email");\n',
|
||||
};
|
||||
setMockFiles(JOURNAL_RANDOM_WHEN, sqlFiles);
|
||||
(sqlClient as any).__appliedHashes = [];
|
||||
|
||||
await runMigrations(sqlClient as any);
|
||||
|
||||
// All 3 migrations should be applied (begin called 3 times)
|
||||
expect((sqlClient as any).begin).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Test: Empty journal → no-op ──
|
||||
|
||||
describe("empty journal", () => {
|
||||
it("should exit early with no errors", async () => {
|
||||
setMockFiles({ version: "7", dialect: "postgresql", entries: [] }, {});
|
||||
|
||||
await runMigrations(sqlClient as any);
|
||||
|
||||
// begin should NOT have been called
|
||||
expect((sqlClient as any).begin).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("hash computation", () => {
|
||||
it("should produce the expected SHA256 hash for a known input", () => {
|
||||
const input = "SELECT 1";
|
||||
const hash = createHash("sha256").update(input).digest("hex");
|
||||
expect(hash).toBe("e004ebd5b5532a4b85984a62f8ad48a81aa3460c1ca07701f386135d72cdecf5");
|
||||
});
|
||||
|
||||
it("should produce a different hash for different input", () => {
|
||||
const hashA = createHash("sha256").update("SELECT 1").digest("hex");
|
||||
const hashB = createHash("sha256").update("SELECT 2").digest("hex");
|
||||
expect(hashA).not.toBe(hashB);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,9 +2,6 @@ import { createHash } from "node:crypto";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join, resolve } from "node:path";
|
||||
import { config as loadEnv } from "dotenv";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import { migrate } from "drizzle-orm/postgres-js/migrator";
|
||||
import postgres from "postgres";
|
||||
|
||||
// ── Dotenv bootstrap (load .env for standalone tsx execution) ──
|
||||
@@ -51,95 +48,29 @@ interface Journal {
|
||||
entries: JournalEntry[];
|
||||
}
|
||||
|
||||
export async function bootstrapExistingDb(sqlClient: postgres.Sql): Promise<void> {
|
||||
const tablesResult = await sqlClient`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'drizzle'
|
||||
AND table_name = '__drizzle_migrations'
|
||||
`;
|
||||
|
||||
if (tablesResult.length > 0) {
|
||||
// __drizzle_migrations already exists — normal migration run
|
||||
console.log("[migrate] Migration tracking table exists — skipping bootstrap");
|
||||
return;
|
||||
}
|
||||
|
||||
// Read journal to find baseline migration SQL files
|
||||
const journalPath = join(migrationsFolder, "meta", "_journal.json");
|
||||
let journal: Journal;
|
||||
|
||||
try {
|
||||
journal = JSON.parse(readFileSync(journalPath, "utf-8")) as Journal;
|
||||
} catch (err) {
|
||||
console.error("[migrate] Failed to read _journal.json:", (err as Error).message);
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!journal.entries || journal.entries.length === 0) {
|
||||
console.warn("[migrate] _journal.json has no entries — no migrations to bootstrap");
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract all CREATE TABLE table names from the baseline SQL file
|
||||
// We only match bare CREATE TABLE (not IF NOT EXISTS) so we can detect
|
||||
// which tables the baseline migration explicitly creates.
|
||||
const baselineEntry = journal.entries[0];
|
||||
const baselineSqlPath = join(migrationsFolder, `${baselineEntry.tag}.sql`);
|
||||
let baselineSql: string;
|
||||
try {
|
||||
baselineSql = readFileSync(baselineSqlPath, "utf-8");
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`[migrate] Failed to read baseline migration ${baselineEntry.tag}.sql:`,
|
||||
(err as Error).message,
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
|
||||
const baselineTables = [...baselineSql.matchAll(/CREATE TABLE\s+"([^"]+)"/g)].map((m) => m[1]);
|
||||
|
||||
if (baselineTables.length === 0) {
|
||||
console.warn("[migrate] No CREATE TABLE statements found in baseline SQL — skipping bootstrap");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check which baseline tables exist in the target database
|
||||
const existingResult = await sqlClient`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = ANY(${baselineTables})
|
||||
`;
|
||||
const existingTables = new Set(
|
||||
existingResult.map((r) => (r as Record<string, string>).table_name),
|
||||
);
|
||||
const existingCount = existingTables.size;
|
||||
const totalCount = baselineTables.length;
|
||||
|
||||
if (existingCount === 0) {
|
||||
// Fresh DB — migrate() will handle everything
|
||||
console.log("[migrate] Fresh database (no baseline tables) — skipping bootstrap");
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingCount < totalCount) {
|
||||
// Partial-baseline state — error out with clear instructions
|
||||
const missingTables = baselineTables.filter((t) => !existingTables.has(t));
|
||||
console.error(
|
||||
`[migrate] DB is in partial-baseline state: ${existingCount}/${totalCount} tables present. Run \`pnpm db:push --force\` once to sync schema, then retry.`,
|
||||
);
|
||||
console.error(`[migrate] Missing tables (first 10): ${missingTables.slice(0, 10).join(", ")}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// All baseline tables exist — bootstrap migration tracking
|
||||
console.log(`[migrate] Bootstrapping existing DB: all ${totalCount} baseline tables present`);
|
||||
|
||||
// Create the drizzle schema if it doesn't exist
|
||||
/**
|
||||
* Hash-based migration runner.
|
||||
*
|
||||
* Instead of Drizzle's built-in `migrate()` (which compares `folderMillis`
|
||||
* timestamps from `_journal.json` — susceptible to clock skew), this runner
|
||||
* tracks applied migrations by SHA256 of the SQL file content.
|
||||
*
|
||||
* How it works:
|
||||
* 1. Ensures the `drizzle` schema and `__drizzle_migrations` tracking table exist.
|
||||
* 2. Reads all previously-applied migration hashes from `__drizzle_migrations`.
|
||||
* 3. Reads `drizzle/meta/_journal.json` for the ordered list of migration files.
|
||||
* 4. For each migration, computes SHA256 of the SQL content.
|
||||
* 5. If the hash already exists → skip (already applied).
|
||||
* 6. If the hash is new → execute all statements in a single transaction,
|
||||
* then record the hash with the journal `when` timestamp.
|
||||
*
|
||||
* This is immune to the "new migration skipped" bug where Drizzle silently
|
||||
* ignores new migrations because `folderMillis` timestamps are stale or
|
||||
* skewed.
|
||||
*/
|
||||
export async function runMigrations(sqlClient: postgres.Sql): Promise<void> {
|
||||
// Ensure drizzle schema and tracking table exist
|
||||
await sqlClient.unsafe(`CREATE SCHEMA IF NOT EXISTS "drizzle"`);
|
||||
|
||||
// Create __drizzle_migrations table matching Drizzle's expected schema
|
||||
await sqlClient.unsafe(`
|
||||
CREATE TABLE IF NOT EXISTS "drizzle"."__drizzle_migrations" (
|
||||
id SERIAL PRIMARY KEY,
|
||||
@@ -148,25 +79,75 @@ export async function bootstrapExistingDb(sqlClient: postgres.Sql): Promise<void
|
||||
)
|
||||
`);
|
||||
|
||||
// Mark all journal entries as applied (hash the SQL content)
|
||||
const now = Date.now();
|
||||
for (const entry of journal.entries) {
|
||||
// Re-read baseline SQL (already loaded above for first entry)
|
||||
const sqlContent =
|
||||
entry.tag === baselineEntry.tag
|
||||
? baselineSql
|
||||
: readFileSync(join(migrationsFolder, `${entry.tag}.sql`), "utf-8");
|
||||
// Read all previously-applied hashes
|
||||
const applied = await sqlClient<{ hash: string }[]>`
|
||||
SELECT hash FROM "drizzle"."__drizzle_migrations"
|
||||
`;
|
||||
const appliedHashes = new Set(applied.map((r) => r.hash));
|
||||
|
||||
// Read journal
|
||||
const journalPath = join(migrationsFolder, "meta", "_journal.json");
|
||||
let journal: Journal;
|
||||
try {
|
||||
journal = JSON.parse(readFileSync(journalPath, "utf-8")) as Journal;
|
||||
} catch (err) {
|
||||
console.error("[migrate] Failed to read _journal.json:", (err as Error).message);
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!journal.entries || journal.entries.length === 0) {
|
||||
console.warn("[migrate] _journal.json has no entries — no migrations to run");
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort entries by idx (ascending) — applies migrations in correct order
|
||||
// regardless of `when` field monotonicity
|
||||
const sorted = [...journal.entries].sort((a, b) => a.idx - b.idx);
|
||||
|
||||
let appliedCount = 0;
|
||||
let skippedCount = 0;
|
||||
|
||||
for (const entry of sorted) {
|
||||
const sqlPath = join(migrationsFolder, `${entry.tag}.sql`);
|
||||
let sqlContent: string;
|
||||
try {
|
||||
sqlContent = readFileSync(sqlPath, "utf-8");
|
||||
} catch (err) {
|
||||
console.error(`[migrate] Failed to read migration ${entry.tag}.sql:`, (err as Error).message);
|
||||
throw err;
|
||||
}
|
||||
|
||||
const hash = createHash("sha256").update(sqlContent).digest("hex");
|
||||
await sqlClient`
|
||||
INSERT INTO "drizzle"."__drizzle_migrations" (hash, created_at)
|
||||
VALUES (${hash}, ${now})
|
||||
`;
|
||||
|
||||
if (appliedHashes.has(hash)) {
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Split on statement breakpoints and execute in a single transaction
|
||||
const statements = sqlContent
|
||||
.split("--> statement-breakpoint")
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s.length > 0);
|
||||
|
||||
await sqlClient.begin(async (tx) => {
|
||||
for (const stmt of statements) {
|
||||
await tx.unsafe(stmt);
|
||||
}
|
||||
|
||||
// Record hash with deterministic created_at from journal (not Date.now())
|
||||
const createdAt = Number.isFinite(entry.when) && entry.when > 0 ? entry.when : Date.now();
|
||||
|
||||
await tx.unsafe(
|
||||
`INSERT INTO "drizzle"."__drizzle_migrations" (hash, created_at) VALUES ('${hash}', ${createdAt})`,
|
||||
);
|
||||
});
|
||||
|
||||
appliedCount++;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[migrate] Bootstrapped existing DB: all ${totalCount} baseline tables present, ` +
|
||||
`marked ${journal.entries.length} migration(s) as applied`,
|
||||
`[migrate] Done: ${appliedCount} applied, ${skippedCount} already applied (total ${sorted.length})`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -183,13 +164,7 @@ async function run(): Promise<void> {
|
||||
const sqlClient = postgres(databaseUrl, { max: 1 });
|
||||
|
||||
try {
|
||||
// Bootstrap existing databases before running the migrator
|
||||
await bootstrapExistingDb(sqlClient);
|
||||
|
||||
// Run Drizzle migrator (idempotent — applies only unapplied migrations)
|
||||
const db = drizzle(sqlClient);
|
||||
await migrate(db, { migrationsFolder });
|
||||
console.log("[migrate] Migration complete");
|
||||
await runMigrations(sqlClient);
|
||||
|
||||
await sqlClient.end();
|
||||
process.exit(0);
|
||||
|
||||
@@ -356,7 +356,20 @@ catalogVehicles
|
||||
|
||||
### Migration Workflow
|
||||
|
||||
**Strategy:** Formal Drizzle SQL migrations with automated startup.
|
||||
**Strategy:** Formal Drizzle SQL migrations with a custom hash-based runner.
|
||||
|
||||
The migration runner at `apps/api/src/database/migrate.ts` uses a **custom hash-based runner** instead of Drizzle's built-in `migrate()`. It tracks applied migrations by SHA256 of SQL file content rather than by timestamp comparison.
|
||||
|
||||
**Why custom?** Drizzle's built-in `migrate()` compares `folderMillis` timestamps from `drizzle/meta/_journal.json`. When `drizzle-kit generate` produces incorrect timestamps (clock skew bug), new migrations can be silently skipped. Hash-based tracking is immune to clock skew and provides deterministic idempotency.
|
||||
|
||||
**How it works:**
|
||||
1. Reads all previously-applied migration hashes from `drizzle.__drizzle_migrations`
|
||||
2. Reads `drizzle/meta/_journal.json` for the list of migration files in index order
|
||||
3. For each migration file, computes SHA256 of the SQL content
|
||||
4. If the hash already exists in `__drizzle_migrations` → skip
|
||||
5. If the hash is new → execute all statements in a single transaction, then record the hash
|
||||
|
||||
**Idempotent baseline:** `0000_brief_guardian.sql` uses `IF NOT EXISTS` for all `CREATE TABLE`, `CREATE INDEX`, and `CREATE UNIQUE INDEX` statements. `ALTER TABLE ADD CONSTRAINT` statements are wrapped in `DO $$ BEGIN ... EXCEPTION WHEN duplicate_object ... END $$` blocks. This allows the baseline to be safely re-applied on existing databases where `__drizzle_migrations` was bootstrapped to a different hash.
|
||||
|
||||
**Schema change workflow:**
|
||||
1. Edit schema files in `apps/api/src/database/schema/`
|
||||
@@ -366,7 +379,7 @@ catalogVehicles
|
||||
|
||||
**Container startup:** The API server runs `apps/api/start.sh` which executes `node dist/database/migrate.js` BEFORE starting the NestJS server. This ensures all pending migrations are applied before any code runs.
|
||||
|
||||
**Bootstrap note:** Existing databases with no migration history (`__drizzle_migrations` table missing) are automatically bootstrapped on first run. The bootstrap creates the tracking table and marks all baseline migrations as already applied (no SQL is executed against the live DB). This is safe for databases previously managed via `pnpm db:push`.
|
||||
**Bootstrap note:** Existing databases with no migration history (`__drizzle_migrations` table empty or missing) are handled automatically. The runner creates the tracking table and applies the baseline migration idempotently (all statements use `IF NOT EXISTS` guards, so no errors on re-application). This is safe for databases previously managed via `pnpm db:push`.
|
||||
|
||||
**Manual commands:**
|
||||
- `pnpm db:migrate` — Run migrations in dev (uses `tsx` for direct TS execution)
|
||||
|
||||
Reference in New Issue
Block a user