sql-code-graph 1.34.3__py3-none-any.whl → 1.35.0__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.
- {sql_code_graph-1.34.3.dist-info → sql_code_graph-1.35.0.dist-info}/METADATA +1 -1
- {sql_code_graph-1.34.3.dist-info → sql_code_graph-1.35.0.dist-info}/RECORD +17 -10
- sqlcg/__init__.py +1 -1
- sqlcg/cli/commands/analyze.py +25 -3
- sqlcg/cli/commands/viz.py +102 -0
- sqlcg/cli/main.py +2 -0
- sqlcg/core/config.py +38 -0
- sqlcg/core/queries.py +1 -0
- sqlcg/core/queries.sql +23 -0
- sqlcg/viz/__init__.py +10 -0
- sqlcg/viz/assets/force-graph.min.js +5 -0
- sqlcg/viz/assets/template.html +274 -0
- sqlcg/viz/data.py +141 -0
- sqlcg/viz/render.py +79 -0
- sqlcg/viz/tags.py +149 -0
- {sql_code_graph-1.34.3.dist-info → sql_code_graph-1.35.0.dist-info}/WHEEL +0 -0
- {sql_code_graph-1.34.3.dist-info → sql_code_graph-1.35.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sql-code-graph
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.35.0
|
|
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,10 +1,10 @@
|
|
|
1
|
-
sqlcg/__init__.py,sha256=
|
|
1
|
+
sqlcg/__init__.py,sha256=2bXiRwcsTAFodsSZ65K7ditQHczrjRuDewuv8ncWvio,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=
|
|
5
|
+
sqlcg/cli/main.py,sha256=K7CDIJ1tz7kxySGLeuKGZoTinucUrhINCxs_zieMmsg,2526
|
|
6
6
|
sqlcg/cli/commands/__init__.py,sha256=oSHtr6VD-jNubOjuCQyZj2tBppjMEpQDh-IGQ8of9eA,30
|
|
7
|
-
sqlcg/cli/commands/analyze.py,sha256=
|
|
7
|
+
sqlcg/cli/commands/analyze.py,sha256=lC7QeBKljWVpKQ-x8BOF5Ok6y4jd329U1aBGrp159OA,31046
|
|
8
8
|
sqlcg/cli/commands/catalog.py,sha256=xCIWp_9xcDPHIMUB0xrZR3-G0WMvKbfq4yMwe2sDT2g,12670
|
|
9
9
|
sqlcg/cli/commands/db.py,sha256=QEDPs0boeFe8ETE8BWe7I2JLSXROUI571SHXMTUqxh8,7982
|
|
10
10
|
sqlcg/cli/commands/find.py,sha256=uMdG08bU-AvB9RqsTKPfdVnJet7b3Ebj0xbsjqTclYA,2678
|
|
@@ -16,17 +16,18 @@ 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=
|
|
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
|
|
25
26
|
sqlcg/core/jobs.py,sha256=Je-fCdSKRgiSsv1W8SgNAlp36a7t7-pJZ-qKPbka9OE,3298
|
|
26
27
|
sqlcg/core/noise_match.py,sha256=8jxMBum0HB7wnVyS8hHaklO279QOSNKDmPtv-0RujD8,4781
|
|
27
28
|
sqlcg/core/queries.cypher,sha256=cvPOVe5GUOzJN4bxUvDxNI--xIIP8gm42TR-gUnea4U,4685
|
|
28
|
-
sqlcg/core/queries.py,sha256=
|
|
29
|
-
sqlcg/core/queries.sql,sha256
|
|
29
|
+
sqlcg/core/queries.py,sha256=QwQsjI2YJPipfw69k9iSu1jpeiOIPPtkegO4TVmAVaY,3703
|
|
30
|
+
sqlcg/core/queries.sql,sha256=-ZoMnNt-i2kojMHLAUHvQhCZURIgI9zkfh8J8QJo1iE,16863
|
|
30
31
|
sqlcg/core/schema.cypher,sha256=StaJqIzOLwF2X9HYvfHSdGiYGFb_YkiHubPrTu-Qpmg,2898
|
|
31
32
|
sqlcg/core/schema.py,sha256=pVGzf__czAddmz-fsXgXqq2DTxN9EKom5RvIHLctIPU,1519
|
|
32
33
|
sqlcg/indexer/__init__.py,sha256=Wh20Unz2OHs1oIyWLrpurPAasF0BET2g4iXtNk7mh2U,56
|
|
@@ -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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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.0.dist-info/METADATA,sha256=U3Aw3b4ufs83tQ2ApR2IH3VKLT5otMEWtFFrs5AXc9A,17791
|
|
76
|
+
sql_code_graph-1.35.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
77
|
+
sql_code_graph-1.35.0.dist-info/entry_points.txt,sha256=Wfe49sVzV9p4eVFGo5RxcV-frr3HOP0yzzst8JBxQLQ,46
|
|
78
|
+
sql_code_graph-1.35.0.dist-info/RECORD,,
|
sqlcg/__init__.py
CHANGED
sqlcg/cli/commands/analyze.py
CHANGED
|
@@ -8,7 +8,10 @@ import typer
|
|
|
8
8
|
from rich.console import Console
|
|
9
9
|
from rich.table import Table
|
|
10
10
|
|
|
11
|
-
from sqlcg.core.queries import
|
|
11
|
+
from sqlcg.core.queries import (
|
|
12
|
+
GET_TABLE_EXTERNAL_CONSUMERS_QUERY,
|
|
13
|
+
IMPACT_CONSUMERS_VIA_LINEAGE_QUERY,
|
|
14
|
+
)
|
|
12
15
|
from sqlcg.server.read_client import resolved_repo_root, run_read_routed
|
|
13
16
|
|
|
14
17
|
if TYPE_CHECKING:
|
|
@@ -312,8 +315,16 @@ def impact( # noqa: B008
|
|
|
312
315
|
table: str = typer.Argument(..., help="Table name to analyze"), # noqa: B008
|
|
313
316
|
raw: bool = typer.Option(False, "--raw", help="Disable noise filtering on results"), # noqa: B008
|
|
314
317
|
) -> None:
|
|
315
|
-
"""Show all queries impacted by a table.
|
|
316
|
-
|
|
318
|
+
"""Show all queries impacted by a table.
|
|
319
|
+
|
|
320
|
+
One-hop "who reads this table" — the union of direct SELECTS_FROM consumers
|
|
321
|
+
and consumers that read the table only via COLUMN_LINEAGE / STAR_SOURCE
|
|
322
|
+
(CTE-wrapped / promoted-intermediate reads, e.g. the live DWH node
|
|
323
|
+
``ba.all_rows_in_selection``). Bug #6 fix: brings ``impact`` to the same
|
|
324
|
+
one-hop consumer completeness ``find_table_usages`` already has. Stays
|
|
325
|
+
one-hop (NOT transitive — that is ``downstream``'s job).
|
|
326
|
+
"""
|
|
327
|
+
direct = run_read_routed(
|
|
317
328
|
"SELECT DISTINCT q.id AS id, q.kind AS kind, q.target_table AS target"
|
|
318
329
|
' FROM "SqlTable" t'
|
|
319
330
|
' JOIN "SELECTS_FROM" sf ON sf.dst_key = t.qualified'
|
|
@@ -321,6 +332,17 @@ def impact( # noqa: B008
|
|
|
321
332
|
" WHERE t.qualified = ? LIMIT 100",
|
|
322
333
|
{"t": table},
|
|
323
334
|
)
|
|
335
|
+
# Consumers reachable only via COLUMN_LINEAGE / STAR_SOURCE. Two "?" both
|
|
336
|
+
# bound to the qualified table name: _params_list returns list(values()) in
|
|
337
|
+
# insertion order, so the ordered dict {"t":..., "t2":...} binds correctly.
|
|
338
|
+
via = run_read_routed(
|
|
339
|
+
IMPACT_CONSUMERS_VIA_LINEAGE_QUERY,
|
|
340
|
+
{"t": table, "t2": table},
|
|
341
|
+
)
|
|
342
|
+
seen = {r["id"] for r in direct}
|
|
343
|
+
results = direct + [r for r in via if r["id"] not in seen]
|
|
344
|
+
# Preserve the existing 100-row bound on the merged consumer set.
|
|
345
|
+
results = results[:100]
|
|
324
346
|
if not raw:
|
|
325
347
|
from sqlcg.server.noise_filter import NoiseFilter
|
|
326
348
|
|
|
@@ -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
|
|
sqlcg/core/queries.py
CHANGED
|
@@ -72,3 +72,4 @@ GET_TABLES_EXTERNAL_CONSUMERS_BATCH_QUERY = _Q["GET_TABLES_EXTERNAL_CONSUMERS_BA
|
|
|
72
72
|
COUNT_EXTERNAL_CONSUMERS_QUERY = _Q["COUNT_EXTERNAL_CONSUMERS"]
|
|
73
73
|
GET_TABLE_READS_ADJACENCY_QUERY = _Q["GET_TABLE_READS_ADJACENCY"]
|
|
74
74
|
GET_PRODUCER_TABLES_QUERY = _Q["GET_PRODUCER_TABLES"]
|
|
75
|
+
IMPACT_CONSUMERS_VIA_LINEAGE_QUERY = _Q["IMPACT_CONSUMERS_VIA_LINEAGE"]
|
sqlcg/core/queries.sql
CHANGED
|
@@ -413,3 +413,26 @@ WHERE t.name = ?
|
|
|
413
413
|
SELECT DISTINCT target_table AS producer_table
|
|
414
414
|
FROM "SqlQuery"
|
|
415
415
|
WHERE target_table <> '' AND target_table IS NOT NULL
|
|
416
|
+
|
|
417
|
+
-- IMPACT_CONSUMERS_VIA_LINEAGE
|
|
418
|
+
-- Bug #6: consumers that read a table only via COLUMN_LINEAGE or STAR_SOURCE
|
|
419
|
+
-- (CTE-wrapped / promoted-intermediate reads that emit no top-level SELECTS_FROM
|
|
420
|
+
-- edge, e.g. the live DWH cross-file-promoted node ba.all_rows_in_selection:
|
|
421
|
+
-- kind='table', non-namespaced, 0 SELECTS_FROM in-edges, 55 COLUMN_LINEAGE
|
|
422
|
+
-- out-edges). Returns the same (id, kind, target) shape as the SELECTS_FROM-only
|
|
423
|
+
-- impact query in analyze.py:impact() so the two result sets merge + dedup on q.id.
|
|
424
|
+
-- Both branches key on the QUALIFIED table name (impact() keys on t.qualified):
|
|
425
|
+
-- COLUMN_LINEAGE branch -> src.table_qualified; STAR_SOURCE branch -> ss.dst_key.
|
|
426
|
+
-- The STAR_SOURCE branch closes the STAR-only-consumer gap (FIND_TABLE_USAGES
|
|
427
|
+
-- itself still lacks it — out of scope, separate follow-up).
|
|
428
|
+
-- params: [table_qualified, table_qualified] (same value bound to BOTH ?)
|
|
429
|
+
SELECT DISTINCT q.id AS id, q.kind AS kind, q.target_table AS target
|
|
430
|
+
FROM "SqlColumn" src
|
|
431
|
+
JOIN "COLUMN_LINEAGE" cl ON cl.src_key = src.id
|
|
432
|
+
JOIN "SqlQuery" q ON q.id = cl.query_id
|
|
433
|
+
WHERE src.table_qualified = ?
|
|
434
|
+
UNION
|
|
435
|
+
SELECT DISTINCT q.id AS id, q.kind AS kind, q.target_table AS target
|
|
436
|
+
FROM "STAR_SOURCE" ss
|
|
437
|
+
JOIN "SqlQuery" q ON q.id = ss.src_key
|
|
438
|
+
WHERE ss.dst_key = ?
|
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
|
+
"""
|