diff --git a/shared/database/src/migrations/0097_fix-fk-on-delete-flowsheet-rotation-reviews.sql b/shared/database/src/migrations/0097_fix-fk-on-delete-flowsheet-rotation-reviews.sql new file mode 100644 index 00000000..8d9688ee --- /dev/null +++ b/shared/database/src/migrations/0097_fix-fk-on-delete-flowsheet-rotation-reviews.sql @@ -0,0 +1,101 @@ +-- precondition-guard: not-required (DROP + ADD CONSTRAINT on existing FKs is a +-- pure constraint-shape change; no data invariant is asserted by the new +-- ON DELETE actions and no rows can violate the redefined FK as long as the +-- referenced parent rows still exist — which they already do because the +-- old NO ACTION constraint enforced exactly that) +-- @no-precondition-needed: ON DELETE behaviour change is forward-looking; it +-- governs future DELETEs on the parent table, not the present FK shape. +-- 0097 — Fix FK ON DELETE drift on flowsheet / rotation / reviews. +-- +-- Five FK constraints were created with ON DELETE NO ACTION in +-- `0000_rare_prima.sql` and recreated unchanged by `0016_nervous_hydra.sql`, +-- but the Drizzle schema source declares them as SET NULL (flowsheet) and +-- CASCADE (rotation, reviews). The most-recent snapshot +-- (`meta/0096_snapshot.json`) records the schema-source values, masking the +-- drift from `drizzle-kit generate` — no subsequent migration patched the +-- production DB to match, so new environments diverge from prod. +-- +-- This migration follows the pattern in `0048_fix-fk-on-delete-set-null.sql` +-- (the predecessor that patched the analogous drift for schedule / +-- shift_covers / shows.primary_dj_id; see #433) but uses `ADD CONSTRAINT +-- ... NOT VALID` rather than a bare `ADD CONSTRAINT` to avoid blocking +-- writes on flowsheet (~857k prod rows) during the deploy. The five +-- constraints below were missed by 0048. +-- +-- ## Lock behaviour: why NOT VALID (and why VALIDATE runs out-of-band) +-- +-- A bare `ALTER TABLE ... ADD CONSTRAINT ... FOREIGN KEY ...` takes an +-- `AccessExclusiveLock` AND runs a full-table validation scan that holds +-- the lock for the entire scan — blocking every concurrent INSERT/UPDATE/ +-- DELETE on the table for the deploy's duration. On an on-air station with +-- active DJs writing flowsheet rows in real time, that is a user-visible +-- outage window. +-- +-- `ADD CONSTRAINT ... NOT VALID` skips the validation scan: it takes +-- `AccessExclusiveLock` for a metadata-only change and releases it +-- instantly. New writes are enforced against the new FK shape immediately; +-- only retroactive validation of pre-existing rows is deferred. +-- +-- The companion `ALTER TABLE ... VALIDATE CONSTRAINT` runs the scan under +-- the lighter `ShareUpdateExclusiveLock`, which allows concurrent SELECT, +-- INSERT, UPDATE, DELETE. **But this benefit only materializes if VALIDATE +-- runs in its OWN transaction** — Drizzle's migrator +-- (`drizzle-orm/pg-core/dialect.js:60`) wraps the entire migration in one +-- `session.transaction()`, so a VALIDATE statement inside the migration +-- file would run under the AccessExclusiveLock that the preceding DROP / +-- ADD already acquired, defeating the point. We therefore omit VALIDATE +-- here and document it as the post-deploy operator step below. +-- +-- For these five constraints the validation is effectively a no-op anyway: +-- the existing `NO ACTION` FK has already kept the reference relation +-- consistent (every flowsheet.album_id either points at a live library.id +-- or is NULL). Changing only the `ON DELETE` action does not introduce +-- any new data invariant on existing rows — the action governs future +-- parent-row DELETEs. VALIDATE still has to scan because PostgreSQL +-- tracks the `convalidated` flag per constraint; until VALIDATE runs the +-- constraint is recorded as "trusted for new writes but not proven for +-- old rows." +-- +-- ## Post-deploy operator step +-- +-- After this migration deploys, an operator runs the following five +-- statements (each in its own implicit transaction — do NOT wrap them in +-- BEGIN/COMMIT) during a low-write window to clear the unvalidated state. +-- Skipping this step is harmless for correctness; it only leaves the +-- constraints with `convalidated = false` until the next operator runs +-- it. A bare `ANALYZE` is not needed (no row mutations). +-- +-- ALTER TABLE "wxyc_schema"."flowsheet" VALIDATE CONSTRAINT "flowsheet_show_id_shows_id_fk"; +-- ALTER TABLE "wxyc_schema"."flowsheet" VALIDATE CONSTRAINT "flowsheet_album_id_library_id_fk"; +-- ALTER TABLE "wxyc_schema"."flowsheet" VALIDATE CONSTRAINT "flowsheet_rotation_id_rotation_id_fk"; +-- ALTER TABLE "wxyc_schema"."rotation" VALIDATE CONSTRAINT "rotation_album_id_library_id_fk"; +-- ALTER TABLE "wxyc_schema"."reviews" VALIDATE CONSTRAINT "reviews_album_id_library_id_fk"; +-- +-- We DROP+ADD rather than `ALTER CONSTRAINT` because PostgreSQL has no +-- syntax to change `ON DELETE` action in place — you must drop and recreate. +-- The DROP itself is metadata-only and instant. +-- +-- See WXYC/Backend-Service#1126 for the full drift table and reproduction. +-- See PostgreSQL docs: +-- https://www.postgresql.org/docs/current/sql-altertable.html (NOT VALID) +-- https://www.postgresql.org/docs/current/explicit-locking.html (lock modes) + +-- flowsheet.show_id → shows.id : NO ACTION → SET NULL +ALTER TABLE "wxyc_schema"."flowsheet" DROP CONSTRAINT "flowsheet_show_id_shows_id_fk"; +ALTER TABLE "wxyc_schema"."flowsheet" ADD CONSTRAINT "flowsheet_show_id_shows_id_fk" FOREIGN KEY ("show_id") REFERENCES "wxyc_schema"."shows"("id") ON DELETE SET NULL ON UPDATE NO ACTION NOT VALID; + +-- flowsheet.album_id → library.id : NO ACTION → SET NULL +ALTER TABLE "wxyc_schema"."flowsheet" DROP CONSTRAINT "flowsheet_album_id_library_id_fk"; +ALTER TABLE "wxyc_schema"."flowsheet" ADD CONSTRAINT "flowsheet_album_id_library_id_fk" FOREIGN KEY ("album_id") REFERENCES "wxyc_schema"."library"("id") ON DELETE SET NULL ON UPDATE NO ACTION NOT VALID; + +-- flowsheet.rotation_id → rotation.id : NO ACTION → SET NULL +ALTER TABLE "wxyc_schema"."flowsheet" DROP CONSTRAINT "flowsheet_rotation_id_rotation_id_fk"; +ALTER TABLE "wxyc_schema"."flowsheet" ADD CONSTRAINT "flowsheet_rotation_id_rotation_id_fk" FOREIGN KEY ("rotation_id") REFERENCES "wxyc_schema"."rotation"("id") ON DELETE SET NULL ON UPDATE NO ACTION NOT VALID; + +-- rotation.album_id → library.id : NO ACTION → CASCADE +ALTER TABLE "wxyc_schema"."rotation" DROP CONSTRAINT "rotation_album_id_library_id_fk"; +ALTER TABLE "wxyc_schema"."rotation" ADD CONSTRAINT "rotation_album_id_library_id_fk" FOREIGN KEY ("album_id") REFERENCES "wxyc_schema"."library"("id") ON DELETE CASCADE ON UPDATE NO ACTION NOT VALID; + +-- reviews.album_id → library.id : NO ACTION → CASCADE +ALTER TABLE "wxyc_schema"."reviews" DROP CONSTRAINT "reviews_album_id_library_id_fk"; +ALTER TABLE "wxyc_schema"."reviews" ADD CONSTRAINT "reviews_album_id_library_id_fk" FOREIGN KEY ("album_id") REFERENCES "wxyc_schema"."library"("id") ON DELETE CASCADE ON UPDATE NO ACTION NOT VALID; diff --git a/shared/database/src/migrations/meta/0097_snapshot.json b/shared/database/src/migrations/meta/0097_snapshot.json new file mode 100644 index 00000000..cb231e35 --- /dev/null +++ b/shared/database/src/migrations/meta/0097_snapshot.json @@ -0,0 +1,4608 @@ +{ + "id": "4d4ed747-aeba-4120-8063-80dc520a1cc5", + "prevId": "c234d50f-46dc-4d1d-8e48-3687f1cedaaa", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.auth_account": { + "name": "auth_account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "auth_account_provider_account_key": { + "name": "auth_account_provider_account_key", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auth_account_user_id_auth_user_id_fk": { + "name": "auth_account_user_id_auth_user_id_fk", + "tableFrom": "auth_account", + "tableTo": "auth_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.album_metadata": { + "name": "album_metadata", + "schema": "wxyc_schema", + "columns": { + "album_id": { + "name": "album_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "artwork_url": { + "name": "artwork_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "discogs_url": { + "name": "discogs_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "release_year": { + "name": "release_year", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "spotify_url": { + "name": "spotify_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "apple_music_url": { + "name": "apple_music_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "youtube_music_url": { + "name": "youtube_music_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "bandcamp_url": { + "name": "bandcamp_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "soundcloud_url": { + "name": "soundcloud_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "artist_bio": { + "name": "artist_bio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "artist_wikipedia_url": { + "name": "artist_wikipedia_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "album_metadata_album_id_library_id_fk": { + "name": "album_metadata_album_id_library_id_fk", + "tableFrom": "album_metadata", + "tableTo": "library", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "album_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.anonymous_devices": { + "name": "anonymous_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "blocked": { + "name": "blocked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "blocked_at": { + "name": "blocked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "anonymous_devices_device_id_key": { + "name": "anonymous_devices_device_id_key", + "columns": [ + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.artist_crossreference": { + "name": "artist_crossreference", + "schema": "wxyc_schema", + "columns": { + "source_artist_id": { + "name": "source_artist_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_artist_id": { + "name": "target_artist_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "comment": { + "name": "comment", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "artist_crossref_source_target": { + "name": "artist_crossref_source_target", + "columns": [ + { + "expression": "source_artist_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_artist_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "artist_crossreference_source_artist_id_artists_id_fk": { + "name": "artist_crossreference_source_artist_id_artists_id_fk", + "tableFrom": "artist_crossreference", + "tableTo": "artists", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "source_artist_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "artist_crossreference_target_artist_id_artists_id_fk": { + "name": "artist_crossreference_target_artist_id_artists_id_fk", + "tableFrom": "artist_crossreference", + "tableTo": "artists", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "target_artist_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.artist_library_crossreference": { + "name": "artist_library_crossreference", + "schema": "wxyc_schema", + "columns": { + "artist_id": { + "name": "artist_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "comment": { + "name": "comment", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "library_id_artist_id": { + "name": "library_id_artist_id", + "columns": [ + { + "expression": "artist_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "artist_library_crossreference_artist_id_artists_id_fk": { + "name": "artist_library_crossreference_artist_id_artists_id_fk", + "tableFrom": "artist_library_crossreference", + "tableTo": "artists", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "artist_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "artist_library_crossreference_library_id_library_id_fk": { + "name": "artist_library_crossreference_library_id_library_id_fk", + "tableFrom": "artist_library_crossreference", + "tableTo": "library", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "library_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.artist_search_alias": { + "name": "artist_search_alias", + "schema": "wxyc_schema", + "columns": { + "artist_id": { + "name": "artist_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variant": { + "name": "variant", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "related_artist_id": { + "name": "related_artist_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "external_subject_id": { + "name": "external_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_object_id": { + "name": "external_object_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "confidence": { + "name": "confidence", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "last_verified_at": { + "name": "last_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "artist_search_alias_variant_trgm_idx": { + "name": "artist_search_alias_variant_trgm_idx", + "columns": [ + { + "expression": "\"variant\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "artist_search_alias_artist_id_artists_id_fk": { + "name": "artist_search_alias_artist_id_artists_id_fk", + "tableFrom": "artist_search_alias", + "tableTo": "artists", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "artist_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "artist_search_alias_related_artist_id_artists_id_fk": { + "name": "artist_search_alias_related_artist_id_artists_id_fk", + "tableFrom": "artist_search_alias", + "tableTo": "artists", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "related_artist_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "artist_search_alias_pkey": { + "name": "artist_search_alias_pkey", + "columns": [ + "artist_id", + "source", + "variant" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "artist_search_alias_confidence_range": { + "name": "artist_search_alias_confidence_range", + "value": "\"wxyc_schema\".\"artist_search_alias\".\"confidence\" BETWEEN 0 AND 1" + }, + "artist_search_alias_variant_nonblank": { + "name": "artist_search_alias_variant_nonblank", + "value": "length(trim(\"wxyc_schema\".\"artist_search_alias\".\"variant\")) > 0" + } + }, + "isRLSEnabled": false + }, + "wxyc_schema.artists": { + "name": "artists", + "schema": "wxyc_schema", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "artist_name": { + "name": "artist_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "alphabetical_name": { + "name": "alphabetical_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "code_letters": { + "name": "code_letters", + "type": "varchar(4)", + "primaryKey": false, + "notNull": true + }, + "add_date": { + "name": "add_date", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_modified": { + "name": "last_modified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "discogs_artist_id": { + "name": "discogs_artist_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "musicbrainz_artist_id": { + "name": "musicbrainz_artist_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "wikidata_qid": { + "name": "wikidata_qid", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "spotify_artist_id": { + "name": "spotify_artist_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "apple_music_artist_id": { + "name": "apple_music_artist_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "bandcamp_id": { + "name": "bandcamp_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "artist_name_trgm_idx": { + "name": "artist_name_trgm_idx", + "columns": [ + { + "expression": "\"artist_name\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "code_letters_idx": { + "name": "code_letters_idx", + "columns": [ + { + "expression": "code_letters", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.banned_fingerprints": { + "name": "banned_fingerprints", + "schema": "wxyc_schema", + "columns": { + "fingerprint": { + "name": "fingerprint", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "banned_at": { + "name": "banned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ban_expires_at": { + "name": "ban_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "banned_by_user_id": { + "name": "banned_by_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "banned_fingerprints_ban_expires_at_idx": { + "name": "banned_fingerprints_ban_expires_at_idx", + "columns": [ + { + "expression": "ban_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"wxyc_schema\".\"banned_fingerprints\".\"ban_expires_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "banned_fingerprints_banned_by_user_id_auth_user_id_fk": { + "name": "banned_fingerprints_banned_by_user_id_auth_user_id_fk", + "tableFrom": "banned_fingerprints", + "tableTo": "auth_user", + "columnsFrom": [ + "banned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.bins": { + "name": "bins", + "schema": "wxyc_schema", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "dj_id": { + "name": "dj_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "album_id": { + "name": "album_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "track_title": { + "name": "track_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "bins_dj_id_auth_user_id_fk": { + "name": "bins_dj_id_auth_user_id_fk", + "tableFrom": "bins", + "tableTo": "auth_user", + "columnsFrom": [ + "dj_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bins_album_id_library_id_fk": { + "name": "bins_album_id_library_id_fk", + "tableFrom": "bins", + "tableTo": "library", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "album_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.compilation_track_artist": { + "name": "compilation_track_artist", + "schema": "wxyc_schema", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "artist_name": { + "name": "artist_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "track_title": { + "name": "track_title", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "track_position": { + "name": "track_position", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "cta_library_id_idx": { + "name": "cta_library_id_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cta_artist_name_idx": { + "name": "cta_artist_name_idx", + "columns": [ + { + "expression": "artist_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cta_unique_idx": { + "name": "cta_unique_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "artist_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "track_title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "compilation_track_artist_library_id_library_id_fk": { + "name": "compilation_track_artist_library_id_library_id_fk", + "tableFrom": "compilation_track_artist", + "tableTo": "library", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "library_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.concerts": { + "name": "concerts", + "schema": "wxyc_schema", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "source": { + "name": "source", + "type": "concert_source_enum", + "typeSchema": "wxyc_schema", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "venue_id": { + "name": "venue_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "starts_at": { + "name": "starts_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "headlining_artist_raw": { + "name": "headlining_artist_raw", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "headlining_artist_id": { + "name": "headlining_artist_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "supporting_artists_raw": { + "name": "supporting_artists_raw", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "ticket_url": { + "name": "ticket_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "concert_status_enum", + "typeSchema": "wxyc_schema", + "primaryKey": false, + "notNull": true, + "default": "'on_sale'" + }, + "raw_data": { + "name": "raw_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "scraped_at": { + "name": "scraped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "first_scraped_at": { + "name": "first_scraped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_modified": { + "name": "last_modified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "concerts_source_source_id_idx": { + "name": "concerts_source_source_id_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "concerts_venue_starts_at_idx": { + "name": "concerts_venue_starts_at_idx", + "columns": [ + { + "expression": "venue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starts_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "concerts_headlining_artist_starts_at_idx": { + "name": "concerts_headlining_artist_starts_at_idx", + "columns": [ + { + "expression": "headlining_artist_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starts_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "concerts_venue_id_venues_id_fk": { + "name": "concerts_venue_id_venues_id_fk", + "tableFrom": "concerts", + "tableTo": "venues", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "venue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "concerts_headlining_artist_id_artists_id_fk": { + "name": "concerts_headlining_artist_id_artists_id_fk", + "tableFrom": "concerts", + "tableTo": "artists", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "headlining_artist_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.cronjob_runs": { + "name": "cronjob_runs", + "schema": "wxyc_schema", + "columns": { + "job_name": { + "name": "job_name", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "last_run": { + "name": "last_run", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.dj_stats": { + "name": "dj_stats", + "schema": "wxyc_schema", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "shows_covered": { + "name": "shows_covered", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "dj_stats_user_id_auth_user_id_fk": { + "name": "dj_stats_user_id_auth_user_id_fk", + "tableFrom": "dj_stats", + "tableTo": "auth_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.flowsheet": { + "name": "flowsheet", + "schema": "wxyc_schema", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "show_id": { + "name": "show_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "album_id": { + "name": "album_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rotation_id": { + "name": "rotation_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "legacy_entry_id": { + "name": "legacy_entry_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "legacy_release_id": { + "name": "legacy_release_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "entry_type": { + "name": "entry_type", + "type": "flowsheet_entry_type", + "typeSchema": "wxyc_schema", + "primaryKey": false, + "notNull": true, + "default": "'track'" + }, + "track_title": { + "name": "track_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "track_position": { + "name": "track_position", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "album_title": { + "name": "album_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "artist_name": { + "name": "artist_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "record_label": { + "name": "record_label", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "label_id": { + "name": "label_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "play_order": { + "name": "play_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "request_flag": { + "name": "request_flag", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "segue": { + "name": "segue", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "message": { + "name": "message", + "type": "varchar(250)", + "primaryKey": false, + "notNull": false + }, + "add_time": { + "name": "add_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "artwork_url": { + "name": "artwork_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "discogs_url": { + "name": "discogs_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "release_year": { + "name": "release_year", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "spotify_url": { + "name": "spotify_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "apple_music_url": { + "name": "apple_music_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "youtube_music_url": { + "name": "youtube_music_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "bandcamp_url": { + "name": "bandcamp_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "soundcloud_url": { + "name": "soundcloud_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "artist_bio": { + "name": "artist_bio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "artist_wikipedia_url": { + "name": "artist_wikipedia_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "dj_name": { + "name": "dj_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linkage_source": { + "name": "linkage_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linkage_confidence": { + "name": "linkage_confidence", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "linked_at": { + "name": "linked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "legacy_link_attempted_at": { + "name": "legacy_link_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata_attempt_at": { + "name": "metadata_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata_status": { + "name": "metadata_status", + "type": "metadata_status_enum", + "typeSchema": "wxyc_schema", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "enriching_since": { + "name": "enriching_since", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "search_doc": { + "name": "search_doc", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "setweight(to_tsvector('simple', coalesce(\"artist_name\", '')), 'A') || setweight(to_tsvector('simple', coalesce(\"track_title\", '')), 'B') || setweight(to_tsvector('simple', coalesce(\"dj_name\", '')), 'B') || setweight(to_tsvector('simple', coalesce(\"album_title\", '')), 'C') || setweight(to_tsvector('simple', coalesce(\"record_label\", '')), 'D')", + "type": "stored" + } + } + }, + "indexes": { + "flowsheet_legacy_entry_id_idx": { + "name": "flowsheet_legacy_entry_id_idx", + "columns": [ + { + "expression": "legacy_entry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "flowsheet_legacy_release_id_idx": { + "name": "flowsheet_legacy_release_id_idx", + "columns": [ + { + "expression": "legacy_release_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "flowsheet_artist_name_trgm_idx": { + "name": "flowsheet_artist_name_trgm_idx", + "columns": [ + { + "expression": "\"artist_name\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "flowsheet_track_title_trgm_idx": { + "name": "flowsheet_track_title_trgm_idx", + "columns": [ + { + "expression": "\"track_title\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "flowsheet_album_title_trgm_idx": { + "name": "flowsheet_album_title_trgm_idx", + "columns": [ + { + "expression": "\"album_title\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "flowsheet_record_label_trgm_idx": { + "name": "flowsheet_record_label_trgm_idx", + "columns": [ + { + "expression": "\"record_label\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "flowsheet_track_add_time_idx": { + "name": "flowsheet_track_add_time_idx", + "columns": [ + { + "expression": "\"add_time\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"wxyc_schema\".\"flowsheet\".\"entry_type\" = 'track'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "flowsheet_search_doc_idx": { + "name": "flowsheet_search_doc_idx", + "columns": [ + { + "expression": "\"search_doc\"", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "flowsheet_album_link_lookup_idx": { + "name": "flowsheet_album_link_lookup_idx", + "columns": [ + { + "expression": "(lower(trim(\"artist_name\")) || '-' || lower(trim(coalesce(\"album_title\", ''))))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"wxyc_schema\".\"flowsheet\".\"album_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "flowsheet_show_id_idx": { + "name": "flowsheet_show_id_idx", + "columns": [ + { + "expression": "show_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "flowsheet_play_order_idx": { + "name": "flowsheet_play_order_idx", + "columns": [ + { + "expression": "\"play_order\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "flowsheet_updated_at_idx": { + "name": "flowsheet_updated_at_idx", + "columns": [ + { + "expression": "\"updated_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "flowsheet_metadata_attempt_pending_idx": { + "name": "flowsheet_metadata_attempt_pending_idx", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"wxyc_schema\".\"flowsheet\".\"entry_type\" = 'track' AND \"wxyc_schema\".\"flowsheet\".\"artist_name\" IS NOT NULL AND \"wxyc_schema\".\"flowsheet\".\"metadata_attempt_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "flowsheet_metadata_attempt_pending_covering_idx": { + "name": "flowsheet_metadata_attempt_pending_covering_idx", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"wxyc_schema\".\"flowsheet\".\"entry_type\" = 'track' AND \"wxyc_schema\".\"flowsheet\".\"artist_name\" IS NOT NULL AND \"wxyc_schema\".\"flowsheet\".\"metadata_attempt_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "flowsheet_metadata_status_pending_idx": { + "name": "flowsheet_metadata_status_pending_idx", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"wxyc_schema\".\"flowsheet\".\"entry_type\" = 'track' AND \"wxyc_schema\".\"flowsheet\".\"artist_name\" IS NOT NULL AND \"wxyc_schema\".\"flowsheet\".\"metadata_status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "flowsheet_metadata_status_enriching_stale_idx": { + "name": "flowsheet_metadata_status_enriching_stale_idx", + "columns": [ + { + "expression": "enriching_since", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"wxyc_schema\".\"flowsheet\".\"metadata_status\" = 'enriching'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "flowsheet_album_id_enriched_idx": { + "name": "flowsheet_album_id_enriched_idx", + "columns": [ + { + "expression": "album_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"wxyc_schema\".\"flowsheet\".\"album_id\" IS NOT NULL AND \"wxyc_schema\".\"flowsheet\".\"metadata_attempt_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "flowsheet_show_id_shows_id_fk": { + "name": "flowsheet_show_id_shows_id_fk", + "tableFrom": "flowsheet", + "tableTo": "shows", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "show_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "flowsheet_album_id_library_id_fk": { + "name": "flowsheet_album_id_library_id_fk", + "tableFrom": "flowsheet", + "tableTo": "library", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "album_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "flowsheet_rotation_id_rotation_id_fk": { + "name": "flowsheet_rotation_id_rotation_id_fk", + "tableFrom": "flowsheet", + "tableTo": "rotation", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "rotation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "flowsheet_label_id_labels_id_fk": { + "name": "flowsheet_label_id_labels_id_fk", + "tableFrom": "flowsheet", + "tableTo": "labels", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.flowsheet_linkage_review": { + "name": "flowsheet_linkage_review", + "schema": "wxyc_schema", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "flowsheet_id": { + "name": "flowsheet_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "candidate_library_ids": { + "name": "candidate_library_ids", + "type": "integer[]", + "primaryKey": false, + "notNull": true + }, + "candidate_confidences": { + "name": "candidate_confidences", + "type": "real[]", + "primaryKey": false, + "notNull": true + }, + "suggested_action": { + "name": "suggested_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reviewed_at": { + "name": "reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reviewed_decision": { + "name": "reviewed_decision", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "flowsheet_linkage_review_unreviewed_idx": { + "name": "flowsheet_linkage_review_unreviewed_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"wxyc_schema\".\"flowsheet_linkage_review\".\"reviewed_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "flowsheet_linkage_review_flowsheet_id_flowsheet_id_fk": { + "name": "flowsheet_linkage_review_flowsheet_id_flowsheet_id_fk", + "tableFrom": "flowsheet_linkage_review", + "tableTo": "flowsheet", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "flowsheet_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "flowsheet_linkage_review_flowsheet_id_unique": { + "name": "flowsheet_linkage_review_flowsheet_id_unique", + "nullsNotDistinct": false, + "columns": [ + "flowsheet_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.flowsheet_watermark": { + "name": "flowsheet_watermark", + "schema": "wxyc_schema", + "columns": { + "id": { + "name": "id", + "type": "boolean", + "primaryKey": true, + "notNull": true, + "default": true + }, + "last_modified_at": { + "name": "last_modified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "flowsheet_watermark_singleton": { + "name": "flowsheet_watermark_singleton", + "value": "\"id\" = true" + } + }, + "isRLSEnabled": false + }, + "wxyc_schema.format": { + "name": "format", + "schema": "wxyc_schema", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "format_name": { + "name": "format_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "add_date": { + "name": "add_date", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.genre_artist_crossreference": { + "name": "genre_artist_crossreference", + "schema": "wxyc_schema", + "columns": { + "artist_id": { + "name": "artist_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "genre_id": { + "name": "genre_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "artist_genre_code": { + "name": "artist_genre_code", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "artist_genre_key": { + "name": "artist_genre_key", + "columns": [ + { + "expression": "artist_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "genre_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "genre_artist_crossreference_artist_id_artists_id_fk": { + "name": "genre_artist_crossreference_artist_id_artists_id_fk", + "tableFrom": "genre_artist_crossreference", + "tableTo": "artists", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "artist_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "genre_artist_crossreference_genre_id_genres_id_fk": { + "name": "genre_artist_crossreference_genre_id_genres_id_fk", + "tableFrom": "genre_artist_crossreference", + "tableTo": "genres", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "genre_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.genres": { + "name": "genres", + "schema": "wxyc_schema", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "genre_name": { + "name": "genre_name", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plays": { + "name": "plays", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "add_date": { + "name": "add_date", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_modified": { + "name": "last_modified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_invitation": { + "name": "auth_invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "auth_invitation_email_idx": { + "name": "auth_invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auth_invitation_organization_id_auth_organization_id_fk": { + "name": "auth_invitation_organization_id_auth_organization_id_fk", + "tableFrom": "auth_invitation", + "tableTo": "auth_organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auth_invitation_inviter_id_auth_user_id_fk": { + "name": "auth_invitation_inviter_id_auth_user_id_fk", + "tableFrom": "auth_invitation", + "tableTo": "auth_user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_jwks": { + "name": "auth_jwks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.labels": { + "name": "labels", + "schema": "wxyc_schema", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "label_name": { + "name": "label_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "parent_label_id": { + "name": "parent_label_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "labels_label_name_unique": { + "name": "labels_label_name_unique", + "nullsNotDistinct": false, + "columns": [ + "label_name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.library": { + "name": "library", + "schema": "wxyc_schema", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "artist_id": { + "name": "artist_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "genre_id": { + "name": "genre_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "format_id": { + "name": "format_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "alternate_artist_name": { + "name": "alternate_artist_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "album_artist": { + "name": "album_artist", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "album_title": { + "name": "album_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "label_id": { + "name": "label_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "code_number": { + "name": "code_number", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "code_volume_letters": { + "name": "code_volume_letters", + "type": "varchar(4)", + "primaryKey": false, + "notNull": false + }, + "disc_quantity": { + "name": "disc_quantity", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "plays": { + "name": "plays", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "legacy_release_id": { + "name": "legacy_release_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "add_date": { + "name": "add_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_modified": { + "name": "last_modified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "date_lost": { + "name": "date_lost", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "date_found": { + "name": "date_found", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "on_streaming": { + "name": "on_streaming", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "artwork_url": { + "name": "artwork_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "artist_name": { + "name": "artist_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "canonical_entity_id": { + "name": "canonical_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_entity_confidence": { + "name": "canonical_entity_confidence", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "canonical_entity_resolved_at": { + "name": "canonical_entity_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "search_doc": { + "name": "search_doc", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "setweight(to_tsvector('simple', coalesce(\"artist_name\", '')), 'A') || setweight(to_tsvector('simple', coalesce(\"album_title\", '')), 'B')", + "type": "stored" + } + } + }, + "indexes": { + "title_trgm_idx": { + "name": "title_trgm_idx", + "columns": [ + { + "expression": "\"album_title\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "genre_id_idx": { + "name": "genre_id_idx", + "columns": [ + { + "expression": "genre_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "format_id_idx": { + "name": "format_id_idx", + "columns": [ + { + "expression": "format_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "artist_id_idx": { + "name": "artist_id_idx", + "columns": [ + { + "expression": "artist_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "library_legacy_release_id_idx": { + "name": "library_legacy_release_id_idx", + "columns": [ + { + "expression": "legacy_release_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "album_artist_trgm_idx": { + "name": "album_artist_trgm_idx", + "columns": [ + { + "expression": "\"album_artist\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "library_artist_name_trgm_idx": { + "name": "library_artist_name_trgm_idx", + "columns": [ + { + "expression": "\"artist_name\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "library_search_doc_idx": { + "name": "library_search_doc_idx", + "columns": [ + { + "expression": "\"search_doc\"", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "library_canonical_entity_id_idx": { + "name": "library_canonical_entity_id_idx", + "columns": [ + { + "expression": "canonical_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "library_artist_id_artists_id_fk": { + "name": "library_artist_id_artists_id_fk", + "tableFrom": "library", + "tableTo": "artists", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "artist_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "library_genre_id_genres_id_fk": { + "name": "library_genre_id_genres_id_fk", + "tableFrom": "library", + "tableTo": "genres", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "genre_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "library_format_id_format_id_fk": { + "name": "library_format_id_format_id_fk", + "tableFrom": "library", + "tableTo": "format", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "format_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "library_label_id_labels_id_fk": { + "name": "library_label_id_labels_id_fk", + "tableFrom": "library", + "tableTo": "labels", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.library_identity": { + "name": "library_identity", + "schema": "wxyc_schema", + "columns": { + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "discogs_master_id": { + "name": "discogs_master_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "discogs_release_id": { + "name": "discogs_release_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "musicbrainz_release_group_mbid": { + "name": "musicbrainz_release_group_mbid", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "musicbrainz_release_mbid": { + "name": "musicbrainz_release_mbid", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "musicbrainz_recording_mbid": { + "name": "musicbrainz_recording_mbid", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "wikidata_qid": { + "name": "wikidata_qid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "spotify_id": { + "name": "spotify_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "apple_music_id": { + "name": "apple_music_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_verified_at": { + "name": "last_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "confidence": { + "name": "confidence", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "agreement_sources": { + "name": "agreement_sources", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "distinct_unresolved_sources": { + "name": "distinct_unresolved_sources", + "type": "integer", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "(\n (CASE WHEN \"discogs_master_id\" IS NULL THEN 1 ELSE 0 END)\n + (CASE WHEN \"discogs_release_id\" IS NULL THEN 1 ELSE 0 END)\n + (CASE WHEN \"musicbrainz_release_group_mbid\" IS NULL THEN 1 ELSE 0 END)\n + (CASE WHEN \"musicbrainz_release_mbid\" IS NULL THEN 1 ELSE 0 END)\n + (CASE WHEN \"musicbrainz_recording_mbid\" IS NULL THEN 1 ELSE 0 END)\n + (CASE WHEN \"wikidata_qid\" IS NULL THEN 1 ELSE 0 END)\n + (CASE WHEN \"spotify_id\" IS NULL THEN 1 ELSE 0 END)\n + (CASE WHEN \"apple_music_id\" IS NULL THEN 1 ELSE 0 END)\n )", + "type": "stored" + } + } + }, + "indexes": { + "library_identity_audit_idx": { + "name": "library_identity_audit_idx", + "columns": [ + { + "expression": "confidence", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"distinct_unresolved_sources\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "library_identity_library_id_library_id_fk": { + "name": "library_identity_library_id_library_id_fk", + "tableFrom": "library_identity", + "tableTo": "library", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "library_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "library_identity_confidence_range": { + "name": "library_identity_confidence_range", + "value": "\"wxyc_schema\".\"library_identity\".\"confidence\" BETWEEN 0 AND 1" + } + }, + "isRLSEnabled": false + }, + "wxyc_schema.library_identity_history": { + "name": "library_identity_history", + "schema": "wxyc_schema", + "columns": { + "history_id": { + "name": "history_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "discogs_master_id": { + "name": "discogs_master_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "discogs_release_id": { + "name": "discogs_release_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "musicbrainz_release_group_mbid": { + "name": "musicbrainz_release_group_mbid", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "musicbrainz_release_mbid": { + "name": "musicbrainz_release_mbid", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "musicbrainz_recording_mbid": { + "name": "musicbrainz_recording_mbid", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "wikidata_qid": { + "name": "wikidata_qid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "spotify_id": { + "name": "spotify_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "apple_music_id": { + "name": "apple_music_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_verified_at": { + "name": "last_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confidence": { + "name": "confidence", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "agreement_sources": { + "name": "agreement_sources", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "superseded_at": { + "name": "superseded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "superseded_reason": { + "name": "superseded_reason", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason_category": { + "name": "reason_category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.library_identity_source": { + "name": "library_identity_source", + "schema": "wxyc_schema", + "columns": { + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "confidence": { + "name": "confidence", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "last_verified_at": { + "name": "last_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "boost_sources": { + "name": "boost_sources", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "library_identity_source_library_id_library_id_fk": { + "name": "library_identity_source_library_id_library_id_fk", + "tableFrom": "library_identity_source", + "tableTo": "library", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "library_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "library_identity_source_library_id_source_pk": { + "name": "library_identity_source_library_id_source_pk", + "columns": [ + "library_id", + "source" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "library_identity_source_confidence_range": { + "name": "library_identity_source_confidence_range", + "value": "\"wxyc_schema\".\"library_identity_source\".\"confidence\" BETWEEN 0 AND 1" + } + }, + "isRLSEnabled": false + }, + "public.auth_member": { + "name": "auth_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "auth_member_org_user_key": { + "name": "auth_member_org_user_key", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auth_member_organization_id_auth_organization_id_fk": { + "name": "auth_member_organization_id_auth_organization_id_fk", + "tableFrom": "auth_member", + "tableTo": "auth_organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auth_member_user_id_auth_user_id_fk": { + "name": "auth_member_user_id_auth_user_id_fk", + "tableFrom": "auth_member", + "tableTo": "auth_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_organization": { + "name": "auth_organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "auth_organization_slug_key": { + "name": "auth_organization_slug_key", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.reviews": { + "name": "reviews", + "schema": "wxyc_schema", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "album_id": { + "name": "album_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "review": { + "name": "review", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "add_date": { + "name": "add_date", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_modified": { + "name": "last_modified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "author": { + "name": "author", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "reviews_album_id_library_id_fk": { + "name": "reviews_album_id_library_id_fk", + "tableFrom": "reviews", + "tableTo": "library", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "album_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reviews_album_id_unique": { + "name": "reviews_album_id_unique", + "nullsNotDistinct": false, + "columns": [ + "album_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.rotation": { + "name": "rotation", + "schema": "wxyc_schema", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "album_id": { + "name": "album_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "legacy_rotation_id": { + "name": "legacy_rotation_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "legacy_library_release_id": { + "name": "legacy_library_release_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rotation_bin": { + "name": "rotation_bin", + "type": "freq_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "add_date": { + "name": "add_date", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "kill_date": { + "name": "kill_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "artist_name": { + "name": "artist_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "album_title": { + "name": "album_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "record_label": { + "name": "record_label", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "discogs_release_id": { + "name": "discogs_release_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "discogs_release_id_source": { + "name": "discogs_release_id_source", + "type": "discogs_release_id_source_enum", + "typeSchema": "wxyc_schema", + "primaryKey": false, + "notNull": true, + "default": "'tubafrenzy_paste'" + }, + "tracklist_lookup_attempted_at": { + "name": "tracklist_lookup_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "lml_identity_id": { + "name": "lml_identity_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "album_id_idx": { + "name": "album_id_idx", + "columns": [ + { + "expression": "album_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "rotation_legacy_rotation_id_idx": { + "name": "rotation_legacy_rotation_id_idx", + "columns": [ + { + "expression": "legacy_rotation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "rotation_album_id_library_id_fk": { + "name": "rotation_album_id_library_id_fk", + "tableFrom": "rotation", + "tableTo": "library", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "album_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.schedule": { + "name": "schedule", + "schema": "wxyc_schema", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "day": { + "name": "day", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "start_time": { + "name": "start_time", + "type": "time", + "primaryKey": false, + "notNull": true + }, + "show_duration": { + "name": "show_duration", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "specialty_id": { + "name": "specialty_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "assigned_dj_id": { + "name": "assigned_dj_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "assigned_dj_id2": { + "name": "assigned_dj_id2", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "schedule_specialty_id_specialty_shows_id_fk": { + "name": "schedule_specialty_id_specialty_shows_id_fk", + "tableFrom": "schedule", + "tableTo": "specialty_shows", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "specialty_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "schedule_assigned_dj_id_auth_user_id_fk": { + "name": "schedule_assigned_dj_id_auth_user_id_fk", + "tableFrom": "schedule", + "tableTo": "auth_user", + "columnsFrom": [ + "assigned_dj_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "schedule_assigned_dj_id2_auth_user_id_fk": { + "name": "schedule_assigned_dj_id2_auth_user_id_fk", + "tableFrom": "schedule", + "tableTo": "auth_user", + "columnsFrom": [ + "assigned_dj_id2" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_session": { + "name": "auth_session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ip_address": { + "name": "ip_address", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "auth_session_token_key": { + "name": "auth_session_token_key", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auth_session_user_id_auth_user_id_fk": { + "name": "auth_session_user_id_auth_user_id_fk", + "tableFrom": "auth_session", + "tableTo": "auth_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.shift_covers": { + "name": "shift_covers", + "schema": "wxyc_schema", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "shift_timestamp": { + "name": "shift_timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "cover_dj_id": { + "name": "cover_dj_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "covered": { + "name": "covered", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "shift_covers_schedule_id_schedule_id_fk": { + "name": "shift_covers_schedule_id_schedule_id_fk", + "tableFrom": "shift_covers", + "tableTo": "schedule", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "schedule_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "shift_covers_cover_dj_id_auth_user_id_fk": { + "name": "shift_covers_cover_dj_id_auth_user_id_fk", + "tableFrom": "shift_covers", + "tableTo": "auth_user", + "columnsFrom": [ + "cover_dj_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.show_djs": { + "name": "show_djs", + "schema": "wxyc_schema", + "columns": { + "show_id": { + "name": "show_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "dj_id": { + "name": "dj_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + } + }, + "indexes": { + "show_djs_show_id_dj_id_unique": { + "name": "show_djs_show_id_dj_id_unique", + "columns": [ + { + "expression": "show_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dj_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "show_djs_show_id_shows_id_fk": { + "name": "show_djs_show_id_shows_id_fk", + "tableFrom": "show_djs", + "tableTo": "shows", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "show_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "show_djs_dj_id_auth_user_id_fk": { + "name": "show_djs_dj_id_auth_user_id_fk", + "tableFrom": "show_djs", + "tableTo": "auth_user", + "columnsFrom": [ + "dj_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.shows": { + "name": "shows", + "schema": "wxyc_schema", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "primary_dj_id": { + "name": "primary_dj_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "specialty_id": { + "name": "specialty_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "legacy_show_id": { + "name": "legacy_show_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "legacy_dj_name": { + "name": "legacy_dj_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "legacy_dj_id": { + "name": "legacy_dj_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "dj_name_override": { + "name": "dj_name_override", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "show_name": { + "name": "show_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "start_time": { + "name": "start_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "end_time": { + "name": "end_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "shows_legacy_show_id_idx": { + "name": "shows_legacy_show_id_idx", + "columns": [ + { + "expression": "legacy_show_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shows_primary_dj_id_auth_user_id_fk": { + "name": "shows_primary_dj_id_auth_user_id_fk", + "tableFrom": "shows", + "tableTo": "auth_user", + "columnsFrom": [ + "primary_dj_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shows_specialty_id_specialty_shows_id_fk": { + "name": "shows_specialty_id_specialty_shows_id_fk", + "tableFrom": "shows", + "tableTo": "specialty_shows", + "schemaTo": "wxyc_schema", + "columnsFrom": [ + "specialty_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.specialty_shows": { + "name": "specialty_shows", + "schema": "wxyc_schema", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "specialty_name": { + "name": "specialty_name", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "add_date": { + "name": "add_date", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_modified": { + "name": "last_modified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_user": { + "name": "auth_user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "role": { + "name": "role", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "display_username": { + "name": "display_username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "real_name": { + "name": "real_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "dj_name": { + "name": "dj_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "app_skin": { + "name": "app_skin", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'modern-light'" + }, + "is_anonymous": { + "name": "is_anonymous", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "has_completed_onboarding": { + "name": "has_completed_onboarding", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "capabilities": { + "name": "capabilities", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + } + }, + "indexes": { + "auth_user_email_key": { + "name": "auth_user_email_key", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auth_user_username_key": { + "name": "auth_user_username_key", + "columns": [ + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_activity": { + "name": "user_activity", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_activity_user_id_auth_user_id_fk": { + "name": "user_activity_user_id_auth_user_id_fk", + "tableFrom": "user_activity", + "tableTo": "auth_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "wxyc_schema.venues": { + "name": "venues", + "schema": "wxyc_schema", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "city": { + "name": "city", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "address": { + "name": "address", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "added_at": { + "name": "added_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_modified": { + "name": "last_modified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "venues_slug_idx": { + "name": "venues_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_verification": { + "name": "auth_verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "wxyc_schema.concert_source_enum": { + "name": "concert_source_enum", + "schema": "wxyc_schema", + "values": [ + "rhp_scrape" + ] + }, + "wxyc_schema.concert_status_enum": { + "name": "concert_status_enum", + "schema": "wxyc_schema", + "values": [ + "on_sale", + "sold_out", + "cancelled", + "rescheduled" + ] + }, + "wxyc_schema.discogs_release_id_source_enum": { + "name": "discogs_release_id_source_enum", + "schema": "wxyc_schema", + "values": [ + "tubafrenzy_paste", + "lml_offline_backfill", + "discogs_direct_backfill", + "library_identity" + ] + }, + "wxyc_schema.flowsheet_entry_type": { + "name": "flowsheet_entry_type", + "schema": "wxyc_schema", + "values": [ + "track", + "show_start", + "show_end", + "dj_join", + "dj_leave", + "talkset", + "breakpoint", + "message" + ] + }, + "public.freq_enum": { + "name": "freq_enum", + "schema": "public", + "values": [ + "S", + "L", + "M", + "H", + "N" + ] + }, + "wxyc_schema.metadata_status_enum": { + "name": "metadata_status_enum", + "schema": "wxyc_schema", + "values": [ + "pending", + "enriching", + "enriched_match", + "enriched_no_match", + "failed_no_retry" + ] + } + }, + "schemas": { + "wxyc_schema": "wxyc_schema" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": { + "wxyc_schema.library_artist_view": { + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code_letters": { + "name": "code_letters", + "type": "varchar(4)", + "primaryKey": false, + "notNull": true + }, + "artist_genre_code": { + "name": "artist_genre_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "code_number": { + "name": "code_number", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "artist_name": { + "name": "artist_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "alphabetical_name": { + "name": "alphabetical_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "album_title": { + "name": "album_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "format_name": { + "name": "format_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "genre_name": { + "name": "genre_name", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "rotation_bin": { + "name": "rotation_bin", + "type": "freq_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "add_date": { + "name": "add_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "label": { + "name": "label", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "label_id": { + "name": "label_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "on_streaming": { + "name": "on_streaming", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "album_artist": { + "name": "album_artist", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "plays": { + "name": "plays", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "artwork_url": { + "name": "artwork_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "discogs_artist_id": { + "name": "discogs_artist_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "musicbrainz_artist_id": { + "name": "musicbrainz_artist_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "wikidata_qid": { + "name": "wikidata_qid", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "spotify_artist_id": { + "name": "spotify_artist_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "apple_music_artist_id": { + "name": "apple_music_artist_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "bandcamp_id": { + "name": "bandcamp_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "artist_id": { + "name": "artist_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "definition": "select \"wxyc_schema\".\"library\".\"id\", \"wxyc_schema\".\"artists\".\"code_letters\", \"wxyc_schema\".\"genre_artist_crossreference\".\"artist_genre_code\", \"wxyc_schema\".\"library\".\"code_number\", \"wxyc_schema\".\"artists\".\"artist_name\", \"wxyc_schema\".\"artists\".\"alphabetical_name\", \"wxyc_schema\".\"library\".\"album_title\", \"wxyc_schema\".\"format\".\"format_name\", \"wxyc_schema\".\"genres\".\"genre_name\", \"wxyc_schema\".\"rotation\".\"rotation_bin\", \"wxyc_schema\".\"library\".\"add_date\", \"wxyc_schema\".\"library\".\"label\", \"wxyc_schema\".\"library\".\"label_id\", \"wxyc_schema\".\"library\".\"on_streaming\", \"wxyc_schema\".\"library\".\"album_artist\", \"wxyc_schema\".\"library\".\"plays\", \"wxyc_schema\".\"library\".\"artwork_url\", \"wxyc_schema\".\"artists\".\"discogs_artist_id\", \"wxyc_schema\".\"artists\".\"musicbrainz_artist_id\", \"wxyc_schema\".\"artists\".\"wikidata_qid\", \"wxyc_schema\".\"artists\".\"spotify_artist_id\", \"wxyc_schema\".\"artists\".\"apple_music_artist_id\", \"wxyc_schema\".\"artists\".\"bandcamp_id\", \"wxyc_schema\".\"library\".\"artist_id\" from \"wxyc_schema\".\"library\" inner join \"wxyc_schema\".\"artists\" on \"wxyc_schema\".\"artists\".\"id\" = \"wxyc_schema\".\"library\".\"artist_id\" inner join \"wxyc_schema\".\"format\" on \"wxyc_schema\".\"format\".\"id\" = \"wxyc_schema\".\"library\".\"format_id\" inner join \"wxyc_schema\".\"genres\" on \"wxyc_schema\".\"genres\".\"id\" = \"wxyc_schema\".\"library\".\"genre_id\" inner join \"wxyc_schema\".\"genre_artist_crossreference\" on (\"wxyc_schema\".\"genre_artist_crossreference\".\"artist_id\" = \"wxyc_schema\".\"library\".\"artist_id\" and \"wxyc_schema\".\"genre_artist_crossreference\".\"genre_id\" = \"wxyc_schema\".\"library\".\"genre_id\") left join \"wxyc_schema\".\"rotation\" on \"wxyc_schema\".\"rotation\".\"album_id\" = \"wxyc_schema\".\"library\".\"id\" AND (\"wxyc_schema\".\"rotation\".\"kill_date\" > CURRENT_DATE OR \"wxyc_schema\".\"rotation\".\"kill_date\" IS NULL)", + "name": "library_artist_view", + "schema": "wxyc_schema", + "isExisting": false, + "materialized": false + }, + "wxyc_schema.rotation_library_view": { + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "label_id": { + "name": "label_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rotation_bin": { + "name": "rotation_bin", + "type": "freq_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "album_title": { + "name": "album_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "artist_name": { + "name": "artist_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "alphabetical_name": { + "name": "alphabetical_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "kill_date": { + "name": "kill_date", + "type": "date", + "primaryKey": false, + "notNull": false + } + }, + "definition": "select \"wxyc_schema\".\"library\".\"id\", \"wxyc_schema\".\"rotation\".\"id\", \"wxyc_schema\".\"library\".\"label\", \"wxyc_schema\".\"library\".\"label_id\", \"wxyc_schema\".\"rotation\".\"rotation_bin\", \"wxyc_schema\".\"library\".\"album_title\", \"wxyc_schema\".\"artists\".\"artist_name\", \"wxyc_schema\".\"artists\".\"alphabetical_name\", \"wxyc_schema\".\"rotation\".\"kill_date\" from \"wxyc_schema\".\"library\" inner join \"wxyc_schema\".\"rotation\" on \"wxyc_schema\".\"library\".\"id\" = \"wxyc_schema\".\"rotation\".\"album_id\" inner join \"wxyc_schema\".\"artists\" on \"wxyc_schema\".\"artists\".\"id\" = \"wxyc_schema\".\"library\".\"artist_id\"", + "name": "rotation_library_view", + "schema": "wxyc_schema", + "isExisting": false, + "materialized": false + }, + "wxyc_schema.album_plays": { + "columns": { + "album_id": { + "name": "album_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "plays": { + "name": "plays", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "name": "album_plays", + "schema": "wxyc_schema", + "isExisting": true, + "materialized": true + } + }, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/shared/database/src/migrations/meta/_journal.json b/shared/database/src/migrations/meta/_journal.json index db4aa77f..da3bd209 100644 --- a/shared/database/src/migrations/meta/_journal.json +++ b/shared/database/src/migrations/meta/_journal.json @@ -666,6 +666,13 @@ "when": 1781468384341, "tag": "0096_cdc_oversized_fallback", "breakpoints": true + }, + { + "idx": 97, + "version": "7", + "when": 1781468384342, + "tag": "0097_fix-fk-on-delete-flowsheet-rotation-reviews", + "breakpoints": true } ] } diff --git a/shared/database/src/migrations/meta/applied-hashes.json b/shared/database/src/migrations/meta/applied-hashes.json index 7816c2eb..276f6d47 100644 --- a/shared/database/src/migrations/meta/applied-hashes.json +++ b/shared/database/src/migrations/meta/applied-hashes.json @@ -93,5 +93,6 @@ "0093_concerts-first-scraped-at": "bbeee146c92ab63fa70e9f2c537d5a06a13f80a5f52c23cb5a1bed1432057421", "0094_rotation-lml-identity-id": "10134a18381f7daad56ca2d8cae80723832be3158cbe6dd01aae2f8e6afcaaa0", "0095_drop-stale-trigram-indexes": "aba5b26a66102d427e1abd6520aa1f7ef14b776eaaa87ea8b99c3b97e79d24c7", - "0096_cdc_oversized_fallback": "5615c7a6aa7514b139340ef6c8ba86a548ac6e7b6f9f0c39241bea2a62a1274b" + "0096_cdc_oversized_fallback": "5615c7a6aa7514b139340ef6c8ba86a548ac6e7b6f9f0c39241bea2a62a1274b", + "0097_fix-fk-on-delete-flowsheet-rotation-reviews": "f13d8eed11d7da774cbdf92abe12cd45440ac7bff594226364c387bd378e4224" } diff --git a/tests/integration/fk-on-delete-flowsheet-rotation-reviews.spec.js b/tests/integration/fk-on-delete-flowsheet-rotation-reviews.spec.js new file mode 100644 index 00000000..903ce814 --- /dev/null +++ b/tests/integration/fk-on-delete-flowsheet-rotation-reviews.spec.js @@ -0,0 +1,169 @@ +/** + * FK ON DELETE behaviour on flowsheet / rotation / reviews + * (WXYC/Backend-Service#1126, migration 0097). + * + * Five FK constraints drifted between the Drizzle schema source (SET NULL + * for the three flowsheet FKs, CASCADE for rotation.album_id and + * reviews.album_id) and the actual migration history (ON DELETE NO ACTION + * via 0000_rare_prima.sql, recreated unchanged by 0016_nervous_hydra.sql). + * Because the latest snapshot already records the schema-source values, + * `drizzle-kit generate` produced no fix migration — the drift was + * invisible to the normal authoring loop. Migration 0094 patches it. + * + * This spec is the live regression test: it asserts the current + * `pg_constraint.confdeltype` for each of the five FKs (mirroring the + * issue body's reproduction query) AND exercises each of the three + * actual ON DELETE behaviours (flowsheet SET NULL, rotation CASCADE, + * reviews CASCADE) via a parent-row DELETE under transaction rollback + * (so the shape fixture loaded by globalSetup is not perturbed). + * + * If a future schema change re-introduces the drift, this spec fails + * before the migration is ever attempted against prod. + */ + +const { getTestDb, withRollback } = require('../utils/db'); + +const SCHEMA = process.env.WXYC_SCHEMA_NAME || 'wxyc_schema'; + +// Map from pg_constraint.confdeltype's single-char encoding to the SQL +// keyword we expect, for readable assertion failure messages. +const CONFDELTYPE_LABEL = { + a: 'NO ACTION', + r: 'RESTRICT', + c: 'CASCADE', + n: 'SET NULL', + d: 'SET DEFAULT', +}; + +// Seeded baseline (dev_env/seed_db.sql): artists 1-3, genres 1-15 +// (rock=11), formats include cd=1 / vinyl=2. The shape fixture +// (tests/fixtures/shape.sql) advances the library/rotation/flowsheet +// sequences to 7199 so any serial-assigned id below lands at 7200+, +// safely above the explicit-id fixture range (7000-7099). Each +// behavioural test wraps its INSERT/DELETE in a withRollback transaction +// so the fixture stays intact for downstream specs. +const SEED_ARTIST_ID = 1; +const SEED_GENRE_ID = 11; +const SEED_FORMAT_ID = 1; + +describe('FK ON DELETE on flowsheet / rotation / reviews (#1126, migration 0097)', () => { + let sql; + + beforeAll(() => { + sql = getTestDb(); + }); + + test('pg_constraint.confdeltype matches the schema-source declarations', async () => { + // The five FKs the migration repairs, with the action declared in + // shared/database/src/schema.ts. + const expectations = [ + { name: 'flowsheet_show_id_shows_id_fk', expected: 'n' }, // SET NULL + { name: 'flowsheet_album_id_library_id_fk', expected: 'n' }, // SET NULL + { name: 'flowsheet_rotation_id_rotation_id_fk', expected: 'n' }, // SET NULL + { name: 'rotation_album_id_library_id_fk', expected: 'c' }, // CASCADE + { name: 'reviews_album_id_library_id_fk', expected: 'c' }, // CASCADE + ]; + + // Constraint namespace is the per-worker schema (WXYC_SCHEMA_NAME). We + // filter by both the constraint name and the namespace so a stale + // constraint in `wxyc_schema` doesn't bleed into a worker schema's + // assertions. postgres-js binds a JS array as a Postgres text[] in the + // `ANY(${arr})` position (see flowsheet-etl-setwhere.spec.js:61, + // album-metadata-upsert.spec.js:159 for the canonical idiom). + const constraintNames = expectations.map((e) => e.name); + const rows = await sql` + SELECT c.conname, c.confdeltype + FROM pg_constraint c + JOIN pg_namespace n ON n.oid = c.connamespace + WHERE n.nspname = ${SCHEMA} + AND c.conname = ANY(${constraintNames}) + `; + const observed = new Map(rows.map((r) => [r.conname, r.confdeltype])); + + for (const { name, expected } of expectations) { + const actual = observed.get(name); + expect({ + constraint: name, + actual: `${actual ?? '(missing)'} (${CONFDELTYPE_LABEL[actual] ?? 'unknown'})`, + expected: `${expected} (${CONFDELTYPE_LABEL[expected]})`, + }).toEqual({ + constraint: name, + actual: `${expected} (${CONFDELTYPE_LABEL[expected]})`, + expected: `${expected} (${CONFDELTYPE_LABEL[expected]})`, + }); + } + }); + + test('DELETE on a library row sets flowsheet.album_id to NULL (was NO ACTION)', async () => { + await withRollback(async (tx) => { + // library: artist_id, genre_id, format_id, album_title, code_number + // are all NOT NULL (see shared/database/src/schema.ts:344). + // Let `serial` auto-assign id from the post-fixture sequence floor + // (7200+); the explicit-id fixture range stops at 7099. + const [lib] = await tx` + INSERT INTO ${tx(SCHEMA)}.library (artist_id, genre_id, format_id, album_title, code_number, artist_name) + VALUES (${SEED_ARTIST_ID}, ${SEED_GENRE_ID}, ${SEED_FORMAT_ID}, + 'FK Test Album SET NULL', 9001, 'FK Test Artist') + RETURNING id + `; + // flowsheet: entry_type defaults to 'track'; play_order is NOT NULL + // with no default (schema.ts:679). + const [fls] = await tx` + INSERT INTO ${tx(SCHEMA)}.flowsheet (album_id, entry_type, play_order, message) + VALUES (${lib.id}, 'track', 1, 'fk-test-set-null') + RETURNING id + `; + + await tx`DELETE FROM ${tx(SCHEMA)}.library WHERE id = ${lib.id}`; + + const after = await tx`SELECT album_id FROM ${tx(SCHEMA)}.flowsheet WHERE id = ${fls.id}`; + expect(after).toHaveLength(1); + expect(after[0].album_id).toBeNull(); + }); + }); + + test('DELETE on a library row cascades to rotation rows (was NO ACTION)', async () => { + await withRollback(async (tx) => { + const [lib] = await tx` + INSERT INTO ${tx(SCHEMA)}.library (artist_id, genre_id, format_id, album_title, code_number, artist_name) + VALUES (${SEED_ARTIST_ID}, ${SEED_GENRE_ID}, ${SEED_FORMAT_ID}, + 'FK Test Album CASCADE rotation', 9002, 'FK Test Artist') + RETURNING id + `; + // rotation: album_id is nullable but referenced; rotation_bin is + // NOT NULL (schema.ts:558). + const [rot] = await tx` + INSERT INTO ${tx(SCHEMA)}.rotation (album_id, rotation_bin) + VALUES (${lib.id}, 'L') + RETURNING id + `; + + await tx`DELETE FROM ${tx(SCHEMA)}.library WHERE id = ${lib.id}`; + + const after = await tx`SELECT id FROM ${tx(SCHEMA)}.rotation WHERE id = ${rot.id}`; + expect(after).toHaveLength(0); + }); + }); + + test('DELETE on a library row cascades to reviews rows (was NO ACTION)', async () => { + await withRollback(async (tx) => { + const [lib] = await tx` + INSERT INTO ${tx(SCHEMA)}.library (artist_id, genre_id, format_id, album_title, code_number, artist_name) + VALUES (${SEED_ARTIST_ID}, ${SEED_GENRE_ID}, ${SEED_FORMAT_ID}, + 'FK Test Album CASCADE reviews', 9003, 'FK Test Artist') + RETURNING id + `; + // reviews: album_id is NOT NULL + UNIQUE (schema.ts:1075). + const [rev] = await tx` + INSERT INTO ${tx(SCHEMA)}.reviews (album_id, review, author) + VALUES (${lib.id}, 'fk-test-cascade-reviews', 'fk-test') + RETURNING id + `; + + await tx`DELETE FROM ${tx(SCHEMA)}.library WHERE id = ${lib.id}`; + + const after = await tx`SELECT id FROM ${tx(SCHEMA)}.reviews WHERE id = ${rev.id}`; + expect(after).toHaveLength(0); + }); + }); +});