Parse a `.sql` file. Extracts: - Tables (CREATE TABLE) → Class nodes with extra["sql_kind"]="table" - Views (CREATE VIEW) → Class nodes with extra["sql_kind"]="view" - Functions (CREATE FUNCTION) → Function nodes with extra["sql_kind"]="function" - Procedur
(
self, path: Path, source: bytes,
)
| 2001 | }) |
| 2002 | |
| 2003 | def _parse_sql( |
| 2004 | self, path: Path, source: bytes, |
| 2005 | ) -> tuple[list[NodeInfo], list[EdgeInfo]]: |
| 2006 | """Parse a `.sql` file. |
| 2007 | |
| 2008 | Extracts: |
| 2009 | - Tables (CREATE TABLE) → Class nodes with extra["sql_kind"]="table" |
| 2010 | - Views (CREATE VIEW) → Class nodes with extra["sql_kind"]="view" |
| 2011 | - Functions (CREATE FUNCTION) → Function nodes with extra["sql_kind"]="function" |
| 2012 | - Procedures (CREATE PROCEDURE, regex fallback) → Function nodes with |
| 2013 | extra["sql_kind"]="procedure" |
| 2014 | |
| 2015 | Data dependencies (FROM/JOIN table references) are recorded as |
| 2016 | IMPORTS_FROM edges so the impact-radius query can follow them. |
| 2017 | """ |
| 2018 | text = source.decode("utf-8", errors="replace") |
| 2019 | file_path_str = str(path) |
| 2020 | test_file = _is_test_file(file_path_str) |
| 2021 | |
| 2022 | nodes: list[NodeInfo] = [] |
| 2023 | edges: list[EdgeInfo] = [] |
| 2024 | |
| 2025 | nodes.append(NodeInfo( |
| 2026 | kind="File", |
| 2027 | name=file_path_str, |
| 2028 | file_path=file_path_str, |
| 2029 | line_start=1, |
| 2030 | line_end=text.count("\n") + 1, |
| 2031 | language="sql", |
| 2032 | is_test=test_file, |
| 2033 | )) |
| 2034 | |
| 2035 | # --- tree-sitter pass --- |
| 2036 | parser = self._get_parser("sql") |
| 2037 | if parser: |
| 2038 | tree = parser.parse(source) |
| 2039 | self._walk_sql_tree( |
| 2040 | tree.root_node, source, file_path_str, nodes, edges, |
| 2041 | ) |
| 2042 | |
| 2043 | # --- regex fallback for CREATE PROCEDURE --- |
| 2044 | for m in self._SQL_PROC_RE.finditer(text): |
| 2045 | raw_name = m.group(1) |
| 2046 | name = raw_name.split(".")[-1] # strip schema prefix |
| 2047 | line = text[: m.start()].count("\n") + 1 |
| 2048 | qualified = f"{file_path_str}::{name}" |
| 2049 | nodes.append(NodeInfo( |
| 2050 | kind="Function", |
| 2051 | name=name, |
| 2052 | file_path=file_path_str, |
| 2053 | line_start=line, |
| 2054 | line_end=line, |
| 2055 | language="sql", |
| 2056 | extra={"sql_kind": "procedure"}, |
| 2057 | )) |
| 2058 | edges.append(EdgeInfo( |
| 2059 | kind="CONTAINS", |
| 2060 | source=file_path_str, |
no test coverage detected