You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: add AGENTS.md with fixture workflow and update generated.json
- Add AGENTS.md documenting the proper workflow for adding test fixtures
- Update generated.json with new fixtures for OUT parameters pattern
- Test 15: OUT parameters with SELECT INTO multiple variables
- Test 16: OUT parameters with SELECT INTO STRICT
Copy file name to clipboardExpand all lines: __fixtures__/plpgsql-generated/generated.json
+5-3Lines changed: 5 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -100,8 +100,10 @@
100
100
"plpgsql_deparser_fixes-11.sql": "-- Test 11: OUT parameter function with bare RETURN\nCREATE FUNCTION test_out_params(OUT ok boolean, OUT message text)\nLANGUAGE plpgsql AS $$\nBEGIN\n ok := true;\n message := 'success';\n RETURN;\nEND$$",
101
101
"plpgsql_deparser_fixes-12.sql": "-- Test 12: RETURNS TABLE function with RETURN QUERY\nCREATE FUNCTION test_returns_table(p_prefix text)\nRETURNS TABLE(id int, name text)\nLANGUAGE plpgsql AS $$\nBEGIN\n RETURN QUERY SELECT 1, p_prefix || '_one';\n RETURN QUERY SELECT 2, p_prefix || '_two';\n RETURN;\nEND$$",
102
102
"plpgsql_deparser_fixes-13.sql": "-- Test 13: Trigger function with complex logic\nCREATE FUNCTION test_trigger_complex() RETURNS trigger\nLANGUAGE plpgsql AS $$\nDECLARE\n defaults_record record;\n bit_len int;\nBEGIN\n bit_len := bit_length(NEW.permissions);\n\n SELECT * INTO defaults_record\n FROM permission_defaults AS t\n LIMIT 1;\n\n IF found THEN\n NEW.is_approved := defaults_record.is_approved;\n NEW.is_verified := defaults_record.is_verified;\n END IF;\n\n IF NEW.is_owner IS TRUE THEN\n NEW.is_admin := true;\n NEW.is_approved := true;\n NEW.is_verified := true;\n END IF;\n\n SELECT\n NEW.is_approved IS TRUE\n AND NEW.is_verified IS TRUE\n AND NEW.is_disabled IS FALSE INTO NEW.is_active;\n\n RETURN NEW;\nEND$$",
103
-
"plpgsql_deparser_fixes-14.sql": "-- Test 14: Procedure (implicit void return)\nCREATE PROCEDURE test_procedure(p_message text)\nLANGUAGE plpgsql AS $$\nBEGIN\n RAISE NOTICE '%', p_message;\nEND$$",
104
-
"plpgsql_control-1.sql": "--\n-- Tests for PL/pgSQL control structures\n--\n\n-- integer FOR loop\n\ndo $$\nbegin\n -- basic case\n for i in 1..3 loop\n raise notice '1..3: i = %', i;\n end loop;\n -- with BY, end matches exactly\n for i in 1..10 by 3 loop\n raise notice '1..10 by 3: i = %', i;\n end loop;\n -- with BY, end does not match\n for i in 1..11 by 3 loop\n raise notice '1..11 by 3: i = %', i;\n end loop;\n -- zero iterations\n for i in 1..0 by 3 loop\n raise notice '1..0 by 3: i = %', i;\n end loop;\n -- REVERSE\n for i in reverse 10..0 by 3 loop\n raise notice 'reverse 10..0 by 3: i = %', i;\n end loop;\n -- potential overflow\n for i in 2147483620..2147483647 by 10 loop\n raise notice '2147483620..2147483647 by 10: i = %', i;\n end loop;\n -- potential overflow, reverse direction\n for i in reverse -2147483620..-2147483647 by 10 loop\n raise notice 'reverse -2147483620..-2147483647 by 10: i = %', i;\n end loop;\nend$$",
103
+
"plpgsql_deparser_fixes-14.sql": "-- Test 14: Procedure (implicit void return)\nCREATE PROCEDURE test_procedure(p_message text)\nLANGUAGE plpgsql AS $$\nBEGIN\n RAISE NOTICE '%', p_message;\nEND$$",
104
+
"plpgsql_deparser_fixes-15.sql": "-- Test 15: OUT parameters with SELECT INTO multiple variables\n-- This pattern is used in auth functions (sign_in, sign_up) where we need to\n-- populate multiple OUT parameters from a single SELECT statement\nCREATE FUNCTION test_out_params_select_into(\n p_user_id uuid,\n OUT id uuid,\n OUT user_id uuid,\n OUT access_token text,\n OUT access_token_expires_at timestamptz,\n OUT is_verified boolean,\n OUT totp_enabled boolean\n)\nLANGUAGE plpgsql AS $$\nDECLARE\n v_token_id uuid;\n v_plaintext_token text;\nBEGIN\n v_plaintext_token := encode(gen_random_bytes(48), 'hex');\n v_token_id := uuid_generate_v5(uuid_ns_url(), v_plaintext_token);\n \n INSERT INTO tokens (id, user_id, access_token_hash)\n VALUES (v_token_id, p_user_id, digest(v_plaintext_token, 'sha256'));\n \n SELECT tkn.id, tkn.user_id, v_plaintext_token, tkn.access_token_expires_at, tkn.is_verified, tkn.totp_enabled\n INTO id, user_id, access_token, access_token_expires_at, is_verified, totp_enabled\n FROM tokens AS tkn\n WHERE tkn.id = v_token_id;\n \n RETURN;\nEND$$",
105
+
"plpgsql_deparser_fixes-16.sql": "-- Test 16: OUT parameters with SELECT INTO and STRICT\nCREATE FUNCTION test_out_params_strict(\n p_id uuid,\n OUT name text,\n OUT email text\n)\nLANGUAGE plpgsql AS $$\nBEGIN\n SELECT u.name, u.email INTO STRICT name, email\n FROM users u\n WHERE u.id = p_id;\nEND$$",
106
+
"plpgsql_control-1.sql":"--\n-- Tests for PL/pgSQL control structures\n--\n\n-- integer FOR loop\n\ndo $$\nbegin\n -- basic case\n for i in 1..3 loop\n raise notice '1..3: i = %', i;\n end loop;\n -- with BY, end matches exactly\n for i in 1..10 by 3 loop\n raise notice '1..10 by 3: i = %', i;\n end loop;\n -- with BY, end does not match\n for i in 1..11 by 3 loop\n raise notice '1..11 by 3: i = %', i;\n end loop;\n -- zero iterations\n for i in 1..0 by 3 loop\n raise notice '1..0 by 3: i = %', i;\n end loop;\n -- REVERSE\n for i in reverse 10..0 by 3 loop\n raise notice 'reverse 10..0 by 3: i = %', i;\n end loop;\n -- potential overflow\n for i in 2147483620..2147483647 by 10 loop\n raise notice '2147483620..2147483647 by 10: i = %', i;\n end loop;\n -- potential overflow, reverse direction\n for i in reverse -2147483620..-2147483647 by 10 loop\n raise notice 'reverse -2147483620..-2147483647 by 10: i = %', i;\n end loop;\nend$$",
105
107
"plpgsql_control-2.sql": "-- BY can't be zero or negative\ndo $$\nbegin\n for i in 1..3 by 0 loop\n raise notice '1..3 by 0: i = %', i;\n end loop;\nend$$",
106
108
"plpgsql_control-3.sql": "do $$\nbegin\n for i in 1..3 by -1 loop\n raise notice '1..3 by -1: i = %', i;\n end loop;\nend$$",
107
109
"plpgsql_control-4.sql": "do $$\nbegin\n for i in reverse 1..3 by -1 loop\n raise notice 'reverse 1..3 by -1: i = %', i;\n end loop;\nend$$",
@@ -189,4 +191,4 @@
189
191
"plpgsql_array-21.sql": "-- some types don't support arrays\ndo $$\ndeclare\n v pg_node_tree;\n v1 v%type[];\nbegin\nend;\n$$",
When adding new test fixtures for the PL/pgSQL deparser, follow this workflow:
6
+
7
+
### Step 1: Add SQL Fixtures
8
+
9
+
Add your PL/pgSQL function/procedure definitions to the appropriate fixture file in `__fixtures__/plpgsql/`. For deparser-specific fixes, use `plpgsql_deparser_fixes.sql`.
10
+
11
+
Example fixture:
12
+
```sql
13
+
-- Test N: Description of what this tests
14
+
CREATEFUNCTIONtest_example(p_input text, OUT result text)
15
+
LANGUAGE plpgsql AS $$
16
+
BEGIN
17
+
result := p_input;
18
+
RETURN;
19
+
END$$;
20
+
```
21
+
22
+
### Step 2: Generate Test Fixtures
23
+
24
+
Run the fixture generation script from the plpgsql-deparser package:
25
+
26
+
```bash
27
+
cd packages/plpgsql-deparser
28
+
pnpm fixtures
29
+
```
30
+
31
+
This script (`scripts/make-fixtures.ts`):
32
+
1. Reads all `.sql` files from `__fixtures__/plpgsql/`
33
+
2. Parses each file to extract PL/pgSQL statements (CREATE FUNCTION, CREATE PROCEDURE, DO blocks)
34
+
3. Validates each statement can be parsed by the PL/pgSQL parser
35
+
4. Outputs valid fixtures to `__fixtures__/plpgsql-generated/generated.json`
36
+
37
+
### Step 3: Run Tests
38
+
39
+
Run the test suite to verify your fixtures round-trip correctly:
40
+
41
+
```bash
42
+
cd packages/plpgsql-deparser
43
+
pnpm test
44
+
```
45
+
46
+
The round-trip test (`__tests__/plpgsql-deparser.test.ts`):
47
+
1. Loads all fixtures from `generated.json`
48
+
2. For each fixture: parse -> deparse -> reparse
49
+
3. Compares the AST from original parse with the AST from reparsed output
50
+
4. Reports any failures (AST mismatches or reparse failures)
51
+
52
+
### Step 4: Commit Both Files
53
+
54
+
Always commit both the fixture file AND the generated.json together:
0 commit comments