Skip to content

Commit 28ee30f

Browse files
committed
big-function
1 parent 89a0621 commit 28ee30f

File tree

5 files changed

+502
-0
lines changed

5 files changed

+502
-0
lines changed
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
CREATE OR REPLACE FUNCTION app_public.big_kitchen_sink(
2+
p_org_id uuid,
3+
p_user_id uuid,
4+
p_from_ts timestamptz DEFAULT now() - interval '30 days',
5+
p_to_ts timestamptz DEFAULT now(),
6+
p_min_total numeric DEFAULT 0,
7+
p_max_rows int DEFAULT 250,
8+
p_currency text DEFAULT 'USD',
9+
p_apply_discount boolean DEFAULT true,
10+
p_discount_rate numeric DEFAULT 0.05, -- 5%
11+
p_tax_rate numeric DEFAULT 0.0875,
12+
p_round_to int DEFAULT 2,
13+
p_note text DEFAULT NULL,
14+
p_lock boolean DEFAULT false,
15+
p_debug boolean DEFAULT false
16+
)
17+
RETURNS TABLE (
18+
org_id uuid,
19+
user_id uuid,
20+
period_from timestamptz,
21+
period_to timestamptz,
22+
orders_scanned int,
23+
orders_upserted int,
24+
gross_total numeric,
25+
discount_total numeric,
26+
tax_total numeric,
27+
net_total numeric,
28+
avg_order_total numeric,
29+
top_sku text,
30+
top_sku_qty bigint,
31+
message text
32+
)
33+
LANGUAGE plpgsql
34+
AS $$
35+
DECLARE
36+
v_orders_scanned int := 0;
37+
v_orders_upserted int := 0;
38+
v_gross numeric := 0;
39+
v_discount numeric := 0;
40+
v_tax numeric := 0;
41+
v_net numeric := 0;
42+
43+
v_avg numeric := 0;
44+
v_top_sku text := NULL;
45+
v_top_sku_qty bigint := 0;
46+
47+
v_now timestamptz := clock_timestamp();
48+
v_jitter numeric := (random() - 0.5) * 0.02; -- +/- 1% jitter
49+
v_discount_rate numeric := GREATEST(LEAST(p_discount_rate, 0.50), 0); -- cap at 50%
50+
v_tax_rate numeric := GREATEST(LEAST(p_tax_rate, 0.30), 0); -- cap at 30%
51+
v_min_total numeric := COALESCE(p_min_total, 0);
52+
53+
v_sql text;
54+
v_rowcount int := 0;
55+
56+
v_lock_key bigint := ('x' || substr(md5(p_org_id::text), 1, 16))::bit(64)::bigint;
57+
BEGIN
58+
-- Basic param validation
59+
IF p_org_id IS NULL OR p_user_id IS NULL THEN
60+
RAISE EXCEPTION 'p_org_id and p_user_id are required';
61+
END IF;
62+
63+
IF p_from_ts > p_to_ts THEN
64+
RAISE EXCEPTION 'p_from_ts (%) must be <= p_to_ts (%)', p_from_ts, p_to_ts;
65+
END IF;
66+
67+
IF p_max_rows < 1 OR p_max_rows > 10000 THEN
68+
RAISE EXCEPTION 'p_max_rows out of range: %', p_max_rows;
69+
END IF;
70+
71+
IF p_round_to < 0 OR p_round_to > 6 THEN
72+
RAISE EXCEPTION 'p_round_to out of range: %', p_round_to;
73+
END IF;
74+
75+
IF p_lock THEN
76+
-- Optional: serialize per-org runs
77+
PERFORM pg_advisory_xact_lock(v_lock_key);
78+
END IF;
79+
80+
IF p_debug THEN
81+
RAISE NOTICE 'big_kitchen_sink start=% org=% user=% from=% to=% min_total=%',
82+
v_now, p_org_id, p_user_id, p_from_ts, p_to_ts, v_min_total;
83+
END IF;
84+
85+
/*
86+
Example “scan” query:
87+
- CTEs
88+
- aggregates
89+
- filtering
90+
- math
91+
*/
92+
WITH base AS (
93+
SELECT
94+
o.id,
95+
o.total_amount::numeric AS total_amount,
96+
o.currency,
97+
o.created_at
98+
FROM app_public.app_order o
99+
WHERE o.org_id = p_org_id
100+
AND o.user_id = p_user_id
101+
AND o.created_at >= p_from_ts
102+
AND o.created_at < p_to_ts
103+
AND o.total_amount::numeric >= v_min_total
104+
AND o.currency = p_currency
105+
ORDER BY o.created_at DESC
106+
LIMIT p_max_rows
107+
),
108+
totals AS (
109+
SELECT
110+
count(*)::int AS orders_scanned,
111+
COALESCE(sum(total_amount), 0) AS gross_total,
112+
COALESCE(avg(total_amount), 0) AS avg_total
113+
FROM base
114+
)
115+
SELECT
116+
t.orders_scanned,
117+
t.gross_total,
118+
t.avg_total
119+
INTO
120+
v_orders_scanned,
121+
v_gross,
122+
v_avg
123+
FROM totals t;
124+
125+
-- Discount math (with named params style internally, too)
126+
IF p_apply_discount THEN
127+
-- Add tiny jitter to demonstrate math; clamp final
128+
v_discount := round(v_gross * GREATEST(LEAST(v_discount_rate + v_jitter, 0.50), 0), p_round_to);
129+
ELSE
130+
v_discount := 0;
131+
END IF;
132+
133+
-- Tax is computed on (gross - discount), typical pattern
134+
v_tax := round(GREATEST(v_gross - v_discount, 0) * v_tax_rate, p_round_to);
135+
136+
-- Net with a couple extra math operations
137+
v_net := round((v_gross - v_discount + v_tax) * power(10::numeric, 0), p_round_to);
138+
139+
/*
140+
Example “top sku” query:
141+
- joins
142+
- group by
143+
- order by
144+
*/
145+
SELECT
146+
oi.sku,
147+
sum(oi.quantity)::bigint AS qty
148+
INTO v_top_sku, v_top_sku_qty
149+
FROM app_public.order_item oi
150+
JOIN app_public.app_order o ON o.id = oi.order_id
151+
WHERE o.org_id = p_org_id
152+
AND o.user_id = p_user_id
153+
AND o.created_at >= p_from_ts
154+
AND o.created_at < p_to_ts
155+
AND o.currency = p_currency
156+
GROUP BY oi.sku
157+
ORDER BY qty DESC, oi.sku ASC
158+
LIMIT 1;
159+
160+
/*
161+
Example mutation:
162+
- upsert
163+
- GET DIAGNOSTICS
164+
*/
165+
INSERT INTO app_public.order_rollup (
166+
org_id,
167+
user_id,
168+
period_from,
169+
period_to,
170+
currency,
171+
orders_scanned,
172+
gross_total,
173+
discount_total,
174+
tax_total,
175+
net_total,
176+
avg_order_total,
177+
top_sku,
178+
top_sku_qty,
179+
note,
180+
updated_at
181+
)
182+
VALUES (
183+
p_org_id,
184+
p_user_id,
185+
p_from_ts,
186+
p_to_ts,
187+
p_currency,
188+
v_orders_scanned,
189+
v_gross,
190+
v_discount,
191+
v_tax,
192+
v_net,
193+
v_avg,
194+
v_top_sku,
195+
v_top_sku_qty,
196+
p_note,
197+
now()
198+
)
199+
ON CONFLICT (org_id, user_id, period_from, period_to, currency)
200+
DO UPDATE SET
201+
orders_scanned = EXCLUDED.orders_scanned,
202+
gross_total = EXCLUDED.gross_total,
203+
discount_total = EXCLUDED.discount_total,
204+
tax_total = EXCLUDED.tax_total,
205+
net_total = EXCLUDED.net_total,
206+
avg_order_total = EXCLUDED.avg_order_total,
207+
top_sku = EXCLUDED.top_sku,
208+
top_sku_qty = EXCLUDED.top_sku_qty,
209+
note = COALESCE(EXCLUDED.note, app_public.order_rollup.note),
210+
updated_at = now();
211+
212+
GET DIAGNOSTICS v_rowcount = ROW_COUNT;
213+
v_orders_upserted := v_rowcount;
214+
215+
/*
216+
Example dynamic query:
217+
- pretend the user can pass a “filter table” name, etc.
218+
- show format() + USING
219+
Here we just demonstrate a safe dynamic query that counts rows from a known table.
220+
*/
221+
v_sql := format(
222+
'SELECT count(*)::int FROM %I.%I WHERE org_id = $1 AND created_at >= $2 AND created_at < $3',
223+
'app_public',
224+
'app_order'
225+
);
226+
227+
EXECUTE v_sql
228+
INTO v_rowcount
229+
USING p_org_id, p_from_ts, p_to_ts;
230+
231+
IF p_debug THEN
232+
RAISE NOTICE 'dynamic count(app_order)=%', v_rowcount;
233+
END IF;
234+
235+
-- Return a single row (RETURNS TABLE)
236+
org_id := p_org_id;
237+
user_id := p_user_id;
238+
period_from := p_from_ts;
239+
period_to := p_to_ts;
240+
orders_scanned := v_orders_scanned;
241+
orders_upserted := v_orders_upserted;
242+
gross_total := v_gross;
243+
discount_total := v_discount;
244+
tax_total := v_tax;
245+
net_total := v_net;
246+
avg_order_total := round(v_avg, p_round_to);
247+
top_sku := v_top_sku;
248+
top_sku_qty := v_top_sku_qty;
249+
message := format(
250+
'rollup ok: gross=%s discount=%s tax=%s net=%s (discount_rate=%s tax_rate=%s)',
251+
v_gross, v_discount, v_tax, v_net, v_discount_rate, v_tax_rate
252+
);
253+
254+
RETURN NEXT;
255+
RETURN;
256+
257+
EXCEPTION
258+
WHEN unique_violation THEN
259+
-- example: if you had other inserts that might conflict
260+
RAISE NOTICE 'unique_violation: %', SQLERRM;
261+
RAISE;
262+
WHEN others THEN
263+
IF p_debug THEN
264+
RAISE NOTICE 'error: % (%:%)', SQLERRM, SQLSTATE, SQLERRM;
265+
END IF;
266+
RAISE;
267+
END;
268+
$$;
269+
270+
-- Example calls using named params (:=)
271+
-- (Swap UUIDs with real values)
272+
SELECT *
273+
FROM app_public.big_kitchen_sink(
274+
p_org_id := '00000000-0000-0000-0000-000000000001',
275+
p_user_id := '00000000-0000-0000-0000-000000000002',
276+
p_from_ts := now() - interval '7 days',
277+
p_to_ts := now(),
278+
p_min_total := 25,
279+
p_apply_discount := true,
280+
p_discount_rate := 0.10,
281+
p_tax_rate := 0.0925,
282+
p_round_to := 2,
283+
p_note := 'weekly rollup',
284+
p_lock := true,
285+
p_debug := true
286+
);

packages/plpgsql-deparser/__tests__/pretty/__snapshots__/plpgsql-pretty.test.ts.snap

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
22

3+
exports[`lowercase: big-function.sql 1`] = `
4+
"begin
5+
if val > 100 then
6+
return 'large';
7+
elsif val > 10 then
8+
return 'medium';
9+
else
10+
return 'small';
11+
end if;
12+
return;
13+
end"
14+
`;
15+
316
exports[`lowercase: if-else-function.sql 1`] = `
417
"begin
518
if val > 100 then
@@ -31,6 +44,19 @@ exports[`lowercase: simple-function.sql 1`] = `
3144
end"
3245
`;
3346

47+
exports[`uppercase: big-function.sql 1`] = `
48+
"BEGIN
49+
IF val > 100 THEN
50+
RETURN 'large';
51+
ELSIF val > 10 THEN
52+
RETURN 'medium';
53+
ELSE
54+
RETURN 'small';
55+
END IF;
56+
RETURN;
57+
END"
58+
`;
59+
3460
exports[`uppercase: if-else-function.sql 1`] = `
3561
"BEGIN
3662
IF val > 100 THEN

packages/plpgsql-deparser/__tests__/pretty/plpgsql-pretty.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { PlpgsqlPrettyTest } from '../../test-utils';
22

33
const prettyTest = new PlpgsqlPrettyTest([
4+
'big-function.sql',
45
'simple-function.sql',
56
'if-else-function.sql',
67
'loop-function.sql',

packages/plpgsql-deparser/test-utils/PlpgsqlPrettyTest.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export class PlpgsqlPrettyTest {
2323
it(`uppercase: ${testName}`, () => {
2424
const sql = fs.readFileSync(filePath, 'utf-8').trim();
2525
const result = parsePlPgSQLSync(sql) as unknown as PLpgSQLParseResult;
26+
console.log(JSON.stringify(result, null, 2));
2627
const deparsed = deparseSync(result, { uppercase: true });
2728
expect(deparsed).toMatchSnapshot();
2829
});

0 commit comments

Comments
 (0)