Constraints
Inline and table-level constraints, NOT VALID deferred validation, and foreign keys.
Constraints may appear inline in the ( ) list or inside the { } block via a CONSTRAINT entry. Both placements are merged by name into one logical set before compilation.
Inline constraints
TABLE accounts (
id UUID NOT NULL DEFAULT gen_random_uuid() CONSTRAINT pk_accounts PRIMARY KEY,
slug TEXT NOT NULL CONSTRAINT uq_accounts_slug UNIQUE,
org UUID NOT NULL CONSTRAINT fk_accounts_org REFERENCES organisations (id) ON DELETE CASCADE,
CONSTRAINT ck_slug_len CHECK (length(slug) >= 3)
);
-- emits
CREATE TABLE "public"."accounts" (
"id" uuid NOT NULL DEFAULT gen_random_uuid() CONSTRAINT "pk_accounts" PRIMARY KEY,
"slug" text NOT NULL CONSTRAINT "uq_accounts_slug" UNIQUE,
"org" uuid NOT NULL CONSTRAINT "fk_accounts_org"
REFERENCES "public"."organisations" ("id") ON DELETE CASCADE,
CONSTRAINT "ck_slug_len" CHECK (length(slug) >= 3)
);
Multi-column constraints (table-level only)
TABLE order_items (
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
quantity INTEGER NOT NULL DEFAULT 1,
CONSTRAINT pk_order_items PRIMARY KEY (order_id, product_id),
CONSTRAINT uq_order_product UNIQUE (order_id, product_id)
);
-- emits
CREATE TABLE "public"."order_items" (
"order_id" bigint NOT NULL,
"product_id" bigint NOT NULL,
"quantity" integer NOT NULL DEFAULT 1,
CONSTRAINT "pk_order_items" PRIMARY KEY ("order_id", "product_id"),
CONSTRAINT "uq_order_product" UNIQUE ("order_id", "product_id")
);
Constraint in { } block
Constraints in the { } block are merged with those in ( ) by name.
TABLE orders (
id BIGINT GENERATED ALWAYS AS IDENTITY,
amount NUMERIC(10,2) NOT NULL,
CONSTRAINT pk_orders PRIMARY KEY (id)
)
{
CONSTRAINT fk_account FOREIGN KEY (account_id)
REFERENCES accounts (id)
ON DELETE CASCADE;
}
-- emits
CREATE TABLE "public"."orders" (
"id" bigint GENERATED ALWAYS AS IDENTITY,
"amount" numeric(10,2) NOT NULL,
CONSTRAINT "pk_orders" PRIMARY KEY ("id")
);
ALTER TABLE "public"."orders"
ADD CONSTRAINT "fk_account"
FOREIGN KEY ("account_id") REFERENCES "public"."accounts" ("id")
ON DELETE CASCADE;
NOT VALID — deferred constraint validation
Adding a constraint with NOT VALID skips scanning existing rows. Removing NOT VALID in a subsequent migration emits VALIDATE CONSTRAINT, which scans existing rows (non-blocking with ShareUpdateExclusiveLock).
-- Migration 1: add without validating existing rows
TABLE orders ( ... )
{
CONSTRAINT ck_amount_positive CHECK (amount > 0) NOT VALID;
}
-- emits
ALTER TABLE "public"."orders"
ADD CONSTRAINT "ck_amount_positive" CHECK (amount > 0) NOT VALID;
-- Migration 2: validate (remove NOT VALID from source)
TABLE orders ( ... )
{
CONSTRAINT ck_amount_positive CHECK (amount > 0);
}
-- emits
ALTER TABLE "public"."orders" VALIDATE CONSTRAINT "ck_amount_positive";
After validation, the constraint may be moved from { } to the ( ) list — the compiler identifies it by name and treats it as already existing.
DEFERRABLE foreign keys
Used to resolve circular foreign key dependencies. The compiler emits the circular FK as a separate ALTER TABLE ADD CONSTRAINT after both tables are created.
TABLE users (
id BIGINT GENERATED ALWAYS AS IDENTITY,
active_post BIGINT,
CONSTRAINT pk_users PRIMARY KEY (id)
)
{
CONSTRAINT fk_active_post FOREIGN KEY (active_post)
REFERENCES posts (id)
DEFERRABLE INITIALLY DEFERRED;
}
TABLE posts (
id BIGINT GENERATED ALWAYS AS IDENTITY,
user_id BIGINT NOT NULL REFERENCES users (id),
CONSTRAINT pk_posts PRIMARY KEY (id)
);
-- emits
CREATE TABLE "public"."users" (
"id" bigint GENERATED ALWAYS AS IDENTITY,
"active_post" bigint,
CONSTRAINT "pk_users" PRIMARY KEY ("id")
);
CREATE TABLE "public"."posts" (
"id" bigint GENERATED ALWAYS AS IDENTITY,
"user_id" bigint NOT NULL REFERENCES "public"."users" ("id"),
CONSTRAINT "pk_posts" PRIMARY KEY ("id")
);
-- circular FK emitted after both tables exist:
ALTER TABLE "public"."users"
ADD CONSTRAINT "fk_active_post"
FOREIGN KEY ("active_post") REFERENCES "public"."posts" ("id")
DEFERRABLE INITIALLY DEFERRED;