sql-glider 0.1.10__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sql-glider
3
- Version: 0.1.10
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=uf7mpKSLRNNF3RxSXHssYzKadEgmCS7IlRw4lFPAcUg,706
3
- sqlglider/cli.py,sha256=9sweHRVLk2iBSzCzT2Gcj8y1g1XKzq26iApQsMaFbx4,51786
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=0ASA749b_FkcKMEVWiijAJP1QKt54ICY7VXxUuo3-y0,11953
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=dHjzESzJRf4RNF-PdtZK7YdJuWt1UJXZBJFzgnaohsQ,65873
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=iNJgSXFw3pmL2MCdvW3SJp4X2T3AQP2QyQuXIXT-6H0,4761
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.10.dist-info/METADATA,sha256=ZUlH3RU_iwgXDJgrrqESBN-uCiPYXjxgojgET2DhHbc,28446
31
- sql_glider-0.1.10.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
32
- sql_glider-0.1.10.dist-info/entry_points.txt,sha256=HDuakHqHS5C0HFKsMIxMYmDU7-BLBGrnIJcYaVRu-s0,251
33
- sql_glider-0.1.10.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
34
- sql_glider-0.1.10.dist-info/RECORD,,
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.10'
32
- __version_tuple__ = version_tuple = (0, 1, 10)
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
@@ -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(sql_content, dialect=file_dialect)
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
@@ -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)
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]: