fix(sql): recover T-SQL bracket-named, CREATE OR ALTER, and PROC routines - #2960
fix(sql): recover T-SQL bracket-named, CREATE OR ALTER, and PROC routines#2960egarcia74 wants to merge 4 commits into
Conversation
T-SQL's AS BEGIN...END body idiom never parses structurally (the grammar has no create_procedure form for it), so recovery from ERROR nodes is the only path such routines have into the graph — and the recovery pattern matched only bare or double-quoted names and only OR REPLACE. In a T-SQL codebase that brackets every identifier ([dbo].[usp_X], a common house standard) and uses CREATE OR ALTER, every stored procedure silently vanished: 0/26 recovered in the reporting corpus. - accept bracket-delimited name parts and OR ALTER, mirroring what fb_proc_or_trigger already does for Firebird - hoist the pattern into a shared _ROUTINE_RECOVERY_RX used by both recovery sites (walk-time ERROR scan and whole-file has_error fallback): when the two drifted, a mixed-delimiter name (dbo.[usp_Mixed]) was captured differently by each, minting a second phantom node named after the schema that id-dedupe could not catch Recovered routines stay name-only nodes (no body reads_from edges), matching the existing PL/pgSQL recovery. Each new guard was mutation-tested: dropping the bracket alternative, dropping OR ALTER, and re-introducing the pattern drift each fail exactly their test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ames [a]]b] names the identifier a]b; stopping at the first ] truncated the recovered routine to [dbo].[a] — a phantom that could collide with a genuinely named [dbo].[a]. The bracketed-part alternative now consumes ]] before treating a lone ] as the closing delimiter, in both name-part positions of the shared _ROUTINE_RECOVERY_RX. Mutation-tested: reverting the escape handling fails the new regression test. Addresses the CodeRabbit P2 review finding on PR #2. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Upstream batches changelog entries in maintainer release commits; the entry's content moves to the PR description.
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Extends SQL routine recovery to handle T-SQL dialect: adds bracket-delimited identifiers ([dbo].[usp_Load], with ]] escaping), CREATE OR ALTER, and the PROC shorthand, and consolidates the previously-duplicated recovery regex into a shared _ROUTINE_RECOVERY_RX so the walk-time ERROR scan and whole-file fallback can't drift and double-emit. Adds _mask_sql_comments (offset-preserving) around the whole-file scan so commented-out DDL can't fabricate routine nodes when an unrelated parse error arms recovery. Covers all of this with new test_sql_* cases.
Worth a look
- Comment masking treats -- inside string literals as a real comment —
graphify/extractors/sql.py:37· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 279 functions depend on the 117 functions this change touches.
Health — this change adds coupling hotspots:
- new:
dispatch_command()— 2 callers, 119 callees - new:
extract_sql()— 15 callers, 9 callees - new:
walk()— 1 callers, 8 callees - new:
test_poisoned_manifest_is_healed()— 0 callers, 6 callees
Verification — 279 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 128 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify extract\_sql.
The verifier did not have enough to check extract\_sql, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
· 4 more finding(s) on lines outside this diff (see the check run).
…ask the ERROR-node scan The recovery mask read '-- note' inside a string literal as a line comment (blanking to end-of-line) and a /* inside a string as a block comment opener (blanking through the next real */), so real DDL sharing the span could be hidden. The mask now preserves single-quoted strings (with '' escapes), double-quoted identifiers, and bracket-delimited identifiers (with ]] escapes) before blanking comments. Literal patterns are deliberately single-line: the mask only runs on files that already failed to parse, where an unclosed quote is likely, and a multi-line match would let one unclosed delimiter swallow real DDL below it. The walk-time ERROR-node scan now masks too: an ERROR blob whose byte span covers commented-out DDL fabricated a routine node from it exactly as the whole-file scan once did (reproduced: a -- CREATE PROC line sandwiched between broken segments). Both guards are mutation-tested: a literal-blind mask fails the new unit pin; an unmasked ERROR scan fails the extended fabrication test.
|
Addressed the Graphify-review finding (
Both directions are mutation-tested: a literal-blind mask fails the new unit pin ( |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Extends SQL routine recovery in extract_sql to handle T-SQL bracket-delimited identifiers ([dbo].[usp_Load], with ]] escapes), CREATE OR ALTER, and the PROC shorthand, and consolidates the walk-time ERROR scan and whole-file fallback onto a single shared _ROUTINE_RECOVERY_RX so the two sites can no longer capture divergent names and double-emit nodes. Adds _mask_sql_comments to blank comment spans (offset-preserving, literals kept verbatim) at both recovery sites so commented-out DDL can't fabricate routine nodes. Adds tests covering the bracketed/OR ALTER/PROC forms, escaped-bracket names, cross-site name agreement, and comment masking.
Worth a look
- Routine recovery still matches DDL text inside string literals —
graphify/extractors/sql.py:510· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Optional tree_sitter_sql dependency is imported instead of skipped —
tests/test_multilang.py:536· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 283 functions depend on the 121 functions this change touches.
Health — this change adds coupling hotspots:
- new:
dispatch_command()— 2 callers, 119 callees - new:
extract_sql()— 16 callers, 9 callees - new:
walk()— 1 callers, 9 callees - new:
test_poisoned_manifest_is_healed()— 0 callers, 6 callees
Verification — 283 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 132 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify extract\_sql.
The verifier did not have enough to check extract\_sql, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
· 4 more finding(s) on lines outside this diff (see the check run).
Problem
T-SQL stored procedures and functions are silently dropped from the graph. On a real 391-file T-SQL data-migration corpus (SQL Server DACPAC project: staging/model/transform layers,
[Schema].[Object]naming throughout), 0 of 26 stored procedures produced a node — while tables and views extracted fine, so the gap is invisible unless you go counting.Two independent defects stack:
CREATE PROCEDURE ... AS BEGIN ... ENDnever parses structurally in tree-sitter-sql (there is nocreate_procedureproduction for theAS BEGINbody idiom), so ERROR-node regex recovery is these objects' only path into the graph.CREATE PROCEDURE [dbo].[usp_Load]), noCREATE OR ALTER, noPROCshorthand. On this corpus, every routine uses brackets, so recovery recovered nothing.What this PR does
All changes are in
graphify/extractors/sql.py:_ROUTINE_RECOVERY_RX, hoisted to module level and used by both recovery sites (the walk-time ERROR-node scan and the whole-filehas_errorfallback). The two sites had drifted; the drifted second site is what minted a phantomdbo()node for a mixed-delimiter name (CREATE PROCEDURE dbo.[usp_Mixed]) — the schema captured as if it were the routine. Sharing the pattern fixes that and prevents re-drift.]]escape for a literal]inside a name ([dbo].[a]]b]recovers as one routine, fully consumed, not truncated at the escape).CREATE OR ALTERalongside the existingOR REPLACE, and thePROCshorthand alongsidePROCEDURE._mask_sql_comments(src)(offset-preserving blanking of--and/* */), so commented-out DDL in a file with an unrelated parse error can no longer fabricate routine nodes.Recovered routines are name-only nodes (no body
reads_fromedges), matching the existing PL/pgSQL recovery behavior — a named node with acontainsedge beats an invisible object.Result on the real corpus
391
.sqlfiles, before → after: 1 → 28 routine nodes, including every bracket-named[Audit]/[Utils]/[Monitoring]procedure; table and view counts unchanged; no phantom schema nodes.Tests
Added in
tests/test_multilang.py, each verified to fail with the fix reverted:[dbo].[usp_X]name;CREATE OR ALTER PROCEDURErecovers;PROCshorthand recovers;dbo.[usp_Mixed]) yields exactly one node — both recovery sites agree on the captured name (the phantom-dbo()regression);]]escape inside a bracket-delimited name is consumed, capturing[dbo].[a]]b]()as one routine;Validation
6 failed, 4836 passed, 28 skipped— the 6 (test_ollama.py,test_ollama_retry_cap.py) fail identically on unmodifiedv8in this environment (local Ollama config), none touch SQL.ruff check graphify/ tests/: all checks passed.Note
Independent of my companion PR #2959 making
tree-sitter-sqla core dependency; both touchtests/test_multilang.py, and a local merge of the two branches resolves trivially (the combined branch produced the 1 → 28 measurement above and runs in production).