Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion graphify/extractors/sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,14 @@ def _collect_defined_names(node) -> None:
if stmt.type == "statement":
for child in stmt.children:
walk(child)
elif stmt.type in ("fb_proc_or_trigger", "set_term", "declare_external_function", "ERROR"):
elif stmt.type in ("fb_proc_or_trigger", "set_term", "declare_external_function",
"ERROR", "transaction"):
# `transaction` wraps BEGIN/START TRANSACTION ... COMMIT/ROLLBACK
# blocks in its own top-level node (#2953), so statements inside
# never reach the `stmt.type == "statement"` branch above and were
# silently dropped. walk() has no special case for "transaction"
# either, so it falls through to its generic per-child recursion,
# which reaches the nested `statement` nodes normally.
walk(stmt)

# Global regex fallback: catch any REFERENCES missed due to ERROR nodes in the parse tree
Expand Down
22 changes: 22 additions & 0 deletions tests/test_multilang.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,28 @@ def test_sql_no_dangling_edges():
for e in r["edges"]:
assert e["source"] in node_ids, f"dangling source: {e['source']}"

def test_sql_transaction_block_is_not_dropped(tmp_path):
"""#2953: statements wrapped in BEGIN; ... COMMIT; land under a top-level
`transaction` node, not `statement`, and were silently skipped entirely."""
pytest.importorskip("tree_sitter_sql")
p = tmp_path / "con_transaccion.sql"
p.write_text(
"BEGIN;\n"
"CREATE TABLE gamma (id INT PRIMARY KEY);\n"
"CREATE TABLE delta (id INT PRIMARY KEY, gamma_id INT REFERENCES gamma(id));\n"
"COMMIT;\n"
)
r = extract_sql(p)
labels = [n["label"] for n in r["nodes"]]
assert "gamma" in labels
assert "delta" in labels
# Both tables get real source info, not a sourceless _ref_stub
by_label = {n["label"]: n for n in r["nodes"]}
assert by_label["gamma"]["source_location"] == "L2"
assert by_label["delta"]["source_location"] == "L3"
fk_edges = [e for e in r["edges"] if e["relation"] == "references"]
assert len(fk_edges) == 1

def test_sql_cte_is_not_read_as_a_table():
"""#2577: a name bound by WITH ... AS (...) is scoped to its statement, not a table.

Expand Down