sql-code-graph 1.34.4__py3-none-any.whl → 1.35.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sql-code-graph
3
- Version: 1.34.4
3
+ Version: 1.35.1
4
4
  Summary: SQL code graph analyzer and lineage tracer
5
5
  Project-URL: Homepage, https://github.com/Warhorze/sql-code-graph
6
6
  Project-URL: Repository, https://github.com/Warhorze/sql-code-graph
@@ -1,8 +1,8 @@
1
- sqlcg/__init__.py,sha256=jFojpr6ZojswTJKMfPZEdxqOfiqZPTpuB2Cb9aa1RM0,116
1
+ sqlcg/__init__.py,sha256=VahDFz44BfVysbx4bUBilcYdqMPT5hNDCMqoIkgonw0,116
2
2
  sqlcg/__main__.py,sha256=1YoFLcqEgTwYq1J3TbUwpkdG0zeeLIf2fJvwWI-CLFU,109
3
3
  sqlcg/cli/__init__.py,sha256=W8fD0LpMq2xm_5WKGNMvJh2WBL1ho5E8hUeAqXQYT1g,28
4
4
  sqlcg/cli/coverage.py,sha256=Xm9ITzZDHv2mJ70Q5jCacVuhDStVrE3gq12_-Ypvtd8,43823
5
- sqlcg/cli/main.py,sha256=tlFvl-l9hWfZBP9dO2svZXvLDbh7T5731CFyctuVqDI,2485
5
+ sqlcg/cli/main.py,sha256=K7CDIJ1tz7kxySGLeuKGZoTinucUrhINCxs_zieMmsg,2526
6
6
  sqlcg/cli/commands/__init__.py,sha256=oSHtr6VD-jNubOjuCQyZj2tBppjMEpQDh-IGQ8of9eA,30
7
7
  sqlcg/cli/commands/analyze.py,sha256=lC7QeBKljWVpKQ-x8BOF5Ok6y4jd329U1aBGrp159OA,31046
8
8
  sqlcg/cli/commands/catalog.py,sha256=xCIWp_9xcDPHIMUB0xrZR3-G0WMvKbfq4yMwe2sDT2g,12670
@@ -16,9 +16,10 @@ sqlcg/cli/commands/mcp.py,sha256=drh5udcroZFddmJAG3q9JWxekDqTO9H4xC-oIqiis-Y,148
16
16
  sqlcg/cli/commands/reindex.py,sha256=6j1QL3TjrsnWaFSIKvpGJ3LF8CoCjAbue0UFtmk9FS0,22704
17
17
  sqlcg/cli/commands/report.py,sha256=JU0qjyMxwOukE7bN3XvvIzOI7zMg_Gsnvk_8F6pKNpA,4915
18
18
  sqlcg/cli/commands/uninstall.py,sha256=WrA1FnINxnd6mmE4-_QBK0aHBnibstJeAT8swnQKG4M,8962
19
+ sqlcg/cli/commands/viz.py,sha256=2GAP6YSNuFXmWM8D8IohkaO5GJ3nj2KLdA0kHLx137M,3779
19
20
  sqlcg/cli/commands/watch.py,sha256=_OvUQ4m_Pp2lAzI_f9UX2Gwb56zbsaSC4Tj8h51uciA,2806
20
21
  sqlcg/core/__init__.py,sha256=dXvLWpbQ72f5CM6sKSvBDnEGqGuIZaN5MmHyD8Vf1aA,154
21
- sqlcg/core/config.py,sha256=QxhIiF_let_ablMa-WttGbcAlCCDIpT4lR4tUVymIEI,19091
22
+ sqlcg/core/config.py,sha256=dOQUQCsMGgT8Vp8gIfLdmMasB2IozGvHpAsjftCyDXw,20552
22
23
  sqlcg/core/duckdb_backend.py,sha256=zPsxiO2_qcE6axVZu8P3gUNHnJ1cgCCFv1XXy_t_LEw,42298
23
24
  sqlcg/core/freshness.py,sha256=Cm-w7jACPrt8TIstZpCzDJpYsHCiBo63ni5js8wCGbg,6248
24
25
  sqlcg/core/graph_db.py,sha256=3iZ1J32cEn7_cVhNA1tLiNuJHCyatN4F40HXLLfmwHY,10815
@@ -43,7 +44,7 @@ sqlcg/lineage/schema_resolver.py,sha256=iXt6LYF6UVWsGUpcfbmjmGn9wCgXl721lTGf_8Aa
43
44
  sqlcg/metrics/__init__.py,sha256=hLJ6wm4St8qqYwKh3o9QG7lcEt1BEYM31ccqO9tGpIg,133
44
45
  sqlcg/metrics/store.py,sha256=KuDtxvyAgug9_KtiSCpvgKM2VZM7VSaI3D11uMLjJJk,10604
45
46
  sqlcg/parsers/__init__.py,sha256=AamA8wBbDZV9_zEtZCI4Hyen5UAVKHmBwjTghTt2PZE,785
46
- sqlcg/parsers/ansi_parser.py,sha256=Iayv93KDOs3qKrQlrPcpQRd4_K8dEsGHOaTY076e-GY,38032
47
+ sqlcg/parsers/ansi_parser.py,sha256=RX6eVj7gt1qmsHNJLAF_a4jyW3RCI5W2oF4rd53cKNg,39336
47
48
  sqlcg/parsers/base.py,sha256=Q2X_qN7DmGFU2oA6Z9fcxyjpkeb6lOsia4BcwPvwNLo,97806
48
49
  sqlcg/parsers/bigquery_parser.py,sha256=mOnWTfXB_Dp4JwFE1PVYOB6CDPf5nYE0Dea8kJCl9uQ,2827
49
50
  sqlcg/parsers/postgres_parser.py,sha256=lYfUpQY6j4Qm7ndXBtXbgPoGzYqYddWt5YeFnWKdA6I,946
@@ -65,7 +66,13 @@ sqlcg/utils/__init__.py,sha256=--iqt5ThTXmT8Wz7da8hs3n0zDfYPl8P-z5OgRJ_77E,154
65
66
  sqlcg/utils/hashing.py,sha256=H25-sYfxHKb3_IERFnHyAIYNiXN470Oqo5sJT_D3YOA,438
66
67
  sqlcg/utils/ignore.py,sha256=6__DfiYgynI2b8nH52O2BzI1EZcZ_LRazhqEOJVGGcE,1955
67
68
  sqlcg/utils/logging.py,sha256=u0fCmYsLj9o81vawm3xZTHaw68GQYVm7JxG-gP81u8A,840
68
- sql_code_graph-1.34.4.dist-info/METADATA,sha256=tzuVGUb49jlzRUkFgV28LwMdvIR0h83TsN9zpFHFA0A,17791
69
- sql_code_graph-1.34.4.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
70
- sql_code_graph-1.34.4.dist-info/entry_points.txt,sha256=Wfe49sVzV9p4eVFGo5RxcV-frr3HOP0yzzst8JBxQLQ,46
71
- sql_code_graph-1.34.4.dist-info/RECORD,,
69
+ sqlcg/viz/__init__.py,sha256=ha-RMeqhhOoAsDQOfOjCcO0mnDKOU8MgHlK_Yxs4O8k,455
70
+ sqlcg/viz/data.py,sha256=deLWOZBgewM1x-kk1NhUnt0mswyt0nzqSevHzBCOHHc,5899
71
+ sqlcg/viz/render.py,sha256=BINkGbJbbb_iqhrkN795RaQsdg8nqCiJtsEFF1yo22Y,2737
72
+ sqlcg/viz/tags.py,sha256=6zRnGlHjuGmEeB6yN1uhzm8rqL7ZGoyL1Ki7jI5oM6A,5368
73
+ sqlcg/viz/assets/force-graph.min.js,sha256=jNdYdDdrYiUdUlElxRkolPBt30rstQk2q15Q32VVdzc,177272
74
+ sqlcg/viz/assets/template.html,sha256=9_j-mvo1ZxwgiJPDdVrNmca37dTrTjjYVd3977u-DxE,12294
75
+ sql_code_graph-1.35.1.dist-info/METADATA,sha256=YOc2OzDrdadgZdpmY3QhwueUwScKj39svzKO4dgd9Q0,17791
76
+ sql_code_graph-1.35.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
77
+ sql_code_graph-1.35.1.dist-info/entry_points.txt,sha256=Wfe49sVzV9p4eVFGo5RxcV-frr3HOP0yzzst8JBxQLQ,46
78
+ sql_code_graph-1.35.1.dist-info/RECORD,,
sqlcg/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """SQL Code Graph - SQL lineage and dependency analysis tool."""
2
2
 
3
- __version__ = "1.34.4"
3
+ __version__ = "1.35.1"
4
4
 
5
5
  __all__ = ["__version__"]
@@ -0,0 +1,102 @@
1
+ """The sqlcg viz command — generate a self-contained graph-explorer HTML.
2
+
3
+ Plan: plan/sprints/feature_graph_viz.md §"CLI signature".
4
+
5
+ Queries the live graph (via the server-aware ``run_read_routed`` seam),
6
+ attaches per-table label facets (tags/jobs) from BYO CSVs, and writes ONE
7
+ self-contained ``.html`` with the force-graph library inlined — openable on
8
+ Windows via ``file://``. Touches no parser/indexer/lineage code.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from pathlib import Path
14
+
15
+ import typer
16
+ from rich.console import Console
17
+
18
+ from sqlcg.core.config import get_viz_schemas
19
+ from sqlcg.utils.logging import getLogger
20
+ from sqlcg.viz.data import build_viz_data
21
+ from sqlcg.viz.render import DEFAULT_VIZ_OUT, render_html
22
+ from sqlcg.viz.tags import FacetMap, load_facet, resolve_labels
23
+
24
+ logger = getLogger(__name__)
25
+ console = Console()
26
+
27
+
28
+ def _facet_payload(facet: FacetMap) -> dict:
29
+ """Shape a FacetMap for baking: ``{labels, colors}``."""
30
+ return {"labels": facet.labels, "colors": facet.colors}
31
+
32
+
33
+ def viz_cmd(
34
+ tags: Path | None = typer.Option( # noqa: B008
35
+ None,
36
+ "--tags",
37
+ help=(
38
+ "BYO tags CSV (pattern,label[,color]; header required). The 'tag' "
39
+ "facet drives the legend swatches (color + filter). Patterns are "
40
+ "fnmatch globs (*, ?, [seq]) matched case-insensitively against the "
41
+ "qualified table name. Many-to-many. Omit to hide the tag legend."
42
+ ),
43
+ ),
44
+ jobs: Path | None = typer.Option( # noqa: B008
45
+ None,
46
+ "--jobs",
47
+ help=(
48
+ "BYO jobs CSV, same shape as --tags. The 'job' facet drives the job "
49
+ "dropdown (filter only). Omit to leave the dropdown empty."
50
+ ),
51
+ ),
52
+ out: Path | None = typer.Option( # noqa: B008
53
+ None,
54
+ "--out",
55
+ help=f"Output HTML path (default: {DEFAULT_VIZ_OUT} in the current directory).",
56
+ ),
57
+ config_dir: Path = typer.Option( # noqa: B008
58
+ Path("."),
59
+ "--config-dir",
60
+ help="Directory searched for .sqlcg.toml ([sqlcg.viz] schemas).",
61
+ ),
62
+ ) -> None:
63
+ """Generate a self-contained graph-explorer HTML from the live graph.
64
+
65
+ Reads the graph via the server-aware routed-read path (never a direct DB
66
+ open). The emitted file inlines the force-graph library plus the baked node/
67
+ edge/adjacency data and the tag/job facets — no external resources — so it
68
+ opens by double-click on Windows.
69
+
70
+ The client-side filter composes schema ∩ kind ∩ tag (multi-select per facet,
71
+ union within a facet, intersection across) plus a job dropdown; an edge
72
+ renders only when both endpoints are visible. Node kinds table/view/temp are
73
+ ON by default, cte/derived OFF (switchable).
74
+ """
75
+ out_path = out if out is not None else Path(DEFAULT_VIZ_OUT)
76
+
77
+ schemas = get_viz_schemas(config_dir)
78
+
79
+ data = build_viz_data()
80
+
81
+ tag_facet = load_facet(tags, "tag") if tags is not None else FacetMap(name="tag")
82
+ job_facet = load_facet(jobs, "job") if jobs is not None else FacetMap(name="job")
83
+
84
+ # Attach resolved facet labels to every node (m2m glob resolution).
85
+ for node in data["nodes"]:
86
+ node["tags"] = resolve_labels(node["id"], tag_facet)
87
+ node["jobs"] = resolve_labels(node["id"], job_facet)
88
+
89
+ html = render_html(
90
+ data,
91
+ _facet_payload(tag_facet),
92
+ _facet_payload(job_facet),
93
+ schemas,
94
+ )
95
+ out_path.write_text(html, encoding="utf-8")
96
+
97
+ schema_count = len(schemas) if schemas else len({n["schema"] for n in data["nodes"]})
98
+ console.print(
99
+ f"Wrote {out_path} — {len(data['nodes'])} nodes, "
100
+ f"{len(data['links'])} edges, {len(tag_facet.labels)} tags, "
101
+ f"{len(job_facet.labels)} jobs, {schema_count} schemas."
102
+ )
sqlcg/cli/main.py CHANGED
@@ -16,6 +16,7 @@ from sqlcg.cli.commands import (
16
16
  reindex,
17
17
  report,
18
18
  uninstall,
19
+ viz,
19
20
  watch,
20
21
  )
21
22
 
@@ -84,6 +85,7 @@ app.command("reindex")(reindex.reindex_cmd)
84
85
  app.command("watch")(watch.watch_cmd)
85
86
  app.command("gain")(gain.gain_cmd)
86
87
  app.command("report")(report.report_cmd)
88
+ app.command("viz")(viz.viz_cmd)
87
89
  app.command("install")(install.install_cmd)
88
90
  app.command("uninstall")(uninstall.uninstall_cmd)
89
91
 
sqlcg/core/config.py CHANGED
@@ -151,6 +151,44 @@ def get_noise_filter_patterns(path: Path) -> list[str]:
151
151
  return default_patterns
152
152
 
153
153
 
154
+ def get_viz_schemas(path: Path) -> list[str]:
155
+ """Get the curated viz schema list from .sqlcg.toml.
156
+
157
+ Reads [sqlcg.viz] -> schemas (a list of schema-name strings) from
158
+ .sqlcg.toml. Used by ``sqlcg viz`` to decide which schemas get a distinct
159
+ hue / appear as multi-select facets. When the block/key is absent or not a
160
+ list, returns ``[]`` and the generator falls back to **data-derived**
161
+ schemas (every distinct schema present in the graph), preserving the
162
+ zero-config small-repo path::
163
+
164
+ [sqlcg.viz]
165
+ schemas = ["ba", "da", "ia_analytics"]
166
+
167
+ This reader mirrors ``get_schema_aliases`` / ``get_noise_filter_patterns``
168
+ exactly (same .sqlcg.toml open, same ``except Exception: pass`` guard, same
169
+ fallback shape). It is presentation-only config; it never touches indexing
170
+ or lineage.
171
+
172
+ Args:
173
+ path: Root directory to search for .sqlcg.toml
174
+
175
+ Returns:
176
+ List of configured schema names. ``[]`` when the key is absent or
177
+ malformed (caller derives schemas from the data).
178
+ """
179
+ config_file = Path(path) / ".sqlcg.toml"
180
+ if config_file.exists():
181
+ try:
182
+ with open(config_file, "rb") as f:
183
+ config = tomllib.load(f)
184
+ raw = config.get("sqlcg", {}).get("viz", {}).get("schemas")
185
+ if isinstance(raw, list):
186
+ return [s for s in raw if isinstance(s, str)]
187
+ except Exception:
188
+ pass
189
+ return []
190
+
191
+
154
192
  def get_ignored_tables(path: Path) -> list[str]:
155
193
  """Get explicitly-ignored qualified table names from .sqlcg.toml.
156
194
 
@@ -551,25 +551,44 @@ class AnsiParser(SqlParser):
551
551
  # measurement artifact (96.1%/4,544 of 4,727 on the DWH corpus).
552
552
  can_have_lineage = self._can_have_column_lineage(stmt)
553
553
 
554
+ # Phantom source-node gate (#161): a non-table/non-view CREATE
555
+ # (SEQUENCE / STAGE / FILE FORMAT / SCHEMA / INDEX / PROCEDURE / ...) has
556
+ # target=None and no data-flow source. Its only Table node is its own
557
+ # object name, which _fallback_table_scan below would otherwise scoop into
558
+ # `sources`, leaking a phantom SqlTable SOURCE node + SELECTS_FROM edge
559
+ # into the graph. #159 fixed the kind LABEL (-> OTHER); this closes the
560
+ # source-node half. Keying off stmt.kind (the sqlglot AST value, e.g.
561
+ # 'SEQUENCE') keeps the gate independent of the _classify/QueryKind path.
562
+ # CREATE TABLE/VIEW ... AS SELECT keep kind in (TABLE, VIEW) and so retain
563
+ # their real sources. Real procedure lineage comes from the spliced inner
564
+ # DML (a separate node the gate never touches), so gating PROCEDURE is
565
+ # edge-neutral. This single gate feeds all three
566
+ # `referenced_tables.extend(query_node.sources)` sites
567
+ # (ansi_parser.py:246, snowflake_parser.py:784 / :944).
568
+ is_non_table_create = isinstance(stmt, exp.Create) and stmt.kind not in ("TABLE", "VIEW")
569
+
554
570
  # Try to extract table references using scope analysis
555
571
  try:
556
- # Use pre-built scope if provided, otherwise build it here (fallback)
557
- root_scope = scope
558
- if root_scope is None:
559
- from sqlglot.optimizer.scope import build_scope
560
-
561
- root_scope = build_scope(stmt)
562
-
563
- if root_scope:
564
- sources = self._real_tables(root_scope)
572
+ if is_non_table_create:
573
+ sources = []
565
574
  else:
566
- # Fallback to basic table extraction
567
- sources = [
568
- r
569
- for s in self._fallback_table_scan(stmt)
570
- if (r := self._apply_table_alias(s)) is not None
571
- ]
572
- parse_failed = can_have_lineage
575
+ # Use pre-built scope if provided, otherwise build it here (fallback)
576
+ root_scope = scope
577
+ if root_scope is None:
578
+ from sqlglot.optimizer.scope import build_scope
579
+
580
+ root_scope = build_scope(stmt)
581
+
582
+ if root_scope:
583
+ sources = self._real_tables(root_scope)
584
+ else:
585
+ # Fallback to basic table extraction
586
+ sources = [
587
+ r
588
+ for s in self._fallback_table_scan(stmt)
589
+ if (r := self._apply_table_alias(s)) is not None
590
+ ]
591
+ parse_failed = can_have_lineage
573
592
  except Exception as exc:
574
593
  logger.debug("Failed to build scope for statement %d in %s: %s", stmt_index, path, exc)
575
594
  sources = [
sqlcg/viz/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ """Self-contained graph-visualization generator for ``sqlcg viz``.
2
+
3
+ Plan: plan/sprints/feature_graph_viz.md
4
+
5
+ A pure read + render feature: queries the live DuckDB graph (via the
6
+ server-aware ``run_read_routed`` seam), resolves query-anchored edges to
7
+ table->table links, attaches per-table label facets (tags/jobs) from a BYO
8
+ CSV, and emits ONE self-contained HTML file with the force-graph library
9
+ inlined. No parser/indexer/lineage code is touched.
10
+ """