mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-25 16:22:30 +00:00
fix(sql): end the statement at GO batch separators
GO is a client-tool command, not SQL, and tree-sitter-sql has no rule
for it. Before parsing, each GO [n] line found on the masked text
(comments and strings blanked) becomes `;` plus spaces, so byte
offsets and line numbers don't move. A go line after a comma, or
before a comma or `)`, is a column and stays.
AST cache schema 20 -> 21 so cached .sql extractions re-run.
(cherry picked from commit 2293c31d79)
This commit is contained in:
1 parent
01715accd5
commit
9ce1404719
2 files changed
+44
-2
No files matched your search
+1
-1
@@ -37,7 +37,7 @@ except Exception:
|
||||
_EXTRACTOR_VERSION = "unknown"
|
||||
|
||||
# Bump when AST cache-key semantics change independently of the package version.
|
||||
_AST_CACHE_SCHEMA = 22 # SQL labels drop T-SQL brackets and routine bodies emit `reads_from`/`writes_to` (#4635), so cached `.sql` extractions re-run; prior (21): a CommonJS `const X = require('./m')` binding no longer mints a module-scope node that shadows the class the required file exports, so `new X()` resolves to the required declaration (#4621); prior (20): Obsidian `[[wikilink]]` targets that start with the scan root's own folder names now resolve to the real page (#4559), so cached markdown extractions re-run; prior (19): Dart `typedef` declarations are extracted as aliases (not variables of type `typedef`); C++ Metal/CUDA GPU qualifiers no longer mint sourceless god nodes (#4550); a `.h` header containing `extern "C"` routes to the C++ extractor; prior (18): Kotlin `typealias` (#4553), C# `delegate` (#4552), and Swift `macro` declarations (#4551) are now extracted so references stop minting sourceless stubs; Rust `use` rooted at a `mod` declared in the same scope carries `_rust_use_name` (#4564); prior (17): Python pytest-fixture markers (`_py_fixture`/`_python_fixture_param`, #4332) and module-level class-id collision salting (#2810); TS `this.field` typed from any method incl. static (#4473); Solidity callables carry `metadata.arity` + inherited-call/`super`/modifier raw calls (#4379); Go type-decl and Rust file-item case-distinct node ids (#4444); prior (16): Rust `use` edges carry the imported name as written (`_rust_use_name`, #4439); (15): Go generic receiver types normalized (`Box[T]` -> `Box`) so methods attach to the type node; prior (14): C# receiver resolution for nameless/`new X()`/`foreach` receivers (defer + receiver-chain markers) and `_`/case-distinct node ids (underscore-salted); prior (13): Kotlin/Scala/C++/Swift generic & template type params no longer mint sourceless stubs; Objective-C NS_ENUM/NS_OPTIONS extraction and `_callable`/`_callable_class` markers; Go and PHP receiver-typed member calls park cross-repo (`go_type_table`/`php_type_table`, receiver fields); prior (12): Java inherited-supertype (`receiver_unbound`) and chained/new/cast receiver typing (`java_receiver_chain`, `_java_ret`); C/C++ quoted cross-directory include provenance (`include_kind`); JS external imports anchored to the package, not the path leaf; prior (11): Python property-accessor decorators / `Self`, Swift `Self`, Go generic type params, Python forward type-hint file node, PowerShell dot-source, C# verbatim-interpolated string recovery; (10): C# partial types carry `type_path`; (9): Python property-read raw calls and `_py_property` nodes; (8): Rust `use`-group per-binding edges; (7): Python opaque-base/super(args) markers + nested-class enclosing ids.
|
||||
_AST_CACHE_SCHEMA = 22 # SQL labels drop T-SQL brackets and routine bodies emit `reads_from`/`writes_to` (#4635), and a `GO` batch separator ends a statement (#4651), so cached `.sql` extractions re-run; prior (21): a CommonJS `const X = require('./m')` binding no longer mints a module-scope node that shadows the class the required file exports, so `new X()` resolves to the required declaration (#4621); prior (20): Obsidian `[[wikilink]]` targets that start with the scan root's own folder names now resolve to the real page (#4559), so cached markdown extractions re-run; prior (19): Dart `typedef` declarations are extracted as aliases (not variables of type `typedef`); C++ Metal/CUDA GPU qualifiers no longer mint sourceless god nodes (#4550); a `.h` header containing `extern "C"` routes to the C++ extractor; prior (18): Kotlin `typealias` (#4553), C# `delegate` (#4552), and Swift `macro` declarations (#4551) are now extracted so references stop minting sourceless stubs; Rust `use` rooted at a `mod` declared in the same scope carries `_rust_use_name` (#4564); prior (17): Python pytest-fixture markers (`_py_fixture`/`_python_fixture_param`, #4332) and module-level class-id collision salting (#2810); TS `this.field` typed from any method incl. static (#4473); Solidity callables carry `metadata.arity` + inherited-call/`super`/modifier raw calls (#4379); Go type-decl and Rust file-item case-distinct node ids (#4444); prior (16): Rust `use` edges carry the imported name as written (`_rust_use_name`, #4439); (15): Go generic receiver types normalized (`Box[T]` -> `Box`) so methods attach to the type node; prior (14): C# receiver resolution for nameless/`new X()`/`foreach` receivers (defer + receiver-chain markers) and `_`/case-distinct node ids (underscore-salted); prior (13): Kotlin/Scala/C++/Swift generic & template type params no longer mint sourceless stubs; Objective-C NS_ENUM/NS_OPTIONS extraction and `_callable`/`_callable_class` markers; Go and PHP receiver-typed member calls park cross-repo (`go_type_table`/`php_type_table`, receiver fields); prior (12): Java inherited-supertype (`receiver_unbound`) and chained/new/cast receiver typing (`java_receiver_chain`, `_java_ret`); C/C++ quoted cross-directory include provenance (`include_kind`); JS external imports anchored to the package, not the path leaf; prior (11): Python property-accessor decorators / `Self`, Swift `Self`, Go generic type params, Python forward type-hint file node, PowerShell dot-source, C# verbatim-interpolated string recovery; (10): C# partial types carry `type_path`; (9): Python property-read raw calls and `_py_property` nodes; (8): Rust `use`-group per-binding edges; (7): Python opaque-base/super(args) markers + nested-class enclosing ids.
|
||||
|
||||
|
||||
# Per-process memo: distributions() scans site-packages metadata, which is
|
||||
|
||||
@@ -348,6 +348,44 @@ def _routine_body_refs(masked: str) -> list[tuple[str, str, int]]:
|
||||
return [(relation, name, pos) for pos, relation, name in sorted(refs)]
|
||||
|
||||
|
||||
# A `GO` batch separator is a client-tool command, not SQL: tree-sitter-sql reads it as an
|
||||
# identifier, so without a `;` before it the next statement is swallowed into an ERROR node.
|
||||
# Found on the masked text (comments and strings blanked), alone on its line, optionally
|
||||
# followed by a repeat count.
|
||||
_GO_LINE_RX = re.compile(r"^[ \t]*(GO(?:[ \t]+\d+)?)[ \t\r]*;?[ \t\r]*$", re.IGNORECASE | re.MULTILINE)
|
||||
|
||||
|
||||
def _go_separators(source: bytes) -> list[tuple[int, int]]:
|
||||
"""Byte spans of the `GO [n]` batch-separator lines in a T-SQL script.
|
||||
|
||||
A bare `go` on its own line is a column or alias, not a separator, only when the last
|
||||
non-blank character before it is a comma or the first one after it is a comma or `)`.
|
||||
Any keyword may precede a real separator (`SET NOCOUNT ON` / `GO`), so none is consulted.
|
||||
"""
|
||||
try:
|
||||
text = source.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
return []
|
||||
masked = _scan_sql(text)[0]
|
||||
spans: list[tuple[int, int]] = []
|
||||
size = len(masked)
|
||||
char_pos = byte_pos = 0 # a character offset and its byte offset, advanced as matches are met
|
||||
for m in _GO_LINE_RX.finditer(masked):
|
||||
before, after = m.start() - 1, m.end()
|
||||
while before >= 0 and masked[before].isspace():
|
||||
before -= 1
|
||||
while after < size and masked[after].isspace():
|
||||
after += 1
|
||||
start, end = m.start(1), m.end(1)
|
||||
byte_start = byte_pos + len(text[char_pos:start].encode("utf-8"))
|
||||
byte_end = byte_start + len(text[start:end].encode("utf-8"))
|
||||
char_pos, byte_pos = end, byte_end
|
||||
if (before >= 0 and masked[before] == ",") or (after < size and masked[after] in ",)"):
|
||||
continue
|
||||
spans.append((byte_start, byte_end))
|
||||
return spans
|
||||
|
||||
|
||||
def _mask_sql_comments(text: str) -> str:
|
||||
"""Masked text only — see _scan_sql for the span-reporting form."""
|
||||
return _scan_sql(text)[0]
|
||||
@@ -422,7 +460,11 @@ def extract_sql(path: Path, content: str | bytes | None = None) -> dict:
|
||||
else content if content is not None
|
||||
else _read_source_bytes(path)
|
||||
)
|
||||
tree = parser.parse(source)
|
||||
# `GO` is not SQL; as `;` (same length, so offsets and lines hold) it ends the statement.
|
||||
patched = bytearray(source)
|
||||
for start, end in _go_separators(source):
|
||||
patched[start:end] = b";" + b" " * (end - start - 1)
|
||||
tree = parser.parse(bytes(patched))
|
||||
root = tree.root_node
|
||||
except Exception as e:
|
||||
return {"nodes": [], "edges": [], "error": str(e)}
|
||||
|
||||
Reference in new issue
Block a user