Skip to content

Commit 36dd819

Browse files
committed
fix(plpgsql-deparser): indent all lines of multi-line statements
Previously, the indent() function only indented the first line of text. This caused incorrect formatting for nested IF/WHILE/LOOP blocks where subsequent lines would not be properly indented. The fix uses text.replace(/\n/g, '\n' + indent) to ensure all lines are indented, matching the behavior of the SQL implementation in constructive-db.
1 parent 286a6b1 commit 36dd819

File tree

3 files changed

+449
-448
lines changed

3 files changed

+449
-448
lines changed

packages/plpgsql-deparser/__tests__/__snapshots__/hydrate-demo.test.ts.snap

Lines changed: 144 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -53,150 +53,150 @@ exports[`hydrate demonstration with big-function.sql should parse, hydrate, modi
5353
sqlerrm CONSTANT text;
5454
BEGIN
5555
BEGIN
56-
IF p_org_id IS NULL OR p_user_id IS NULL THEN
57-
RAISE EXCEPTION 'p_org_id and p_user_id are required';
58-
END IF;
59-
IF p_from_ts > p_to_ts THEN
60-
RAISE EXCEPTION 'p_from_ts (%) must be <= p_to_ts (%)', p_from_ts, p_to_ts;
61-
END IF;
62-
IF p_max_rows < 1 OR p_max_rows > 10000 THEN
63-
RAISE EXCEPTION 'p_max_rows out of range: %', p_max_rows;
64-
END IF;
65-
IF p_round_to < 0 OR p_round_to > 6 THEN
66-
RAISE EXCEPTION 'p_round_to out of range: %', p_round_to;
67-
END IF;
68-
IF p_lock THEN
69-
PERFORM SELECT pg_advisory_xact_lock(v_lock_key);
70-
END IF;
71-
IF p_debug THEN
72-
RAISE NOTICE 'big_kitchen_sink start=% org=% user=% from=% to=% min_total=%', v_now, p_org_id, p_user_id, p_from_ts, p_to_ts, v_min_total;
73-
END IF;
74-
WITH
75-
base AS (SELECT
76-
o.id,
77-
o.total_amount::numeric AS total_amount,
78-
o.currency,
79-
o.created_at
80-
FROM app_public.app_order AS o
81-
WHERE
82-
o.org_id = p_org_id
83-
AND o.user_id = p_user_id
84-
AND o.created_at >= p_from_ts
85-
AND o.created_at < p_to_ts
86-
AND o.total_amount::numeric >= v_min_total
87-
AND o.currency = p_currency
88-
ORDER BY
89-
o.created_at DESC
90-
LIMIT p_max_rows),
91-
totals AS (SELECT
92-
(count(*))::int AS orders_scanned,
93-
COALESCE(sum(total_amount), 0) AS gross_total,
94-
COALESCE(avg(total_amount), 0) AS avg_total
95-
FROM base)
96-
SELECT
97-
t.orders_scanned,
98-
t.gross_total,
99-
t.avg_total
100-
FROM totals AS t;
101-
IF p_apply_discount THEN
102-
v_rebate := round(v_gross * GREATEST(LEAST(v_discount_rate + v_jitter, 0.50), 0), p_round_to);
103-
ELSE
104-
v_discount := 0;
105-
END IF;
106-
v_levy := round(GREATEST(v_gross - v_discount, 0) * v_tax_rate, p_round_to);
107-
v_net := round((v_gross - v_discount + v_tax) * power(10::numeric, 0), p_round_to);
108-
SELECT
109-
oi.sku,
110-
CAST(sum(oi.quantity) AS bigint) AS qty
111-
FROM app_public.order_item AS oi
112-
JOIN app_public.app_order AS o ON o.id = oi.order_id
113-
WHERE
114-
o.org_id = p_org_id
115-
AND o.user_id = p_user_id
116-
AND o.created_at >= p_from_ts
117-
AND o.created_at < p_to_ts
118-
AND o.currency = p_currency
119-
GROUP BY
120-
oi.sku
121-
ORDER BY
122-
qty DESC,
123-
oi.sku ASC
124-
LIMIT 1;
125-
INSERT INTO app_public.order_rollup (
126-
org_id,
127-
user_id,
128-
period_from,
129-
period_to,
130-
currency,
131-
orders_scanned,
132-
gross_total,
133-
discount_total,
134-
tax_total,
135-
net_total,
136-
avg_order_total,
137-
top_sku,
138-
top_sku_qty,
139-
note,
140-
updated_at
141-
) VALUES
142-
(
143-
p_org_id,
144-
p_user_id,
145-
p_from_ts,
146-
p_to_ts,
147-
p_currency,
148-
v_orders_scanned,
149-
v_gross,
150-
v_discount,
151-
v_tax,
152-
v_net,
153-
v_avg,
154-
v_top_sku,
155-
v_top_sku_qty,
156-
p_note,
157-
now()
158-
) ON CONFLICT (org_id, user_id, period_from, period_to, currency) DO UPDATE SET
159-
orders_scanned = excluded.orders_scanned,
160-
gross_total = excluded.gross_total,
161-
discount_total = excluded.discount_total,
162-
tax_total = excluded.tax_total,
163-
net_total = excluded.net_total,
164-
avg_order_total = excluded.avg_order_total,
165-
top_sku = excluded.top_sku,
166-
top_sku_qty = excluded.top_sku_qty,
167-
note = COALESCE(excluded.note, app_public.order_rollup.note),
168-
updated_at = now();
169-
GET DIAGNOSTICS v_rowcount = ;
170-
v_orders_upserted := v_rowcount;
171-
v_sql := format(
172-
'SELECT count(*)::int FROM %I.%I WHERE org_id = $1 AND created_at >= $2 AND created_at < $3',
173-
'app_public',
174-
'app_order'
175-
);
176-
EXECUTE v_sql INTO (unnamed row) USING p_org_id, p_from_ts, p_to_ts;
177-
IF p_debug THEN
178-
RAISE NOTICE 'dynamic count(app_order)=%', v_rowcount;
179-
END IF;
180-
org_id := p_org_id;
181-
user_id := p_user_id;
182-
period_from := p_from_ts;
183-
period_to := p_to_ts;
184-
orders_scanned := v_orders_scanned;
185-
orders_upserted := v_orders_upserted;
186-
gross_total := v_gross;
187-
discount_total := v_discount;
188-
tax_total := v_tax;
189-
net_total := v_net;
190-
avg_order_total := round(v_avg, p_round_to);
191-
top_sku := v_top_sku;
192-
top_sku_qty := v_top_sku_qty;
193-
message := format(
194-
'rollup ok: gross=%s discount=%s tax=%s net=%s (discount_rate=%s tax_rate=%s)',
195-
v_gross, v_discount, v_tax, v_net, v_discount_rate, v_tax_rate
196-
);
197-
RETURN NEXT;
198-
RETURN;
199-
END;
56+
IF p_org_id IS NULL OR p_user_id IS NULL THEN
57+
RAISE EXCEPTION 'p_org_id and p_user_id are required';
58+
END IF;
59+
IF p_from_ts > p_to_ts THEN
60+
RAISE EXCEPTION 'p_from_ts (%) must be <= p_to_ts (%)', p_from_ts, p_to_ts;
61+
END IF;
62+
IF p_max_rows < 1 OR p_max_rows > 10000 THEN
63+
RAISE EXCEPTION 'p_max_rows out of range: %', p_max_rows;
64+
END IF;
65+
IF p_round_to < 0 OR p_round_to > 6 THEN
66+
RAISE EXCEPTION 'p_round_to out of range: %', p_round_to;
67+
END IF;
68+
IF p_lock THEN
69+
PERFORM SELECT pg_advisory_xact_lock(v_lock_key);
70+
END IF;
71+
IF p_debug THEN
72+
RAISE NOTICE 'big_kitchen_sink start=% org=% user=% from=% to=% min_total=%', v_now, p_org_id, p_user_id, p_from_ts, p_to_ts, v_min_total;
73+
END IF;
74+
WITH
75+
base AS (SELECT
76+
o.id,
77+
o.total_amount::numeric AS total_amount,
78+
o.currency,
79+
o.created_at
80+
FROM app_public.app_order AS o
81+
WHERE
82+
o.org_id = p_org_id
83+
AND o.user_id = p_user_id
84+
AND o.created_at >= p_from_ts
85+
AND o.created_at < p_to_ts
86+
AND o.total_amount::numeric >= v_min_total
87+
AND o.currency = p_currency
88+
ORDER BY
89+
o.created_at DESC
90+
LIMIT p_max_rows),
91+
totals AS (SELECT
92+
(count(*))::int AS orders_scanned,
93+
COALESCE(sum(total_amount), 0) AS gross_total,
94+
COALESCE(avg(total_amount), 0) AS avg_total
95+
FROM base)
96+
SELECT
97+
t.orders_scanned,
98+
t.gross_total,
99+
t.avg_total
100+
FROM totals AS t;
101+
IF p_apply_discount THEN
102+
v_rebate := round(v_gross * GREATEST(LEAST(v_discount_rate + v_jitter, 0.50), 0), p_round_to);
103+
ELSE
104+
v_discount := 0;
105+
END IF;
106+
v_levy := round(GREATEST(v_gross - v_discount, 0) * v_tax_rate, p_round_to);
107+
v_net := round((v_gross - v_discount + v_tax) * power(10::numeric, 0), p_round_to);
108+
SELECT
109+
oi.sku,
110+
CAST(sum(oi.quantity) AS bigint) AS qty
111+
FROM app_public.order_item AS oi
112+
JOIN app_public.app_order AS o ON o.id = oi.order_id
113+
WHERE
114+
o.org_id = p_org_id
115+
AND o.user_id = p_user_id
116+
AND o.created_at >= p_from_ts
117+
AND o.created_at < p_to_ts
118+
AND o.currency = p_currency
119+
GROUP BY
120+
oi.sku
121+
ORDER BY
122+
qty DESC,
123+
oi.sku ASC
124+
LIMIT 1;
125+
INSERT INTO app_public.order_rollup (
126+
org_id,
127+
user_id,
128+
period_from,
129+
period_to,
130+
currency,
131+
orders_scanned,
132+
gross_total,
133+
discount_total,
134+
tax_total,
135+
net_total,
136+
avg_order_total,
137+
top_sku,
138+
top_sku_qty,
139+
note,
140+
updated_at
141+
) VALUES
142+
(
143+
p_org_id,
144+
p_user_id,
145+
p_from_ts,
146+
p_to_ts,
147+
p_currency,
148+
v_orders_scanned,
149+
v_gross,
150+
v_discount,
151+
v_tax,
152+
v_net,
153+
v_avg,
154+
v_top_sku,
155+
v_top_sku_qty,
156+
p_note,
157+
now()
158+
) ON CONFLICT (org_id, user_id, period_from, period_to, currency) DO UPDATE SET
159+
orders_scanned = excluded.orders_scanned,
160+
gross_total = excluded.gross_total,
161+
discount_total = excluded.discount_total,
162+
tax_total = excluded.tax_total,
163+
net_total = excluded.net_total,
164+
avg_order_total = excluded.avg_order_total,
165+
top_sku = excluded.top_sku,
166+
top_sku_qty = excluded.top_sku_qty,
167+
note = COALESCE(excluded.note, app_public.order_rollup.note),
168+
updated_at = now();
169+
GET DIAGNOSTICS v_rowcount = ;
170+
v_orders_upserted := v_rowcount;
171+
v_sql := format(
172+
'SELECT count(*)::int FROM %I.%I WHERE org_id = $1 AND created_at >= $2 AND created_at < $3',
173+
'app_public',
174+
'app_order'
175+
);
176+
EXECUTE v_sql INTO (unnamed row) USING p_org_id, p_from_ts, p_to_ts;
177+
IF p_debug THEN
178+
RAISE NOTICE 'dynamic count(app_order)=%', v_rowcount;
179+
END IF;
180+
org_id := p_org_id;
181+
user_id := p_user_id;
182+
period_from := p_from_ts;
183+
period_to := p_to_ts;
184+
orders_scanned := v_orders_scanned;
185+
orders_upserted := v_orders_upserted;
186+
gross_total := v_gross;
187+
discount_total := v_discount;
188+
tax_total := v_tax;
189+
net_total := v_net;
190+
avg_order_total := round(v_avg, p_round_to);
191+
top_sku := v_top_sku;
192+
top_sku_qty := v_top_sku_qty;
193+
message := format(
194+
'rollup ok: gross=%s discount=%s tax=%s net=%s (discount_rate=%s tax_rate=%s)',
195+
v_gross, v_discount, v_tax, v_net, v_discount_rate, v_tax_rate
196+
);
197+
RETURN NEXT;
198+
RETURN;
199+
END;
200200
RETURN;
201201
END$$"
202202
`;

0 commit comments

Comments
 (0)