Skip to content

Commit ce88da3

Browse files
committed
feat: compose-dials, diff-granularity, diff-bundle, transform-check examples; document module-vs-db limitation
1 parent 1cf7d1a commit ce88da3

226 files changed

Lines changed: 2230 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ produced it.
4949
| [emit-bundle](examples/emit-bundle) | [the module](examples/import-dump/output/shop) | [content-addressed bundle](examples/emit-bundle/output/shop.bundle.tar.gz) powering `pgpm deploy --fast` |
5050
| [append-migration](examples/append-migration) | v1→v2 delta + [shop.v3.sql](examples/append-migration/input/shop.v3.sql) | [one living migration module](examples/append-migration/output/shop-migrations) (`diff --append-module`) covering v1→v3 |
5151
| [diff-live-db](examples/diff-live-db) | two **live databases** (`db:a` vs `db:b`) | migration module generated on demand at test time |
52+
| [compose-dials](examples/compose-dials) | [raw pg_dump](examples/import-dump/input/shop.v1.sql) | [all dials at once](examples/compose-dials/output/shop-composed) (atomic × per-alteration × flat) + [linear SQL](examples/compose-dials/output/shop-composed.sql), one command |
53+
| [diff-granularity](examples/diff-granularity) | the v1 module vs v2 | [per-alteration migration](examples/diff-granularity/output/shop-v1-to-v2-per-alteration) — same delta, 7 → 17 independently revertible changes |
54+
| [diff-bundle](examples/diff-bundle) | the v1 module vs v2 | [the delta as a content-addressed bundle](examples/diff-bundle/output/shop.v1-to-v2.bundle.tar.gz) |
55+
| [transform-check](examples/transform-check) | [the module](examples/import-dump/output/shop) | nothing — the CLI's built-in scratch-DB lossless-transform oracle |
56+
| [diff-module-vs-db](examples/diff-module-vs-db) | a module vs a **live database** | *blocked* — documents the mixed-side normalization asymmetry + workaround |
5257

5358
See the full combination matrix in
5459
[constructive-planning#1344](https://github.com/constructive-io/constructive-planning/issues/1344);

examples/compose-dials/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# compose-dials — every dial in one command
2+
3+
**Input:** [`../import-dump/input/shop.v1.sql`](../import-dump/input/shop.v1.sql) — the same raw dump as [import-dump](../import-dump).
4+
5+
**Output:** [`output/shop-composed/`](output/shop-composed) + [`output/shop-composed.sql`](output/shop-composed.sql) — one `pgpm import` run stacking four dials and an extra projection: **atomic** statement shape × **per-alteration** change distribution × **flat** naming, plus the same model projected to a single linear SQL file. 53 statements → 54 changes and one packed script, from one command.
6+
7+
```sh
8+
pgpm import examples/import-dump/input/shop.v1.sql --pkg shop-composed \
9+
--granularity atomic --change-granularity alteration --naming flat \
10+
--out examples/compose-dials/output \
11+
--emit-sql "$PWD/examples/compose-dials/output/shop-composed.sql"
12+
```
13+
14+
The dials are orthogonal projections of one identity-keyed model, so any combination is semantically invariant. CI deploys the composed module and asserts the catalog is identical to every other variant.
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
CREATE SCHEMA shop;
2+
3+
COMMENT ON SCHEMA shop IS 'Storefront: customers, products, orders.';
4+
5+
GRANT USAGE ON SCHEMA shop TO app_user;
6+
7+
CREATE SCHEMA audit;
8+
9+
COMMENT ON SCHEMA audit IS 'Append-only change log for shop tables.';
10+
11+
CREATE SEQUENCE shop.order_number_seq START 1000 INCREMENT 1 NO MINVALUE NO MAXVALUE CACHE 1;
12+
13+
CREATE TABLE shop.customers ();
14+
15+
ALTER TABLE shop.customers
16+
ADD COLUMN id uuid
17+
DEFAULT gen_random_uuid()
18+
NOT NULL;
19+
20+
ALTER TABLE shop.customers
21+
ADD COLUMN email text
22+
NOT NULL;
23+
24+
ALTER TABLE shop.customers
25+
ADD COLUMN full_name text
26+
NOT NULL;
27+
28+
ALTER TABLE shop.customers
29+
ADD COLUMN phone text;
30+
31+
ALTER TABLE shop.customers
32+
ADD COLUMN created_at timestamptz
33+
DEFAULT now()
34+
NOT NULL;
35+
36+
ALTER TABLE ONLY shop.customers
37+
ADD CONSTRAINT customers_pkey PRIMARY KEY (id);
38+
39+
ALTER TABLE ONLY shop.customers
40+
ADD CONSTRAINT customers_email_key
41+
UNIQUE (email);
42+
43+
COMMENT ON TABLE shop.customers IS 'Registered storefront customers.';
44+
45+
COMMENT ON COLUMN shop.customers.email IS 'Unique login email, lowercased.';
46+
47+
GRANT SELECT ON shop.customers TO app_user;
48+
49+
CREATE TABLE shop.products ();
50+
51+
ALTER TABLE shop.products
52+
ADD COLUMN id uuid
53+
DEFAULT gen_random_uuid()
54+
NOT NULL;
55+
56+
ALTER TABLE shop.products
57+
ADD COLUMN sku text
58+
NOT NULL;
59+
60+
ALTER TABLE shop.products
61+
ADD COLUMN name text
62+
NOT NULL;
63+
64+
ALTER TABLE shop.products
65+
ADD COLUMN description text;
66+
67+
ALTER TABLE shop.products
68+
ADD COLUMN price_cents int
69+
NOT NULL;
70+
71+
ALTER TABLE shop.products
72+
ADD COLUMN created_at timestamptz
73+
DEFAULT now()
74+
NOT NULL;
75+
76+
ALTER TABLE ONLY shop.products
77+
ADD CONSTRAINT products_pkey PRIMARY KEY (id);
78+
79+
ALTER TABLE ONLY shop.products
80+
ADD CONSTRAINT products_sku_key
81+
UNIQUE (sku);
82+
83+
ALTER TABLE shop.products
84+
ADD CONSTRAINT products_price_cents_check
85+
CHECK (price_cents >= 0);
86+
87+
COMMENT ON TABLE shop.products IS 'Sellable products with price in cents.';
88+
89+
GRANT SELECT ON shop.products TO app_user;
90+
91+
CREATE TABLE shop.orders ();
92+
93+
ALTER TABLE shop.orders
94+
ENABLE ROW LEVEL SECURITY;
95+
96+
COMMENT ON TABLE shop.orders IS 'Customer orders; one row per checkout.';
97+
98+
GRANT SELECT, INSERT, UPDATE ON shop.orders TO app_user;
99+
100+
ALTER TABLE shop.orders
101+
ADD COLUMN id uuid
102+
DEFAULT gen_random_uuid()
103+
NOT NULL;
104+
105+
ALTER TABLE shop.orders
106+
ADD COLUMN order_number bigint
107+
DEFAULT nextval(CAST('shop.order_number_seq' AS regclass))
108+
NOT NULL;
109+
110+
ALTER TABLE shop.orders
111+
ADD COLUMN customer_id uuid
112+
NOT NULL;
113+
114+
ALTER TABLE shop.orders
115+
ADD COLUMN status text
116+
DEFAULT 'pending'
117+
NOT NULL;
118+
119+
ALTER TABLE shop.orders
120+
ADD COLUMN placed_at timestamptz
121+
DEFAULT now()
122+
NOT NULL;
123+
124+
ALTER TABLE ONLY shop.orders
125+
ADD CONSTRAINT orders_pkey PRIMARY KEY (id);
126+
127+
ALTER TABLE ONLY shop.orders
128+
ADD CONSTRAINT orders_order_number_key
129+
UNIQUE (order_number);
130+
131+
ALTER TABLE shop.orders
132+
ADD CONSTRAINT orders_status_check
133+
CHECK (status IN ('pending', 'paid', 'shipped', 'cancelled'));
134+
135+
ALTER TABLE ONLY shop.orders
136+
ADD CONSTRAINT orders_customer_id_fkey
137+
FOREIGN KEY(customer_id)
138+
REFERENCES shop.customers (id);
139+
140+
CREATE TABLE shop.order_items ();
141+
142+
ALTER TABLE shop.order_items
143+
ADD COLUMN id uuid
144+
DEFAULT gen_random_uuid()
145+
NOT NULL;
146+
147+
ALTER TABLE shop.order_items
148+
ADD COLUMN order_id uuid
149+
NOT NULL;
150+
151+
ALTER TABLE shop.order_items
152+
ADD COLUMN product_id uuid
153+
NOT NULL;
154+
155+
ALTER TABLE shop.order_items
156+
ADD COLUMN quantity int
157+
DEFAULT 1
158+
NOT NULL;
159+
160+
ALTER TABLE shop.order_items
161+
ADD COLUMN unit_price_cents int
162+
NOT NULL;
163+
164+
ALTER TABLE ONLY shop.order_items
165+
ADD CONSTRAINT order_items_pkey PRIMARY KEY (id);
166+
167+
ALTER TABLE ONLY shop.order_items
168+
ADD CONSTRAINT order_items_order_id_fkey
169+
FOREIGN KEY(order_id)
170+
REFERENCES shop.orders (id)
171+
ON DELETE CASCADE;
172+
173+
ALTER TABLE ONLY shop.order_items
174+
ADD CONSTRAINT order_items_product_id_fkey
175+
FOREIGN KEY(product_id)
176+
REFERENCES shop.products (id);
177+
178+
GRANT SELECT, INSERT ON shop.order_items TO app_user;
179+
180+
CREATE TABLE audit.change_log ();
181+
182+
ALTER TABLE audit.change_log
183+
ADD COLUMN id bigint
184+
GENERATED ALWAYS AS IDENTITY
185+
NOT NULL;
186+
187+
ALTER TABLE audit.change_log
188+
ADD COLUMN table_name text
189+
NOT NULL;
190+
191+
ALTER TABLE audit.change_log
192+
ADD COLUMN row_id uuid
193+
NOT NULL;
194+
195+
ALTER TABLE audit.change_log
196+
ADD COLUMN operation text
197+
NOT NULL;
198+
199+
ALTER TABLE audit.change_log
200+
ADD COLUMN changed_at timestamptz
201+
DEFAULT now()
202+
NOT NULL;
203+
204+
ALTER TABLE ONLY audit.change_log
205+
ADD CONSTRAINT change_log_pkey PRIMARY KEY (id);
206+
207+
CREATE FUNCTION shop.order_total(
208+
p_order_id uuid
209+
) RETURNS int LANGUAGE sql STABLE AS $EOFCODE$
210+
SELECT COALESCE(SUM(quantity * unit_price_cents), 0)::integer
211+
FROM shop.order_items
212+
WHERE order_id = p_order_id;
213+
$EOFCODE$;
214+
215+
COMMENT ON FUNCTION shop.order_total(p_order_id uuid) IS 'Sum of line totals for an order, in cents.';
216+
217+
CREATE FUNCTION audit.log_order_change() RETURNS trigger LANGUAGE plpgsql AS $EOFCODE$
218+
BEGIN
219+
INSERT INTO audit.change_log (table_name, row_id, operation)
220+
VALUES (TG_TABLE_NAME, NEW.id, TG_OP);
221+
RETURN NEW;
222+
END;
223+
$EOFCODE$;
224+
225+
CREATE INDEX idx_orders_customer_id ON shop.orders (customer_id);
226+
227+
CREATE INDEX idx_orders_placed_at ON shop.orders (placed_at);
228+
229+
CREATE INDEX idx_order_items_order_id ON shop.order_items (order_id);
230+
231+
CREATE TRIGGER orders_audit_trigger
232+
AFTER INSERT OR UPDATE
233+
ON shop.orders
234+
FOR EACH ROW
235+
EXECUTE PROCEDURE audit.log_order_change();
236+
237+
CREATE POLICY orders_select_own
238+
ON shop.orders
239+
AS PERMISSIVE
240+
FOR SELECT
241+
TO PUBLIC
242+
USING (
243+
customer_id = (current_setting('app.current_customer_id', true))::uuid
244+
);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-- Deploy: schemas/audit/procedures/log_order_change
2+
-- made with <3 @ constructive.io
3+
4+
-- requires: schemas/audit/schema
5+
6+
7+
CREATE FUNCTION audit.log_order_change() RETURNS trigger LANGUAGE plpgsql AS $$
8+
BEGIN
9+
INSERT INTO audit.change_log (table_name, row_id, operation)
10+
VALUES (TG_TABLE_NAME, NEW.id, TG_OP);
11+
RETURN NEW;
12+
END;
13+
$$;
14+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- Deploy: schemas/audit/schema
2+
-- made with <3 @ constructive.io
3+
4+
5+
6+
7+
CREATE SCHEMA audit;
8+
9+
COMMENT ON SCHEMA audit IS 'Append-only change log for shop tables.';
10+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- Deploy: schemas/audit/tables/change_log/columns/changed_at
2+
-- made with <3 @ constructive.io
3+
4+
-- requires: schemas/audit/schema
5+
-- requires: schemas/audit/tables/change_log/table
6+
7+
8+
ALTER TABLE audit.change_log
9+
ADD COLUMN changed_at timestamptz
10+
DEFAULT now()
11+
NOT NULL;
12+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- Deploy: schemas/audit/tables/change_log/columns/id
2+
-- made with <3 @ constructive.io
3+
4+
-- requires: schemas/audit/schema
5+
-- requires: schemas/audit/tables/change_log/table
6+
7+
8+
ALTER TABLE audit.change_log
9+
ADD COLUMN id bigint
10+
GENERATED ALWAYS AS IDENTITY
11+
NOT NULL;
12+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- Deploy: schemas/audit/tables/change_log/columns/operation
2+
-- made with <3 @ constructive.io
3+
4+
-- requires: schemas/audit/schema
5+
-- requires: schemas/audit/tables/change_log/table
6+
7+
8+
ALTER TABLE audit.change_log
9+
ADD COLUMN operation text
10+
NOT NULL;
11+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- Deploy: schemas/audit/tables/change_log/columns/row_id
2+
-- made with <3 @ constructive.io
3+
4+
-- requires: schemas/audit/schema
5+
-- requires: schemas/audit/tables/change_log/table
6+
7+
8+
ALTER TABLE audit.change_log
9+
ADD COLUMN row_id uuid
10+
NOT NULL;
11+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- Deploy: schemas/audit/tables/change_log/columns/table_name
2+
-- made with <3 @ constructive.io
3+
4+
-- requires: schemas/audit/schema
5+
-- requires: schemas/audit/tables/change_log/table
6+
7+
8+
ALTER TABLE audit.change_log
9+
ADD COLUMN table_name text
10+
NOT NULL;
11+

0 commit comments

Comments
 (0)