sql-glider 0.1.15__tar.gz → 0.1.16__tar.gz

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.
Files changed (150) hide show
  1. {sql_glider-0.1.15 → sql_glider-0.1.16}/PKG-INFO +1 -1
  2. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/_version.py +2 -2
  3. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/lineage/analyzer.py +49 -5
  4. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/lineage/test_analyzer.py +99 -1
  5. {sql_glider-0.1.15 → sql_glider-0.1.16}/.github/workflows/ci.yml +0 -0
  6. {sql_glider-0.1.15 → sql_glider-0.1.16}/.github/workflows/publish.yml +0 -0
  7. {sql_glider-0.1.15 → sql_glider-0.1.16}/.gitignore +0 -0
  8. {sql_glider-0.1.15 → sql_glider-0.1.16}/.python-version +0 -0
  9. {sql_glider-0.1.15 → sql_glider-0.1.16}/ARCHITECTURE.md +0 -0
  10. {sql_glider-0.1.15 → sql_glider-0.1.16}/CLAUDE.md +0 -0
  11. {sql_glider-0.1.15 → sql_glider-0.1.16}/LICENSE +0 -0
  12. {sql_glider-0.1.15 → sql_glider-0.1.16}/README.md +0 -0
  13. {sql_glider-0.1.15 → sql_glider-0.1.16}/plans/2025-12-05-column-level-lineage.md +0 -0
  14. {sql_glider-0.1.15 → sql_glider-0.1.16}/plans/2025-12-05-reverse-lineage.md +0 -0
  15. {sql_glider-0.1.15 → sql_glider-0.1.16}/plans/2025-12-06-config-file-support.md +0 -0
  16. {sql_glider-0.1.15 → sql_glider-0.1.16}/plans/2025-12-06-graph-lineage.md +0 -0
  17. {sql_glider-0.1.15 → sql_glider-0.1.16}/plans/2025-12-06-unify-single-multi-query.md +0 -0
  18. {sql_glider-0.1.15 → sql_glider-0.1.16}/plans/2025-12-07-sample-data-model.md +0 -0
  19. {sql_glider-0.1.15 → sql_glider-0.1.16}/plans/2025-12-07-sql-templating.md +0 -0
  20. {sql_glider-0.1.15 → sql_glider-0.1.16}/plans/2025-12-08-tables-command.md +0 -0
  21. {sql_glider-0.1.15 → sql_glider-0.1.16}/plans/2025-12-09-graph-query-paths.md +0 -0
  22. {sql_glider-0.1.15 → sql_glider-0.1.16}/plans/2025-12-13-dissect-command.md +0 -0
  23. {sql_glider-0.1.15 → sql_glider-0.1.16}/plans/2025-12-14-tables-pull-command.md +0 -0
  24. {sql_glider-0.1.15 → sql_glider-0.1.16}/plans/2026-01-25-fix-union-lineage-chain.md +0 -0
  25. {sql_glider-0.1.15 → sql_glider-0.1.16}/plans/2026-01-26-file-scoped-schema-context.md +0 -0
  26. {sql_glider-0.1.15 → sql_glider-0.1.16}/plans/2026-01-28-sparksql-table-extraction.md +0 -0
  27. {sql_glider-0.1.15 → sql_glider-0.1.16}/plans/2026-01-29-no-star-flag.md +0 -0
  28. {sql_glider-0.1.15 → sql_glider-0.1.16}/plans/2026-01-29-resolve-schema.md +0 -0
  29. {sql_glider-0.1.15 → sql_glider-0.1.16}/plans/2026-01-29-schema-pruning-optimization.md +0 -0
  30. {sql_glider-0.1.15 → sql_glider-0.1.16}/plans/2026-01-29-tables-scrape-command.md +0 -0
  31. {sql_glider-0.1.15 → sql_glider-0.1.16}/pyproject.toml +0 -0
  32. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/README.md +0 -0
  33. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/business/expire_dim_customer.sql +0 -0
  34. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/business/load_fact_orders.sql +0 -0
  35. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/business/load_fact_payments.sql +0 -0
  36. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/business/merge_dim_customer.sql +0 -0
  37. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/business/merge_dim_product.sql +0 -0
  38. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/business/update_dim_customer_metrics.sql +0 -0
  39. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/complex/conditional_merge.sql +0 -0
  40. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/complex/cte_insert.sql +0 -0
  41. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/complex/multi_table_transform.sql +0 -0
  42. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/ddl/dim_customer.sql +0 -0
  43. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/ddl/dim_product.sql +0 -0
  44. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/ddl/fact_orders.sql +0 -0
  45. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/ddl/fact_payments.sql +0 -0
  46. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/ddl/raw_addresses.sql +0 -0
  47. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/ddl/raw_customers.sql +0 -0
  48. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/ddl/raw_order_items.sql +0 -0
  49. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/ddl/raw_orders.sql +0 -0
  50. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/ddl/raw_payments.sql +0 -0
  51. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/ddl/raw_products.sql +0 -0
  52. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/ddl/stg_customers.sql +0 -0
  53. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/ddl/stg_orders.sql +0 -0
  54. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/ddl/stg_payments.sql +0 -0
  55. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/ddl/stg_products.sql +0 -0
  56. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/incremental/incr_fact_orders.sql +0 -0
  57. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/incremental/incr_fact_payments.sql +0 -0
  58. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/incremental/incr_pres_sales_summary.sql +0 -0
  59. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/maintenance/delete_expired_customers.sql +0 -0
  60. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/maintenance/update_product_status.sql +0 -0
  61. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/presentation/load_pres_customer_360.sql +0 -0
  62. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/presentation/load_pres_customer_cohort.sql +0 -0
  63. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/presentation/load_pres_product_performance.sql +0 -0
  64. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/presentation/load_pres_sales_summary.sql +0 -0
  65. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/staging/load_stg_customers.sql +0 -0
  66. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/staging/load_stg_orders.sql +0 -0
  67. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/staging/load_stg_payments.sql +0 -0
  68. {sql_glider-0.1.15 → sql_glider-0.1.16}/sample_data_model/staging/load_stg_products.sql +0 -0
  69. {sql_glider-0.1.15 → sql_glider-0.1.16}/sqlglider.toml.example +0 -0
  70. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/__init__.py +0 -0
  71. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/catalog/__init__.py +0 -0
  72. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/catalog/base.py +0 -0
  73. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/catalog/databricks.py +0 -0
  74. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/catalog/registry.py +0 -0
  75. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/cli.py +0 -0
  76. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/dissection/__init__.py +0 -0
  77. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/dissection/analyzer.py +0 -0
  78. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/dissection/formatters.py +0 -0
  79. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/dissection/models.py +0 -0
  80. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/global_models.py +0 -0
  81. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/graph/__init__.py +0 -0
  82. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/graph/builder.py +0 -0
  83. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/graph/formatters.py +0 -0
  84. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/graph/merge.py +0 -0
  85. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/graph/models.py +0 -0
  86. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/graph/query.py +0 -0
  87. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/graph/serialization.py +0 -0
  88. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/lineage/__init__.py +0 -0
  89. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/lineage/formatters.py +0 -0
  90. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/schema/__init__.py +0 -0
  91. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/schema/extractor.py +0 -0
  92. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/templating/__init__.py +0 -0
  93. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/templating/base.py +0 -0
  94. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/templating/jinja.py +0 -0
  95. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/templating/registry.py +0 -0
  96. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/templating/variables.py +0 -0
  97. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/utils/__init__.py +0 -0
  98. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/utils/config.py +0 -0
  99. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/utils/file_utils.py +0 -0
  100. {sql_glider-0.1.15 → sql_glider-0.1.16}/src/sqlglider/utils/schema.py +0 -0
  101. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/__init__.py +0 -0
  102. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/fixtures/multi_file_queries/analytics_pipeline.sql +0 -0
  103. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/fixtures/multi_file_queries/analytics_pipeline_union_merge.sql +0 -0
  104. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/fixtures/multi_file_queries/customers.sql +0 -0
  105. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/fixtures/multi_file_queries/orders.sql +0 -0
  106. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/fixtures/multi_file_queries/reports.sql +0 -0
  107. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/fixtures/multi_file_queries/view_based_merge.sql +0 -0
  108. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/fixtures/original_queries/test_cte.sql +0 -0
  109. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/fixtures/original_queries/test_cte_query.sql +0 -0
  110. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/fixtures/original_queries/test_cte_view_star.sql +0 -0
  111. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/fixtures/original_queries/test_generated_column_query.sql +0 -0
  112. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/fixtures/original_queries/test_multi.sql +0 -0
  113. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/fixtures/original_queries/test_multi_query.sql +0 -0
  114. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/fixtures/original_queries/test_single_query.sql +0 -0
  115. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/fixtures/original_queries/test_subquery.sql +0 -0
  116. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/fixtures/original_queries/test_tables.sql +0 -0
  117. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/fixtures/original_queries/test_view.sql +0 -0
  118. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/fixtures/original_queries/test_view_window_cte.sql +0 -0
  119. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/fixtures/sample_manifest.csv +0 -0
  120. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/__init__.py +0 -0
  121. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/catalog/__init__.py +0 -0
  122. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/catalog/test_base.py +0 -0
  123. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/catalog/test_databricks.py +0 -0
  124. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/catalog/test_registry.py +0 -0
  125. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/dissection/__init__.py +0 -0
  126. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/dissection/test_analyzer.py +0 -0
  127. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/dissection/test_formatters.py +0 -0
  128. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/dissection/test_models.py +0 -0
  129. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/graph/__init__.py +0 -0
  130. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/graph/test_builder.py +0 -0
  131. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/graph/test_formatters.py +0 -0
  132. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/graph/test_merge.py +0 -0
  133. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/graph/test_models.py +0 -0
  134. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/graph/test_query.py +0 -0
  135. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/graph/test_serialization.py +0 -0
  136. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/lineage/__init__.py +0 -0
  137. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/lineage/test_formatters.py +0 -0
  138. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/schema/__init__.py +0 -0
  139. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/schema/test_extractor.py +0 -0
  140. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/templating/__init__.py +0 -0
  141. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/templating/test_base.py +0 -0
  142. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/templating/test_jinja.py +0 -0
  143. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/templating/test_registry.py +0 -0
  144. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/templating/test_variables.py +0 -0
  145. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/test_cli.py +0 -0
  146. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/utils/__init__.py +0 -0
  147. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/utils/test_config.py +0 -0
  148. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/utils/test_file_utils.py +0 -0
  149. {sql_glider-0.1.15 → sql_glider-0.1.16}/tests/sqlglider/utils/test_schema.py +0 -0
  150. {sql_glider-0.1.15 → sql_glider-0.1.16}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sql-glider
3
- Version: 0.1.15
3
+ Version: 0.1.16
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/
@@ -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.15'
32
- __version_tuple__ = version_tuple = (0, 1, 15)
31
+ __version__ = version = '0.1.16'
32
+ __version_tuple__ = version_tuple = (0, 1, 16)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -11,6 +11,48 @@ from sqlglot.lineage import Node, lineage
11
11
  from sqlglider.global_models import AnalysisLevel
12
12
 
13
13
 
14
+ def _flat_schema_to_nested(
15
+ schema: Dict[str, Dict[str, str]],
16
+ ) -> Dict[str, object]:
17
+ """Convert flat dot-notation schema keys to the nested dict structure sqlglot expects.
18
+
19
+ sqlglot's MappingSchema requires consistent nesting depth across all tables.
20
+ Flat keys like ``"db.table"`` are split on dots and nested accordingly.
21
+ Shorter keys are padded with empty-string prefixes to match the max depth.
22
+
23
+ Examples::
24
+
25
+ {"users": {"id": "UNKNOWN"}}
26
+ → {"users": {"id": "UNKNOWN"}} (depth 1, no change)
27
+
28
+ {"db.users": {"id": "UNKNOWN"}, "my_view": {"x": "UNKNOWN"}}
29
+ → {"db": {"users": {"id": "UNKNOWN"}}, "": {"my_view": {"x": "UNKNOWN"}}}
30
+ """
31
+ if not schema:
32
+ return {}
33
+
34
+ # Split all keys into parts
35
+ entries = [(key.split("."), cols) for key, cols in schema.items()]
36
+ max_depth = max(len(parts) for parts, _ in entries)
37
+
38
+ # If all keys are single-part (unqualified), return as-is
39
+ if max_depth == 1:
40
+ return schema # type: ignore[return-value]
41
+
42
+ # Pad shorter keys with empty-string prefixes to match max depth
43
+ nested: Dict[str, object] = {}
44
+ for parts, cols in entries:
45
+ while len(parts) < max_depth:
46
+ parts.insert(0, "")
47
+ d: Dict[str, object] = nested
48
+ for part in parts[:-1]:
49
+ if part not in d:
50
+ d[part] = {}
51
+ d = d[part] # type: ignore[assignment]
52
+ d[parts[-1]] = cols
53
+ return nested
54
+
55
+
14
56
  class StarResolutionError(Exception):
15
57
  """Raised when SELECT * cannot be resolved and no_star mode is enabled."""
16
58
 
@@ -860,8 +902,10 @@ class LineageAnalyzer:
860
902
  current_query_sql = self.expr.sql(dialect=self.dialect)
861
903
 
862
904
  # Prune schema to only tables referenced in this query to avoid
863
- # sqlglot.lineage() performance degradation with large schema dicts
864
- pruned_schema: Optional[Dict[str, Dict[str, str]]] = None
905
+ # sqlglot.lineage() performance degradation with large schema dicts.
906
+ # Then convert from flat dot-notation keys to the nested dict structure
907
+ # that sqlglot's MappingSchema expects.
908
+ lineage_schema: Optional[Dict[str, object]] = None
865
909
  if self._file_schema:
866
910
  referenced = {t.lower() for t in self._get_query_tables()}
867
911
  pruned_schema = {
@@ -869,8 +913,8 @@ class LineageAnalyzer:
869
913
  for table, cols in self._file_schema.items()
870
914
  if table.lower() in referenced
871
915
  }
872
- if not pruned_schema:
873
- pruned_schema = None
916
+ if pruned_schema:
917
+ lineage_schema = _flat_schema_to_nested(pruned_schema)
874
918
 
875
919
  for col in columns_to_analyze:
876
920
  try:
@@ -883,7 +927,7 @@ class LineageAnalyzer:
883
927
  lineage_col,
884
928
  current_query_sql,
885
929
  dialect=self.dialect,
886
- schema=pruned_schema,
930
+ schema=lineage_schema,
887
931
  )
888
932
 
889
933
  # Collect all source columns
@@ -3,7 +3,11 @@
3
3
  import pytest
4
4
 
5
5
  from sqlglider.global_models import AnalysisLevel
6
- from sqlglider.lineage.analyzer import LineageAnalyzer, StarResolutionError
6
+ from sqlglider.lineage.analyzer import (
7
+ LineageAnalyzer,
8
+ StarResolutionError,
9
+ _flat_schema_to_nested,
10
+ )
7
11
 
8
12
 
9
13
  class TestCaseInsensitiveForwardLineage:
@@ -3181,3 +3185,97 @@ class TestSchemaPruning:
3181
3185
  output_names = {item.output_name for r in results for item in r.lineage_items}
3182
3186
  assert "id" in output_names
3183
3187
  assert "email" in output_names
3188
+
3189
+
3190
+ class TestFlatSchemaToNested:
3191
+ """Tests for _flat_schema_to_nested conversion utility."""
3192
+
3193
+ def test_empty(self):
3194
+ assert _flat_schema_to_nested({}) == {}
3195
+
3196
+ def test_unqualified_passthrough(self):
3197
+ schema = {"users": {"id": "UNKNOWN"}}
3198
+ assert _flat_schema_to_nested(schema) == schema
3199
+
3200
+ def test_two_part_keys(self):
3201
+ schema = {"db.users": {"id": "UNKNOWN"}}
3202
+ result = _flat_schema_to_nested(schema)
3203
+ assert result == {"db": {"users": {"id": "UNKNOWN"}}}
3204
+
3205
+ def test_three_part_keys(self):
3206
+ schema = {"cat.db.users": {"id": "UNKNOWN"}}
3207
+ result = _flat_schema_to_nested(schema)
3208
+ assert result == {"cat": {"db": {"users": {"id": "UNKNOWN"}}}}
3209
+
3210
+ def test_mixed_depth_pads_shorter_keys(self):
3211
+ schema = {
3212
+ "my_view": {"x": "UNKNOWN"},
3213
+ "db.users": {"id": "UNKNOWN"},
3214
+ }
3215
+ result = _flat_schema_to_nested(schema)
3216
+ assert result == {
3217
+ "": {"my_view": {"x": "UNKNOWN"}},
3218
+ "db": {"users": {"id": "UNKNOWN"}},
3219
+ }
3220
+
3221
+
3222
+ class TestQualifiedSchemaKeys:
3223
+ """Tests for schema with qualified (dotted) table names."""
3224
+
3225
+ def test_qualified_star_expansion(self):
3226
+ """SELECT * resolves correctly with qualified schema keys."""
3227
+ sql = "SELECT * FROM mydb.users"
3228
+ schema = {"mydb.users": {"id": "UNKNOWN", "name": "UNKNOWN"}}
3229
+ analyzer = LineageAnalyzer(sql, dialect="spark", schema=schema)
3230
+ results = analyzer.analyze_queries(level=AnalysisLevel.COLUMN)
3231
+ items = {
3232
+ (item.source_name, item.output_name)
3233
+ for r in results
3234
+ for item in r.lineage_items
3235
+ }
3236
+ assert ("mydb.users.id", "id") in items
3237
+ assert ("mydb.users.name", "name") in items
3238
+
3239
+ def test_qualified_explicit_columns(self):
3240
+ """Explicit columns trace sources correctly with qualified schema keys."""
3241
+ sql = "SELECT id, name FROM mydb.users"
3242
+ schema = {"mydb.users": {"id": "UNKNOWN", "name": "UNKNOWN"}}
3243
+ analyzer = LineageAnalyzer(sql, dialect="spark", schema=schema)
3244
+ results = analyzer.analyze_queries(level=AnalysisLevel.COLUMN)
3245
+ items = {
3246
+ (item.source_name, item.output_name)
3247
+ for r in results
3248
+ for item in r.lineage_items
3249
+ }
3250
+ assert ("mydb.users.id", "mydb.users.id") in items
3251
+ assert ("mydb.users.name", "mydb.users.name") in items
3252
+
3253
+ def test_three_part_qualified(self):
3254
+ """3-part qualified names (catalog.db.table) work correctly."""
3255
+ sql = "SELECT id FROM catalog.mydb.users"
3256
+ schema = {"catalog.mydb.users": {"id": "UNKNOWN"}}
3257
+ analyzer = LineageAnalyzer(sql, dialect="spark", schema=schema)
3258
+ results = analyzer.analyze_queries(level=AnalysisLevel.COLUMN)
3259
+ items = [
3260
+ (item.source_name, item.output_name)
3261
+ for r in results
3262
+ for item in r.lineage_items
3263
+ ]
3264
+ assert len(items) == 1
3265
+ assert items[0] == ("catalog.mydb.users.id", "catalog.mydb.users.id")
3266
+
3267
+ def test_mixed_qualified_and_unqualified(self):
3268
+ """Mix of qualified and unqualified table names in schema."""
3269
+ sql = "SELECT * FROM my_view"
3270
+ schema = {
3271
+ "my_view": {"id": "UNKNOWN"},
3272
+ "mydb.users": {"id": "UNKNOWN", "name": "UNKNOWN"},
3273
+ }
3274
+ analyzer = LineageAnalyzer(sql, dialect="spark", schema=schema)
3275
+ results = analyzer.analyze_queries(level=AnalysisLevel.COLUMN)
3276
+ items = {
3277
+ (item.source_name, item.output_name)
3278
+ for r in results
3279
+ for item in r.lineage_items
3280
+ }
3281
+ assert ("my_view.id", "id") in items
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes