-- Every table, column, enum type, index and constraint goes from quoted -- camelCase to unquoted snake_case. -- -- The camelCase came from Prisma, which generated the original schema. 0001 -- kept it deliberately ("the cost of keeping them is zero"), and that turned -- out to be wrong: the cost is that every identifier in every query must stay -- quoted forever, and any unquoted one folds to lowercase and fails at run -- time instead of at compile time. This pays that off once. -- -- Tables are pluralised. That is not only taste: "User" could not become -- "user" because USER is a reserved word in Postgres, and naming ten tables by -- one rule and the eleventh by another is worse than renaming all eleven. -- -- Enum VALUES are left alone. They are already uppercase ('DONATION', -- 'AKTIV') and they are data, not identifiers — the Go code compares against -- them as strings and the API sends them over the wire. -- -- RENAME is a catalogue update: no table is rewritten and no row is touched, -- so this is instant whatever the database holds. The whole file runs in one -- transaction (see store.Migrate), so it either lands completely or not at all. -- ------------------------------------------------------------- enum types --- ALTER TYPE "TierType" RENAME TO tier_type; ALTER TYPE "CampaignStatus" RENAME TO campaign_status; ALTER TYPE "PledgeStatus" RENAME TO pledge_status; ALTER TYPE "PhysicalTierPolicy" RENAME TO physical_tier_policy; -- ----------------------------------------------------------------- tables --- ALTER TABLE "Owner" RENAME TO owners; ALTER TABLE "User" RENAME TO users; ALTER TABLE "Session" RENAME TO sessions; ALTER TABLE "Verification" RENAME TO verifications; ALTER TABLE "Campaign" RENAME TO campaigns; ALTER TABLE "StorySection" RENAME TO story_sections; ALTER TABLE "RewardTier" RENAME TO reward_tiers; ALTER TABLE "Pledge" RENAME TO pledges; ALTER TABLE "ShippingAddress" RENAME TO shipping_addresses; ALTER TABLE "CampaignUpdate" RENAME TO campaign_updates; ALTER TABLE "Comment" RENAME TO comments; -- ---------------------------------------------------------------- columns --- -- Only the mixed-case ones: id, slug, title and the rest are already lowercase -- and renaming a column to its own name is an error. ALTER TABLE owners RENAME COLUMN "stripeAccountId" TO stripe_account_id; ALTER TABLE owners RENAME COLUMN "createdAt" TO created_at; ALTER TABLE owners RENAME COLUMN "avatarUrl" TO avatar_url; ALTER TABLE users RENAME COLUMN "emailVerified" TO email_verified; ALTER TABLE users RENAME COLUMN "isAdmin" TO is_admin; ALTER TABLE users RENAME COLUMN "createdAt" TO created_at; ALTER TABLE users RENAME COLUMN "updatedAt" TO updated_at; ALTER TABLE sessions RENAME COLUMN "expiresAt" TO expires_at; ALTER TABLE sessions RENAME COLUMN "ipAddress" TO ip_address; ALTER TABLE sessions RENAME COLUMN "userAgent" TO user_agent; ALTER TABLE sessions RENAME COLUMN "userId" TO user_id; ALTER TABLE sessions RENAME COLUMN "createdAt" TO created_at; ALTER TABLE sessions RENAME COLUMN "updatedAt" TO updated_at; ALTER TABLE verifications RENAME COLUMN "expiresAt" TO expires_at; ALTER TABLE verifications RENAME COLUMN "consumedAt" TO consumed_at; ALTER TABLE verifications RENAME COLUMN "createdAt" TO created_at; ALTER TABLE verifications RENAME COLUMN "updatedAt" TO updated_at; ALTER TABLE campaigns RENAME COLUMN "goalAmount" TO goal_amount; ALTER TABLE campaigns RENAME COLUMN "startsAt" TO starts_at; ALTER TABLE campaigns RENAME COLUMN "endsAt" TO ends_at; ALTER TABLE campaigns RENAME COLUMN "physicalTierPolicy" TO physical_tier_policy; ALTER TABLE campaigns RENAME COLUMN "ownerId" TO owner_id; ALTER TABLE campaigns RENAME COLUMN "createdAt" TO created_at; ALTER TABLE campaigns RENAME COLUMN "updatedAt" TO updated_at; ALTER TABLE story_sections RENAME COLUMN "campaignId" TO campaign_id; ALTER TABLE reward_tiers RENAME COLUMN "campaignId" TO campaign_id; ALTER TABLE reward_tiers RENAME COLUMN "listPrice" TO list_price; ALTER TABLE reward_tiers RENAME COLUMN "inventoryLimit" TO inventory_limit; ALTER TABLE reward_tiers RENAME COLUMN "estimatedDelivery" TO estimated_delivery; ALTER TABLE pledges RENAME COLUMN "campaignId" TO campaign_id; ALTER TABLE pledges RENAME COLUMN "tierId" TO tier_id; ALTER TABLE pledges RENAME COLUMN "userId" TO user_id; ALTER TABLE pledges RENAME COLUMN "backerEmail" TO backer_email; ALTER TABLE pledges RENAME COLUMN "stripePaymentIntentId" TO stripe_payment_intent_id; ALTER TABLE pledges RENAME COLUMN "createdAt" TO created_at; ALTER TABLE pledges RENAME COLUMN "capturedAt" TO captured_at; ALTER TABLE shipping_addresses RENAME COLUMN "pledgeId" TO pledge_id; ALTER TABLE shipping_addresses RENAME COLUMN "postalCode" TO postal_code; ALTER TABLE campaign_updates RENAME COLUMN "campaignId" TO campaign_id; ALTER TABLE campaign_updates RENAME COLUMN "publishedAt" TO published_at; ALTER TABLE comments RENAME COLUMN "campaignId" TO campaign_id; ALTER TABLE comments RENAME COLUMN "authorName" TO author_name; ALTER TABLE comments RENAME COLUMN "createdAt" TO created_at; -- ---------------------------------------------------------------- indexes --- -- campaigns_slug_key is load-bearing beyond cosmetics: store.SaveCampaign -- matches on the constraint name to turn a 23505 into ErrSlugTaken. ALTER INDEX "Owner_slug_key" RENAME TO owners_slug_key; ALTER INDEX "Owner_stripeAccountId_key" RENAME TO owners_stripe_account_id_key; ALTER INDEX "User_email_key" RENAME TO users_email_key; ALTER INDEX "Session_token_key" RENAME TO sessions_token_key; ALTER INDEX "Session_userId_idx" RENAME TO sessions_user_id_idx; ALTER INDEX "Verification_identifier_idx" RENAME TO verifications_identifier_idx; ALTER INDEX "Verification_value_key" RENAME TO verifications_value_key; ALTER INDEX "Campaign_slug_key" RENAME TO campaigns_slug_key; ALTER INDEX "Campaign_status_endsAt_idx" RENAME TO campaigns_status_ends_at_idx; ALTER INDEX "StorySection_campaignId_position_key" RENAME TO story_sections_campaign_id_position_key; ALTER INDEX "RewardTier_campaignId_position_key" RENAME TO reward_tiers_campaign_id_position_key; ALTER INDEX "Pledge_stripePaymentIntentId_key" RENAME TO pledges_stripe_payment_intent_id_key; ALTER INDEX "Pledge_campaignId_status_idx" RENAME TO pledges_campaign_id_status_idx; ALTER INDEX "Pledge_tierId_idx" RENAME TO pledges_tier_id_idx; ALTER INDEX "ShippingAddress_pledgeId_key" RENAME TO shipping_addresses_pledge_id_key; -- ------------------------------------------------------------ constraints --- -- Primary and foreign keys are named by Postgres from the table and column, so -- these names are written down nowhere — not in 0001, not in the Go code. Two -- of them still say Organization: 0003 renamed the table and the column but -- not the constraints that were named after them, despite its comment claiming -- "nothing in the schema still says Organization". ALTER TABLE owners RENAME CONSTRAINT "Organization_pkey" TO owners_pkey; ALTER TABLE users RENAME CONSTRAINT "User_pkey" TO users_pkey; ALTER TABLE sessions RENAME CONSTRAINT "Session_pkey" TO sessions_pkey; ALTER TABLE verifications RENAME CONSTRAINT "Verification_pkey" TO verifications_pkey; ALTER TABLE campaigns RENAME CONSTRAINT "Campaign_pkey" TO campaigns_pkey; ALTER TABLE story_sections RENAME CONSTRAINT "StorySection_pkey" TO story_sections_pkey; ALTER TABLE reward_tiers RENAME CONSTRAINT "RewardTier_pkey" TO reward_tiers_pkey; ALTER TABLE pledges RENAME CONSTRAINT "Pledge_pkey" TO pledges_pkey; ALTER TABLE shipping_addresses RENAME CONSTRAINT "ShippingAddress_pkey" TO shipping_addresses_pkey; ALTER TABLE campaign_updates RENAME CONSTRAINT "CampaignUpdate_pkey" TO campaign_updates_pkey; ALTER TABLE comments RENAME CONSTRAINT "Comment_pkey" TO comments_pkey; ALTER TABLE sessions RENAME CONSTRAINT "Session_userId_fkey" TO sessions_user_id_fkey; ALTER TABLE campaigns RENAME CONSTRAINT "Campaign_organizationId_fkey" TO campaigns_owner_id_fkey; ALTER TABLE story_sections RENAME CONSTRAINT "StorySection_campaignId_fkey" TO story_sections_campaign_id_fkey; ALTER TABLE reward_tiers RENAME CONSTRAINT "RewardTier_campaignId_fkey" TO reward_tiers_campaign_id_fkey; ALTER TABLE pledges RENAME CONSTRAINT "Pledge_campaignId_fkey" TO pledges_campaign_id_fkey; ALTER TABLE pledges RENAME CONSTRAINT "Pledge_tierId_fkey" TO pledges_tier_id_fkey; ALTER TABLE pledges RENAME CONSTRAINT "Pledge_userId_fkey" TO pledges_user_id_fkey; ALTER TABLE shipping_addresses RENAME CONSTRAINT "ShippingAddress_pledgeId_fkey" TO shipping_addresses_pledge_id_fkey; ALTER TABLE campaign_updates RENAME CONSTRAINT "CampaignUpdate_campaignId_fkey" TO campaign_updates_campaign_id_fkey; ALTER TABLE comments RENAME CONSTRAINT "Comment_campaignId_fkey" TO comments_campaign_id_fkey; -- ------------------------------------------------------------------ guard --- -- Everything above was written by reading 0001 and 0003. Anything those files -- do not mention — a constraint added by hand, a name Postgres generated -- differently than expected — would survive silently and only surface as a -- quoted identifier someone has to keep quoting. This looks in the catalogue -- instead of in the migrations and fails the transaction if it finds one. -- -- lower() rather than a [A-Z] regex: character classes are collation-dependent -- and this is not. DO $$ DECLARE offenders text; BEGIN SELECT string_agg(name, ', ' ORDER BY name) INTO offenders FROM ( SELECT c.relname AS name FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'public' AND c.relname <> lower(c.relname) UNION ALL -- Tables only. An index carries its own copy of the column names it -- was built on, and a column rename does not update them: after this -- migration sessions_user_id_idx still has an attribute called -- "userId". Postgres offers no way to rename it and no SQL can refer -- to it, so it is not an identifier this migration is about. SELECT c.relname || '.' || a.attname FROM pg_attribute a JOIN pg_class c ON c.oid = a.attrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'public' AND c.relkind IN ('r', 'p', 'v', 'm') AND a.attnum > 0 AND NOT a.attisdropped AND a.attname <> lower(a.attname) UNION ALL SELECT t.typname FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = 'public' AND t.typname <> lower(t.typname) UNION ALL SELECT con.conname FROM pg_constraint con JOIN pg_namespace n ON n.oid = con.connamespace WHERE n.nspname = 'public' AND con.conname <> lower(con.conname) ) s; IF offenders IS NOT NULL THEN RAISE EXCEPTION 'snake_case migration missed: %', offenders; END IF; END $$;