Skip to content

fix(sql): T-SQL stored procedures and functions are silently dropped — 0 of 26 recovered on a 391-file SQL Server corpus #3164

Description

@egarcia74

The defect

extract_sql emits no node for T-SQL stored procedures or functions. Tables and views from the same files extract fine, so the gap is invisible unless you count: on a real 391-file SQL Server DACPAC corpus (staging/model/transform layers, [Schema].[Object] naming throughout), 0 of 26 procedures produced a node, no warning, exit 0.

Two independent defects stack:

  1. The grammar cannot parse the T-SQL routine shape. CREATE PROCEDURE ... AS BEGIN ... END has no create_procedure production in tree-sitter-sql (the AS BEGIN body idiom never parses structurally), so the ERROR-node regex recovery in graphify/extractors/sql.py is these objects' only path into the graph.
  2. The recovery regex does not speak T-SQL. It accepts only bare or double-quoted names — no bracket-delimited identifiers (CREATE PROCEDURE [dbo].[usp_Load]), no CREATE OR ALTER (T-SQL has no OR REPLACE), no PROC shorthand. On a corpus where every routine is bracket-named, recovery recovers nothing.

A third, smaller defect surfaces once you look: the two recovery sites (the walk-time ERROR-node scan and the whole-file has_error fallback) carry two copies of the regex that have drifted, so a mixed-delimiter name (CREATE PROCEDURE dbo.[usp_Mixed]) mints a phantom dbo() node from the second site while the first captures dbo.[usp_Mixed].

Reproduce

graphifyy 0.9.51 (current v8), tree-sitter-sql installed.

-- routines.sql
CREATE PROCEDURE [dbo].[usp_Load]
AS
BEGIN
    SELECT 1;
END
GO
CREATE OR ALTER PROC [dbo].[usp_Refresh] AS BEGIN SELECT 2; END
from pathlib import Path
from graphify.extractors.sql import extract_sql
sorted(n["label"] for n in extract_sql(Path("routines.sql"))["nodes"])
# v8:       ['routines.sql']                                    -- no routine nodes
# expected: ['[dbo].[usp_Load]()', '[dbo].[usp_Refresh]()', 'routines.sql']

Expected

usp_Load() and usp_Refresh() as nodes, recovered through the same ERROR-node path that already recovers PL/pgSQL routines (#2180).

Adjacent, not addressed here

A bracket-named CREATE FUNCTION [dbo].[fn_Total]() RETURNS INT AS BEGIN RETURN 1; END does parse structurally, but its label comes out as dbo].[fn_Total( — the outer delimiters are stripped as if the whole [dbo].[fn_Total] were one bracketed token. That is a separate defect in the structural create_function path; noting it so it is not mistaken for a recovery gap.

Proposed fix

A PR is ready: one shared module-level recovery pattern used by both sites (fixes the drift/phantom), bracket-delimited identifiers including the ]] escape, CREATE OR ALTER, PROC, a \b before CREATE, and a comment/string-masked whole-file scan so commented-out or dynamic-SQL DDL cannot fabricate routines when an unrelated parse error arms recovery. All changes are in graphify/extractors/sql.py; tests in tests/test_multilang.py.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions