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
The SQL extractor still silently drops CREATE FUNCTION definitions whose PL/pgSQL body contains PL/pgSQL-only statements. On a real (private) PostgreSQL schema this loses 57 of 186 functions (31%) from a single file, with no warning and exit code 0 — so a consumer cannot tell the graph is incomplete.
This looks like an incomplete fix for #1910 ("ERROR nodes on PostgreSQL/PLpgSQL functions", closed by 49df466, shipped 0.9.17). That fix's closing note says the extractor now recovers names from ERROR nodes for "PL/pgSQL bodies with OUT params, tagged dollar-quotes, PERFORM/:= parse as ERROR". PERFORM and := still drop in 0.9.26 — see the matrix below.
Two files, identical except for one statement in the body. Both are valid PostgreSQL, both RETURNS "void".
migrations/20260101000001_dropped.sql:
CREATE OR REPLACEFUNCTION "public"."probe_no_return"("p_id""uuid") RETURNS "void"
LANGUAGE "plpgsql" SECURITY DEFINER
SET"search_path" TO 'public'AS $$
BEGIN
RAISE EXCEPTION 'insufficient_privilege' USING ERRCODE ='42501';
END;
$$;
migrations/20260101000002_extracted.sql:
CREATE OR REPLACEFUNCTION "public"."probe_with_return"("p_id""uuid") RETURNS "void"
LANGUAGE "plpgsql" SECURITY DEFINER
SET"search_path" TO 'public'AS $$
BEGIN
RETURN;
END;
$$;
Expected: 4 nodes (2 file nodes + 2 function nodes). Actual: 3 nodes — only "public"."probe_with_return"() is present. probe_no_return is absent, with no diagnostic.
Which body statements decide it
Same 7-line function each time, RETURNS "void", varying only the single body statement:
Body statement
Result
RETURN;
extracted
INSERT INTO public.t (a) VALUES (1);
extracted
UPDATE public.t SET a = 1;
extracted
NULL;
dropped
RAISE EXCEPTION 'boom';
dropped
RAISE NOTICE 'hi';
dropped
PERFORM public.other_fn();
dropped
x := 1;
dropped
IF true THEN RAISE EXCEPTION 'x'; END IF;
dropped
Bodies parseable as plain SQL survive; bodies using PL/pgSQL-only constructs are dropped. PERFORM and := are the two cases #1910's fix specifically claims to handle.
Impact at scale
Measured on one generated schema file: ~35,000 lines, 186CREATE OR REPLACE FUNCTION declarations, single schema, no overloads.
Of the 57 missing, 44 have bodies using PL/pgSQL-only constructs (RAISE/PERFORM/:=/IF…THEN/DECLARE/LOOP/bare NULL;).
The predicate is not clean at that scale, though: 76 functions that also use those constructs extract fine, and 13 of the missing use none of them. So the single-statement repro above is deterministic, but full-file behaviour likely also depends on the statement mix and where tree-sitter's error recovery resyncs. I did not narrow that further.
Ruled out as the discriminator (each checked across all 186 declarations): position in file / truncation, dollar-quote tag ($$ vs $_$), RETURNS type, SET search_path shape, argument count, identifier length, body length, gap to the next declaration, redefinition in a later file, node-id collisions, and quote-escaping ('') inside a trailing COMMENT ON FUNCTION.
Why this matters more than the coverage number
The failure is silent. Exit code is 0, no ERROR node is surfaced, and the file node is created normally — so the graph looks healthy while a third of the schema's callable surface is missing.
That makes a negative result untrustworthy: "graphify has no node for function X" cannot be distinguished from "function X does not exist". We have had to tell our agents that a hit is trustworthy but a miss is not proof of absence, and to fall back to grep/pg_proc for any database-object question — which removes much of the graph's value for the SQL layer specifically.
Same shape of complaint as #1689 (R files silently dropped with zero warning).
Suggested resolution (in preference order)
Extend the ERROR nodes on PostgreSQL/PLpgSQL functions #1910 ERROR-node name recovery to cover these bodies — at minimum RAISE, PERFORM, :=, IF…THEN, and bare NULL;, which are ordinary PL/pgSQL rather than edge cases.
Failing that, make the drop loud: emit a per-file warning with a count and the offsets it could not recover (e.g. extracted N of M CREATE FUNCTION statements in <file>). A visible partial result is far more useful than a silent one, and lets CI gate on it.
Summary
The SQL extractor still silently drops
CREATE FUNCTIONdefinitions whose PL/pgSQL body contains PL/pgSQL-only statements. On a real (private) PostgreSQL schema this loses 57 of 186 functions (31%) from a single file, with no warning and exit code 0 — so a consumer cannot tell the graph is incomplete.This looks like an incomplete fix for #1910 ("ERROR nodes on PostgreSQL/PLpgSQL functions", closed by
49df466, shipped 0.9.17). That fix's closing note says the extractor now recovers names from ERROR nodes for "PL/pgSQL bodies with OUT params, tagged dollar-quotes, PERFORM/:= parse as ERROR".PERFORMand:=still drop in 0.9.26 — see the matrix below.Version: graphify 0.9.26 (
graphify update <path> --no-cluster), macOS (darwin 25.5.0), Node 24.16.Minimal reproduction
Two files, identical except for one statement in the body. Both are valid PostgreSQL, both
RETURNS "void".migrations/20260101000001_dropped.sql:migrations/20260101000002_extracted.sql:Then:
Expected: 4 nodes (2 file nodes + 2 function nodes).
Actual: 3 nodes — only
"public"."probe_with_return"()is present.probe_no_returnis absent, with no diagnostic.Which body statements decide it
Same 7-line function each time,
RETURNS "void", varying only the single body statement:RETURN;INSERT INTO public.t (a) VALUES (1);UPDATE public.t SET a = 1;NULL;RAISE EXCEPTION 'boom';RAISE NOTICE 'hi';PERFORM public.other_fn();x := 1;IF true THEN RAISE EXCEPTION 'x'; END IF;Bodies parseable as plain SQL survive; bodies using PL/pgSQL-only constructs are dropped.
PERFORMand:=are the two cases #1910's fix specifically claims to handle.Impact at scale
Measured on one generated schema file: ~35,000 lines, 186
CREATE OR REPLACE FUNCTIONdeclarations, single schema, no overloads.RAISE/PERFORM/:=/IF…THEN/DECLARE/LOOP/bareNULL;).Ruled out as the discriminator (each checked across all 186 declarations): position in file / truncation, dollar-quote tag (
$$vs$_$),RETURNStype,SET search_pathshape, argument count, identifier length, body length, gap to the next declaration, redefinition in a later file, node-id collisions, and quote-escaping ('') inside a trailingCOMMENT ON FUNCTION.Why this matters more than the coverage number
The failure is silent. Exit code is 0, no ERROR node is surfaced, and the file node is created normally — so the graph looks healthy while a third of the schema's callable surface is missing.
That makes a negative result untrustworthy: "graphify has no node for function X" cannot be distinguished from "function X does not exist". We have had to tell our agents that a hit is trustworthy but a miss is not proof of absence, and to fall back to
grep/pg_procfor any database-object question — which removes much of the graph's value for the SQL layer specifically.Same shape of complaint as #1689 (R files silently dropped with zero warning).
Suggested resolution (in preference order)
RAISE,PERFORM,:=,IF…THEN, and bareNULL;, which are ordinary PL/pgSQL rather than edge cases.extracted N of M CREATE FUNCTION statements in <file>). A visible partial result is far more useful than a silent one, and lets CI gate on it.tree-sitter-postgresgrammar suggested in ERROR nodes on PostgreSQL/PLpgSQL functions #1910 addresses the class rather than the symptom.Happy to test a patch against the 186-function schema — it is a good stress case, and I can report the before/after extraction count.