sql-glider 0.1.9__py3-none-any.whl → 0.1.11__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_glider-0.1.9.dist-info → sql_glider-0.1.11.dist-info}/METADATA +1 -1
- {sql_glider-0.1.9.dist-info → sql_glider-0.1.11.dist-info}/RECORD +10 -10
- sqlglider/_version.py +2 -2
- sqlglider/cli.py +14 -1
- sqlglider/graph/builder.py +6 -1
- sqlglider/lineage/analyzer.py +68 -2
- sqlglider/utils/config.py +1 -0
- {sql_glider-0.1.9.dist-info → sql_glider-0.1.11.dist-info}/WHEEL +0 -0
- {sql_glider-0.1.9.dist-info → sql_glider-0.1.11.dist-info}/entry_points.txt +0 -0
- {sql_glider-0.1.9.dist-info → sql_glider-0.1.11.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sql-glider
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.11
|
|
4
4
|
Summary: SQL Utility Toolkit for better understanding, use, and governance of your queries in a native environment.
|
|
5
5
|
Project-URL: Homepage, https://github.com/rycowhi/sql-glider/
|
|
6
6
|
Project-URL: Repository, https://github.com/rycowhi/sql-glider/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
sqlglider/__init__.py,sha256=gDf7s52dMcX7JuCZ1SLawcB1vb3U0yJCohu9RQAATBY,125
|
|
2
|
-
sqlglider/_version.py,sha256=
|
|
3
|
-
sqlglider/cli.py,sha256=
|
|
2
|
+
sqlglider/_version.py,sha256=0-Ruc52ECccw_8Ef0d7jMkzrb8fkobUkZLqGGvcm1ik,706
|
|
3
|
+
sqlglider/cli.py,sha256=DMCMw5dxDHB2MuxBXuJMNeDSlIGAfKDz1Renp0YwGGM,52224
|
|
4
4
|
sqlglider/global_models.py,sha256=2vyJXAuXOsXQpE-D3F0ejj7eR9z0nDWFjTkielhzM8k,356
|
|
5
5
|
sqlglider/catalog/__init__.py,sha256=2PqFPyzFXJ14FpSUcBmVK2L-a_ypWQHAbHFHxLDk_LE,814
|
|
6
6
|
sqlglider/catalog/base.py,sha256=R7htHC43InpH4uRjYk33dMYYji6oylHns7Ye_mgfjJE,3116
|
|
@@ -11,13 +11,13 @@ sqlglider/dissection/analyzer.py,sha256=-GD3-lTbfBthq1BW6HiDjvJx2y4LDmnUVHIVIb0H
|
|
|
11
11
|
sqlglider/dissection/formatters.py,sha256=M7gsmTNljRIeLIRv4D0vHvqJVrTqWSpsg7vem83zSzY,7302
|
|
12
12
|
sqlglider/dissection/models.py,sha256=RRD3RIteqbUBY6e-74skKDvMH3qeAUaqA2sFcrjP5GQ,3618
|
|
13
13
|
sqlglider/graph/__init__.py,sha256=4DDdrPM75CmeQWt7wHdBsjCm1s70BHGLYdijIbaUEKY,871
|
|
14
|
-
sqlglider/graph/builder.py,sha256=
|
|
14
|
+
sqlglider/graph/builder.py,sha256=HdkMcuZkxdEFO0CXMAaqGQSyhvzuaIQTaFscQdO2GSI,12146
|
|
15
15
|
sqlglider/graph/merge.py,sha256=uUZlm4BN3S9gRL66Cc2mzhbtuh4SVAv2n4cN4eUEQBU,4077
|
|
16
16
|
sqlglider/graph/models.py,sha256=EYmjv_WzDSNp_WfhJ6H-qBIOkAcoNKS7GRUryfKrHuY,9330
|
|
17
17
|
sqlglider/graph/query.py,sha256=LHU8Cvn7ZPPSEnqdDn2pF8f1_LQjIvNIrZqs8cFlb6U,9433
|
|
18
18
|
sqlglider/graph/serialization.py,sha256=vMXn7s35jA499e7l90vNVaJE_3QR_VHf3rEfQ9ZlgTQ,2781
|
|
19
19
|
sqlglider/lineage/__init__.py,sha256=llXMeI5_PIZaiBo8tKk3-wOubF4m_6QBHbn1FtWxT7k,256
|
|
20
|
-
sqlglider/lineage/analyzer.py,sha256=
|
|
20
|
+
sqlglider/lineage/analyzer.py,sha256=gjJtJU-sxFokoSVxcHpcIdbP3H8GD_KQaubbbcG0UCM,68982
|
|
21
21
|
sqlglider/lineage/formatters.py,sha256=_Y9wcTX4JXn1vVnZ1xI656g1FF2rMjcAVc-GHjbd9QA,10389
|
|
22
22
|
sqlglider/templating/__init__.py,sha256=g3_wb6rSDI0usq2UUMDpn-J5kVwlAw3NtLdwbxL6UHs,1435
|
|
23
23
|
sqlglider/templating/base.py,sha256=y5bWAW7qXl_4pPyo5KycfHwNVvt1-7slZ63DAsvTE1s,2902
|
|
@@ -25,10 +25,10 @@ sqlglider/templating/jinja.py,sha256=o01UG72N4G1-tOT5LKK1Wkccv4nJH2VN4VFaMi5c1-g
|
|
|
25
25
|
sqlglider/templating/registry.py,sha256=BJU3N2qNVMTUtkgbibyqo8Wme_acXQRw5XI-6ZVgyac,3476
|
|
26
26
|
sqlglider/templating/variables.py,sha256=5593PtLBcOxsnMCSRm2pGAD5I0Y9f__VV3_J_HfXVlQ,8010
|
|
27
27
|
sqlglider/utils/__init__.py,sha256=KGp9-UzKz_OFBOTFoSy-g-NXDZsvyWXG_9-1zcC6ePE,276
|
|
28
|
-
sqlglider/utils/config.py,sha256=
|
|
28
|
+
sqlglider/utils/config.py,sha256=rbbiDCWA_h29vgWJZ1z3zQmGcei0KcxhTPcymSCYeFo,4796
|
|
29
29
|
sqlglider/utils/file_utils.py,sha256=5_ff28E0r1R7emZzsOnRuHd-7zIX6873eyr1SuPEr4E,1093
|
|
30
|
-
sql_glider-0.1.
|
|
31
|
-
sql_glider-0.1.
|
|
32
|
-
sql_glider-0.1.
|
|
33
|
-
sql_glider-0.1.
|
|
34
|
-
sql_glider-0.1.
|
|
30
|
+
sql_glider-0.1.11.dist-info/METADATA,sha256=JxQakiYUUzvldsEzjdXQLV63ud07Gw_bcZ2BIi29nuQ,28446
|
|
31
|
+
sql_glider-0.1.11.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
32
|
+
sql_glider-0.1.11.dist-info/entry_points.txt,sha256=HDuakHqHS5C0HFKsMIxMYmDU7-BLBGrnIJcYaVRu-s0,251
|
|
33
|
+
sql_glider-0.1.11.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
34
|
+
sql_glider-0.1.11.dist-info/RECORD,,
|
sqlglider/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.1.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
31
|
+
__version__ = version = '0.1.11'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 11)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
sqlglider/cli.py
CHANGED
|
@@ -166,6 +166,11 @@ def lineage(
|
|
|
166
166
|
exists=True,
|
|
167
167
|
help="Path to variables file (JSON or YAML)",
|
|
168
168
|
),
|
|
169
|
+
no_star: bool = typer.Option(
|
|
170
|
+
False,
|
|
171
|
+
"--no-star",
|
|
172
|
+
help="Fail if SELECT * cannot be resolved to actual columns",
|
|
173
|
+
),
|
|
169
174
|
) -> None:
|
|
170
175
|
"""
|
|
171
176
|
Analyze column or table lineage for a SQL file.
|
|
@@ -207,6 +212,7 @@ def lineage(
|
|
|
207
212
|
level_str = level or config.level or "column"
|
|
208
213
|
output_format = output_format or config.output_format or "text"
|
|
209
214
|
templater = templater or config.templater # None means no templating
|
|
215
|
+
no_star = no_star or config.no_star or False
|
|
210
216
|
# Validate and convert level to enum
|
|
211
217
|
try:
|
|
212
218
|
analysis_level = AnalysisLevel(level_str)
|
|
@@ -261,7 +267,7 @@ def lineage(
|
|
|
261
267
|
)
|
|
262
268
|
|
|
263
269
|
# Create analyzer
|
|
264
|
-
analyzer = LineageAnalyzer(sql, dialect=dialect)
|
|
270
|
+
analyzer = LineageAnalyzer(sql, dialect=dialect, no_star=no_star)
|
|
265
271
|
|
|
266
272
|
# Unified lineage analysis (handles both single and multi-query files)
|
|
267
273
|
results = analyzer.analyze_queries(
|
|
@@ -990,6 +996,11 @@ def graph_build(
|
|
|
990
996
|
exists=True,
|
|
991
997
|
help="Path to variables file (JSON or YAML)",
|
|
992
998
|
),
|
|
999
|
+
no_star: bool = typer.Option(
|
|
1000
|
+
False,
|
|
1001
|
+
"--no-star",
|
|
1002
|
+
help="Fail if SELECT * cannot be resolved to actual columns",
|
|
1003
|
+
),
|
|
993
1004
|
) -> None:
|
|
994
1005
|
"""
|
|
995
1006
|
Build a lineage graph from SQL files.
|
|
@@ -1024,6 +1035,7 @@ def graph_build(
|
|
|
1024
1035
|
config = load_config()
|
|
1025
1036
|
dialect = dialect or config.dialect or "spark"
|
|
1026
1037
|
templater = templater or config.templater # None means no templating
|
|
1038
|
+
no_star = no_star or config.no_star or False
|
|
1027
1039
|
|
|
1028
1040
|
# Validate and convert node format to enum
|
|
1029
1041
|
try:
|
|
@@ -1080,6 +1092,7 @@ def graph_build(
|
|
|
1080
1092
|
node_format=node_format_enum,
|
|
1081
1093
|
dialect=dialect,
|
|
1082
1094
|
sql_preprocessor=sql_preprocessor,
|
|
1095
|
+
no_star=no_star,
|
|
1083
1096
|
)
|
|
1084
1097
|
|
|
1085
1098
|
# Process manifest if provided
|
sqlglider/graph/builder.py
CHANGED
|
@@ -33,6 +33,7 @@ class GraphBuilder:
|
|
|
33
33
|
node_format: NodeFormat = NodeFormat.QUALIFIED,
|
|
34
34
|
dialect: str = "spark",
|
|
35
35
|
sql_preprocessor: Optional[SqlPreprocessor] = None,
|
|
36
|
+
no_star: bool = False,
|
|
36
37
|
):
|
|
37
38
|
"""
|
|
38
39
|
Initialize the graph builder.
|
|
@@ -43,10 +44,12 @@ class GraphBuilder:
|
|
|
43
44
|
sql_preprocessor: Optional function to preprocess SQL before analysis.
|
|
44
45
|
Takes (sql: str, file_path: Path) and returns processed SQL.
|
|
45
46
|
Useful for templating (e.g., Jinja2 rendering).
|
|
47
|
+
no_star: If True, fail when SELECT * cannot be resolved to columns
|
|
46
48
|
"""
|
|
47
49
|
self.node_format = node_format
|
|
48
50
|
self.dialect = dialect
|
|
49
51
|
self.sql_preprocessor = sql_preprocessor
|
|
52
|
+
self.no_star = no_star
|
|
50
53
|
self.graph: rx.PyDiGraph = rx.PyDiGraph()
|
|
51
54
|
self._node_index_map: Dict[str, int] = {} # identifier -> rustworkx node index
|
|
52
55
|
self._source_files: Set[str] = set()
|
|
@@ -82,7 +85,9 @@ class GraphBuilder:
|
|
|
82
85
|
if self.sql_preprocessor:
|
|
83
86
|
sql_content = self.sql_preprocessor(sql_content, file_path)
|
|
84
87
|
|
|
85
|
-
analyzer = LineageAnalyzer(
|
|
88
|
+
analyzer = LineageAnalyzer(
|
|
89
|
+
sql_content, dialect=file_dialect, no_star=self.no_star
|
|
90
|
+
)
|
|
86
91
|
results = analyzer.analyze_queries(level=AnalysisLevel.COLUMN)
|
|
87
92
|
|
|
88
93
|
# Print warnings for any skipped queries within the file
|
sqlglider/lineage/analyzer.py
CHANGED
|
@@ -11,6 +11,10 @@ from sqlglot.lineage import Node, lineage
|
|
|
11
11
|
from sqlglider.global_models import AnalysisLevel
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
class StarResolutionError(Exception):
|
|
15
|
+
"""Raised when SELECT * cannot be resolved and no_star mode is enabled."""
|
|
16
|
+
|
|
17
|
+
|
|
14
18
|
class TableUsage(str, Enum):
|
|
15
19
|
"""How a table is used in a query."""
|
|
16
20
|
|
|
@@ -85,19 +89,21 @@ WarningCallback = Callable[[str], None]
|
|
|
85
89
|
class LineageAnalyzer:
|
|
86
90
|
"""Analyze column and table lineage for SQL queries."""
|
|
87
91
|
|
|
88
|
-
def __init__(self, sql: str, dialect: str = "spark"):
|
|
92
|
+
def __init__(self, sql: str, dialect: str = "spark", no_star: bool = False):
|
|
89
93
|
"""
|
|
90
94
|
Initialize the lineage analyzer.
|
|
91
95
|
|
|
92
96
|
Args:
|
|
93
97
|
sql: SQL query string to analyze (can contain multiple statements)
|
|
94
98
|
dialect: SQL dialect (default: spark)
|
|
99
|
+
no_star: If True, fail when SELECT * cannot be resolved to columns
|
|
95
100
|
|
|
96
101
|
Raises:
|
|
97
102
|
ParseError: If the SQL cannot be parsed
|
|
98
103
|
"""
|
|
99
104
|
self.sql = sql
|
|
100
105
|
self.dialect = dialect
|
|
106
|
+
self._no_star = no_star
|
|
101
107
|
self._skipped_queries: List[SkippedQuery] = []
|
|
102
108
|
# File-scoped schema context for cross-statement lineage
|
|
103
109
|
# Maps table/view names to their column definitions
|
|
@@ -171,6 +177,12 @@ class LineageAnalyzer:
|
|
|
171
177
|
columns.append(qualified_name)
|
|
172
178
|
self._column_mapping[qualified_name] = star_col
|
|
173
179
|
if not columns:
|
|
180
|
+
if self._no_star:
|
|
181
|
+
raise StarResolutionError(
|
|
182
|
+
f"SELECT * could not be resolved to columns "
|
|
183
|
+
f"for target table '{target_table}'. "
|
|
184
|
+
f"Provide schema context or avoid using SELECT *."
|
|
185
|
+
)
|
|
174
186
|
# Fallback: can't resolve *, use * as column name
|
|
175
187
|
qualified_name = f"{target_table}.*"
|
|
176
188
|
columns.append(qualified_name)
|
|
@@ -200,6 +212,12 @@ class LineageAnalyzer:
|
|
|
200
212
|
columns.append(qualified_name)
|
|
201
213
|
self._column_mapping[qualified_name] = col
|
|
202
214
|
if not qualified_star_cols:
|
|
215
|
+
if self._no_star:
|
|
216
|
+
raise StarResolutionError(
|
|
217
|
+
f"SELECT {source_table}.* could not be resolved "
|
|
218
|
+
f"to columns for target table '{target_table}'. "
|
|
219
|
+
f"Provide schema context or avoid using SELECT *."
|
|
220
|
+
)
|
|
203
221
|
# Fallback: can't resolve t.*, use * as column name
|
|
204
222
|
qualified_name = f"{target_table}.*"
|
|
205
223
|
columns.append(qualified_name)
|
|
@@ -226,6 +244,23 @@ class LineageAnalyzer:
|
|
|
226
244
|
# Get the first SELECT for table resolution (handles UNION case)
|
|
227
245
|
first_select = self._get_first_select(select_node)
|
|
228
246
|
for projection in projections:
|
|
247
|
+
# Handle SELECT * in DQL context
|
|
248
|
+
if isinstance(projection, exp.Star):
|
|
249
|
+
if first_select:
|
|
250
|
+
star_columns = self._resolve_star_columns(first_select)
|
|
251
|
+
for star_col in star_columns:
|
|
252
|
+
columns.append(star_col)
|
|
253
|
+
self._column_mapping[star_col] = star_col
|
|
254
|
+
if not columns:
|
|
255
|
+
if self._no_star:
|
|
256
|
+
raise StarResolutionError(
|
|
257
|
+
"SELECT * could not be resolved to columns. "
|
|
258
|
+
"Provide schema context or avoid using SELECT *."
|
|
259
|
+
)
|
|
260
|
+
columns.append("*")
|
|
261
|
+
self._column_mapping["*"] = "*"
|
|
262
|
+
continue
|
|
263
|
+
|
|
229
264
|
# Get the underlying expression (unwrap alias if present)
|
|
230
265
|
if isinstance(projection, exp.Alias):
|
|
231
266
|
source_expr = projection.this
|
|
@@ -236,6 +271,30 @@ class LineageAnalyzer:
|
|
|
236
271
|
column_name = None
|
|
237
272
|
lineage_name = None
|
|
238
273
|
|
|
274
|
+
# Handle table-qualified star in DQL context (e.g., t.*)
|
|
275
|
+
if isinstance(source_expr, exp.Column) and isinstance(
|
|
276
|
+
source_expr.this, exp.Star
|
|
277
|
+
):
|
|
278
|
+
source_table = source_expr.table
|
|
279
|
+
dql_star_cols: List[str] = []
|
|
280
|
+
if source_table and first_select:
|
|
281
|
+
dql_star_cols = self._resolve_qualified_star(
|
|
282
|
+
source_table, first_select
|
|
283
|
+
)
|
|
284
|
+
for col in dql_star_cols:
|
|
285
|
+
columns.append(col)
|
|
286
|
+
self._column_mapping[col] = col
|
|
287
|
+
if not dql_star_cols:
|
|
288
|
+
if self._no_star:
|
|
289
|
+
raise StarResolutionError(
|
|
290
|
+
f"SELECT {source_table}.* could not be resolved "
|
|
291
|
+
f"to columns. "
|
|
292
|
+
f"Provide schema context or avoid using SELECT *."
|
|
293
|
+
)
|
|
294
|
+
columns.append("*")
|
|
295
|
+
self._column_mapping["*"] = "*"
|
|
296
|
+
continue
|
|
297
|
+
|
|
239
298
|
# Try to extract fully qualified name
|
|
240
299
|
if isinstance(source_expr, exp.Column):
|
|
241
300
|
# Get table and column parts
|
|
@@ -407,6 +466,8 @@ class LineageAnalyzer:
|
|
|
407
466
|
level=level,
|
|
408
467
|
)
|
|
409
468
|
)
|
|
469
|
+
except StarResolutionError:
|
|
470
|
+
raise
|
|
410
471
|
except ValueError as e:
|
|
411
472
|
# Unsupported statement type - track it and continue
|
|
412
473
|
stmt_type = self._get_statement_type(expr)
|
|
@@ -751,7 +812,12 @@ class LineageAnalyzer:
|
|
|
751
812
|
|
|
752
813
|
lineage_items = []
|
|
753
814
|
# Get SQL for current expression only (not full multi-query SQL)
|
|
754
|
-
|
|
815
|
+
# For CACHE TABLE, pass just the SELECT since sqlglot.lineage doesn't
|
|
816
|
+
# natively understand CACHE statements
|
|
817
|
+
if isinstance(self.expr, exp.Cache) and self.expr.expression:
|
|
818
|
+
current_query_sql = self.expr.expression.sql(dialect=self.dialect)
|
|
819
|
+
else:
|
|
820
|
+
current_query_sql = self.expr.sql(dialect=self.dialect)
|
|
755
821
|
|
|
756
822
|
for col in columns_to_analyze:
|
|
757
823
|
try:
|
sqlglider/utils/config.py
CHANGED
|
@@ -60,6 +60,7 @@ class ConfigSettings(BaseModel):
|
|
|
60
60
|
catalog_type: Optional[str] = None
|
|
61
61
|
ddl_folder: Optional[str] = None
|
|
62
62
|
catalog: Optional[CatalogConfig] = None
|
|
63
|
+
no_star: Optional[bool] = None
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
def find_config_file(start_path: Optional[Path] = None) -> Optional[Path]:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|