sql-code-graph 1.4.0__tar.gz → 1.4.1__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 (438) hide show
  1. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/PKG-INFO +1 -1
  2. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/pyproject.toml +1 -1
  3. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/__init__.py +1 -1
  4. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/cli/commands/analyze.py +8 -1
  5. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/parsers/base.py +82 -0
  6. sql_code_graph-1.4.1/tests/integration/test_analyze_case_fold.py +234 -0
  7. sql_code_graph-1.4.1/tests/integration/test_bare_column_cte_lineage.py +332 -0
  8. sql_code_graph-1.4.1/tests/integration/test_v141_surface_guards.py +490 -0
  9. sql_code_graph-1.4.1/tests/unit/test_analyze_case_fold.py +229 -0
  10. sql_code_graph-1.4.1/tests/unit/test_graph_completeness_invariant.py +188 -0
  11. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/uv.lock +1 -1
  12. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/.claude/agents/api-documenter.md +0 -0
  13. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/.claude/agents/architect-planner.md +0 -0
  14. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/.claude/agents/architect-reviewer.md +0 -0
  15. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/.claude/agents/code-reviewer.md +0 -0
  16. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/.claude/agents/developer.md +0 -0
  17. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/.claude/agents/plan-reviewer.md +0 -0
  18. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/.claude/agents/sprint-planner.md +0 -0
  19. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  20. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  21. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  22. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/.github/workflows/benchmark.yml +0 -0
  23. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/.github/workflows/e2e-tests.yml +0 -0
  24. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/.github/workflows/release.yml +0 -0
  25. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/.github/workflows/test.yml +0 -0
  26. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/.gitignore +0 -0
  27. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/.pre-commit-config.yaml +0 -0
  28. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/.sqlcgignore +0 -0
  29. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/ARCHITECTURE_REVIEW.md +0 -0
  30. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/CHANGELOG.md +0 -0
  31. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/CLAUDE.md +0 -0
  32. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/README.md +0 -0
  33. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/docs/AIRBNB_PARSE_REPORT.md +0 -0
  34. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/docs/cli.md +0 -0
  35. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/e2e_firstuser_report.md +0 -0
  36. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/e2e_run_20260528_101413.output +0 -0
  37. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/main.py +0 -0
  38. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/WORKFLOW.md +0 -0
  39. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/blueprint.md +0 -0
  40. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/bundle_claude_skill.md +0 -0
  41. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/feature_34_unused_presentation_segregation.md +0 -0
  42. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/feature_35_external_downstream_injection.md +0 -0
  43. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/feature_F2_bundle_claude_skill.md +0 -0
  44. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/feature_kuzu_to_duckdb_migration.md +0 -0
  45. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/fix_downstream_sink_location.md +0 -0
  46. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/fix_dynamic_table_parsing.md +0 -0
  47. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/fix_expand_qualify_perf.md +0 -0
  48. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/fix_firstuser_findings.md +0 -0
  49. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/fix_issue29_live_test_followups.md +0 -0
  50. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/fix_schema_case_mismatch.md +0 -0
  51. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/hygiene_config_path_and_survivors.md +0 -0
  52. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/investigation_e5_e4.md +0 -0
  53. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/living_codebase_resync.md +0 -0
  54. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/measurements/schema_comparison_with_schema.json +0 -0
  55. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/measurements/schema_comparison_without_schema.json +0 -0
  56. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/measurements/sprint_08_changelogs_fullindex.json +0 -0
  57. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/measurements/sprint_08_fullcorpus_index.json +0 -0
  58. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/measurements/sprint_pool_300s_plan.json +0 -0
  59. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/parse_diagnostics.md +0 -0
  60. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/parsing_errors_experiment.md +0 -0
  61. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/snowflake_en_test_suite.md +0 -0
  62. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sprint_01.md +0 -0
  63. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sprint_01_deployment_pypi.md +0 -0
  64. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sprint_02.md +0 -0
  65. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sprint_02_v0.3.0_core.md +0 -0
  66. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sprint_03.md +0 -0
  67. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sprint_03_v0.3.1_postmortem.md +0 -0
  68. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sprint_04_column_lineage.md +0 -0
  69. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sprint_04_column_lineage_fix.md +0 -0
  70. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sprint_05_star_resolution.md +0 -0
  71. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sprint_06_lineage_coverage.md +0 -0
  72. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sprint_07_open_ecodes.md +0 -0
  73. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sprint_07_perf_and_live_test.md +0 -0
  74. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sprint_08_perf_upsert.md +0 -0
  75. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sprint_09_lineage_coverage.md +0 -0
  76. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sprint_10_anchor_tools.md +0 -0
  77. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sprint_11_v1.0.2_bugfix.md +0 -0
  78. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sprint_12_v1.1.0.md +0 -0
  79. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sprint_13_v1.1.0_cluster_b.md +0 -0
  80. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sprint_3.1_postmortem.md +0 -0
  81. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/sqlcg.md +0 -0
  82. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/trust_layer.md +0 -0
  83. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/v1.1.0_cluster_b_provenance_trust.md +0 -0
  84. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/v1.1.0_live_graph_freshness.md +0 -0
  85. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/v1.1.1_batch_upsert_perf.md +0 -0
  86. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/v1_1_2_bugfix.md +0 -0
  87. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/v1_1_3_union_cte_star.md +0 -0
  88. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/v1_2_0_read_proxy.md +0 -0
  89. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/plan/v1_2_1_bugfix.md +0 -0
  90. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/profile.html +0 -0
  91. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/pyrightconfig.json +0 -0
  92. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/scripts/collect_parse_errors.py +0 -0
  93. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/scripts/generate_cli_docs.sh +0 -0
  94. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/__main__.py +0 -0
  95. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/cli/__init__.py +0 -0
  96. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/cli/commands/__init__.py +0 -0
  97. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/cli/commands/db.py +0 -0
  98. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/cli/commands/find.py +0 -0
  99. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/cli/commands/gain.py +0 -0
  100. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/cli/commands/git.py +0 -0
  101. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/cli/commands/index.py +0 -0
  102. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/cli/commands/install.py +0 -0
  103. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/cli/commands/mcp.py +0 -0
  104. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/cli/commands/reindex.py +0 -0
  105. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/cli/commands/report.py +0 -0
  106. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/cli/commands/uninstall.py +0 -0
  107. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/cli/commands/watch.py +0 -0
  108. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/cli/main.py +0 -0
  109. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/core/__init__.py +0 -0
  110. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/core/config.py +0 -0
  111. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/core/duckdb_backend.py +0 -0
  112. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/core/freshness.py +0 -0
  113. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/core/graph_db.py +0 -0
  114. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/core/jobs.py +0 -0
  115. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/core/queries.cypher +0 -0
  116. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/core/queries.py +0 -0
  117. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/core/queries.sql +0 -0
  118. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/core/schema.cypher +0 -0
  119. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/core/schema.py +0 -0
  120. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/indexer/__init__.py +0 -0
  121. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/indexer/dbt_adapter.py +0 -0
  122. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/indexer/error_classify.py +0 -0
  123. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/indexer/git_delta.py +0 -0
  124. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/indexer/indexer.py +0 -0
  125. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/indexer/pool.py +0 -0
  126. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/indexer/walker.py +0 -0
  127. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/indexer/watcher.py +0 -0
  128. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/lineage/__init__.py +0 -0
  129. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/lineage/aggregator.py +0 -0
  130. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/lineage/schema_resolver.py +0 -0
  131. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/metrics/__init__.py +0 -0
  132. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/metrics/store.py +0 -0
  133. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/parsers/__init__.py +0 -0
  134. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/parsers/ansi_parser.py +0 -0
  135. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/parsers/bigquery_parser.py +0 -0
  136. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/parsers/postgres_parser.py +0 -0
  137. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/parsers/registry.py +0 -0
  138. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/parsers/snowflake_parser.py +0 -0
  139. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/parsers/tsql_parser.py +0 -0
  140. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/server/__init__.py +0 -0
  141. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/server/control.py +0 -0
  142. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/server/exceptions.py +0 -0
  143. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/server/models.py +0 -0
  144. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/server/noise_filter.py +0 -0
  145. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/server/read_client.py +0 -0
  146. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/server/server.py +0 -0
  147. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/server/skill.py +0 -0
  148. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/server/tools.py +0 -0
  149. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/server/writer.py +0 -0
  150. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/utils/__init__.py +0 -0
  151. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/utils/hashing.py +0 -0
  152. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/utils/ignore.py +0 -0
  153. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/src/sqlcg/utils/logging.py +0 -0
  154. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/__init__.py +0 -0
  155. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/__init__.py +0 -0
  156. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/adversarial/200_join.sql +0 -0
  157. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/adversarial/500_union.sql +0 -0
  158. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/bench_indexer.py +0 -0
  159. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/conftest.py +0 -0
  160. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/golden_corpus/snowflake/case_normalization.sql +0 -0
  161. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/golden_corpus/snowflake/colon_cast.sql +0 -0
  162. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/golden_corpus/snowflake/colon_reserved_word.sql +0 -0
  163. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/golden_corpus/snowflake/copy_into.sql +0 -0
  164. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/golden_corpus/snowflake/create_procedure.sql +0 -0
  165. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/golden_corpus/snowflake/identifier_dynamic.sql +0 -0
  166. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/golden_corpus/snowflake/lateral_flatten.sql +0 -0
  167. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/golden_corpus/snowflake/qualify.sql +0 -0
  168. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/golden_corpus/snowflake/scripting_block.sql +0 -0
  169. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/golden_corpus/snowflake/three_part.sql +0 -0
  170. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/tpch/q01.sql +0 -0
  171. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/tpch/q02.sql +0 -0
  172. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/tpch/q03.sql +0 -0
  173. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/tpch/q04.sql +0 -0
  174. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/benchmarks/tpch/q05.sql +0 -0
  175. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/e2e/__init__.py +0 -0
  176. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/e2e/conftest.py +0 -0
  177. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/e2e/test_F2_skill_install_e2e.py +0 -0
  178. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/e2e/test_airbnb_e2e.py +0 -0
  179. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/e2e/test_cli_index.py +0 -0
  180. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/e2e/test_git_hook_install.py +0 -0
  181. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/e2e/test_golden_lineage.py +0 -0
  182. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/e2e/test_mcp_lifecycle.py +0 -0
  183. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/e2e/test_mcp_tools.py +0 -0
  184. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/e2e/test_parse_diagnostics_cli.py +0 -0
  185. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/e2e/test_star_resolution_e2e.py +0 -0
  186. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/e2e/test_watch.py +0 -0
  187. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/airbnb/dim_hosts_cleansed.sql +0 -0
  188. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/airbnb/dim_listings_cleansed.sql +0 -0
  189. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/airbnb/fct_reviews.sql +0 -0
  190. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/airbnb/mart_fullmoon_reviews.sql +0 -0
  191. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/airbnb/raw_hosts.sql +0 -0
  192. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/airbnb/raw_listings.sql +0 -0
  193. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/airbnb/raw_reviews.sql +0 -0
  194. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/airbnb/src_hosts.sql +0 -0
  195. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/airbnb/src_listings.sql +0 -0
  196. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/airbnb/src_reviews.sql +0 -0
  197. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/bigquery/.gitkeep +0 -0
  198. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/duckdb_parity/kuzu_reference.json +0 -0
  199. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/jaffle_shop/customers.sql +0 -0
  200. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/jaffle_shop/orders.sql +0 -0
  201. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/jaffle_shop/raw_orders.sql +0 -0
  202. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/snowflake/base_tables.sql +0 -0
  203. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/snowflake/reports.sql +0 -0
  204. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/snowflake/views.sql +0 -0
  205. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/star_corpus/ddl_src.sql +0 -0
  206. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/star_corpus/ddl_tgt.sql +0 -0
  207. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/star_corpus/etl_alias_star.sql +0 -0
  208. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/star_corpus/etl_star.sql +0 -0
  209. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/synthetic/base_tables.sql +0 -0
  210. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/synthetic/reports.sql +0 -0
  211. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/fixtures/synthetic/views.sql +0 -0
  212. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/__init__.py +0 -0
  213. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/snowflake/__init__.py +0 -0
  214. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/snowflake/test_insert_select.py +0 -0
  215. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_T34_presentation_segregation.py +0 -0
  216. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_T35_external_consumers.py +0 -0
  217. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_anchor_tools.py +0 -0
  218. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_bulk_upsert.py +0 -0
  219. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_column_lineage_e2e.py +0 -0
  220. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_cross_file_lineage.py +0 -0
  221. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_cte_recall_guard.py +0 -0
  222. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_cte_schema_alias_guard.py +0 -0
  223. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_cte_source_node_invariant.py +0 -0
  224. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_dialect_auto_resolution.py +0 -0
  225. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_dialect_matrix.py +0 -0
  226. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_duckdb_parity.py +0 -0
  227. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_e36_xfile_regression_guard.py +0 -0
  228. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_freshness_mcp.py +0 -0
  229. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_hygiene_config_root_reconciliation.py +0 -0
  230. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_indexer_batching.py +0 -0
  231. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_indexer_commits.py +0 -0
  232. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_indexer_to_graph.py +0 -0
  233. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_live_anchors.py +0 -0
  234. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_mvcc_rebuild.py +0 -0
  235. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_parse_diagnostics.py +0 -0
  236. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_pr1_confidence_reason.py +0 -0
  237. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_pr2_source_location.py +0 -0
  238. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_pr3_kind_tagging.py +0 -0
  239. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_read_via_server.py +0 -0
  240. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_readonly_under_lock.py +0 -0
  241. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_reindex_via_server.py +0 -0
  242. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_resync.py +0 -0
  243. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_single_writer_queue.py +0 -0
  244. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_star_resolution.py +0 -0
  245. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_temp_table_lineage.py +0 -0
  246. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_union_cte_star_recall_guard.py +0 -0
  247. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/integration/test_user_surface_recall_guard.py +0 -0
  248. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/perf/__init__.py +0 -0
  249. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/perf/test_perf.py +0 -0
  250. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E10/__init__.py +0 -0
  251. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E11/__init__.py +0 -0
  252. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E12/__init__.py +0 -0
  253. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E12/e12_json_path.sql +0 -0
  254. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E12/e12_lateral_flatten.sql +0 -0
  255. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E12/test_e12.py +0 -0
  256. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E13/__init__.py +0 -0
  257. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E14/__init__.py +0 -0
  258. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E15/__init__.py +0 -0
  259. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E16/__init__.py +0 -0
  260. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E16/e16_merge.sql +0 -0
  261. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E16/e16_merge_delete.sql +0 -0
  262. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E16/test_e16.py +0 -0
  263. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E17/__init__.py +0 -0
  264. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E18/__init__.py +0 -0
  265. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E18/e18_decode.sql +0 -0
  266. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E18/e18_iff_decode.sql +0 -0
  267. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E18/e18_nvl2.sql +0 -0
  268. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E18/test_e18.py +0 -0
  269. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E19/__init__.py +0 -0
  270. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E2/__init__.py +0 -0
  271. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E2/e2_expr_alias.sql +0 -0
  272. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E2/e2_function_alias.sql +0 -0
  273. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E2/e2_multiply_alias.sql +0 -0
  274. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E2/test_e2.py +0 -0
  275. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E20/__init__.py +0 -0
  276. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E21/__init__.py +0 -0
  277. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E21/e21_alias_forward_ref.sql +0 -0
  278. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E21/e21_three_level_chain.sql +0 -0
  279. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E21/test_e21.py +0 -0
  280. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E22/__init__.py +0 -0
  281. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E23/__init__.py +0 -0
  282. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E23/e23_stored_proc.sql +0 -0
  283. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E23/e23_stored_proc_multi.sql +0 -0
  284. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E23/test_e23.py +0 -0
  285. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E24/__init__.py +0 -0
  286. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E25/__init__.py +0 -0
  287. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E25/e25_cross_db.sql +0 -0
  288. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E25/e25_two_part.sql +0 -0
  289. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E25/test_e25.py +0 -0
  290. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E25/test_e25_full_id.py +0 -0
  291. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E26/__init__.py +0 -0
  292. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E27/__init__.py +0 -0
  293. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E27/e27_nested_udf.sql +0 -0
  294. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E27/e27_udf.sql +0 -0
  295. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E27/test_e27.py +0 -0
  296. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E28/__init__.py +0 -0
  297. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E29/__init__.py +0 -0
  298. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E3/__init__.py +0 -0
  299. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E3/e3_alter_table.sql +0 -0
  300. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E3/e3_create_sequence.sql +0 -0
  301. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E3/e3_ddl_only.sql +0 -0
  302. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E3/test_e3.py +0 -0
  303. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E30/__init__.py +0 -0
  304. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E31/__init__.py +0 -0
  305. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E32/__init__.py +0 -0
  306. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E33/__init__.py +0 -0
  307. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E34/__init__.py +0 -0
  308. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E35/__init__.py +0 -0
  309. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E36/__init__.py +0 -0
  310. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E36/e36_temp_multi_use.sql +0 -0
  311. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E36/e36_temp_table.sql +0 -0
  312. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E36/test_e36.py +0 -0
  313. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E36/test_e36_xfile.py +0 -0
  314. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E37/__init__.py +0 -0
  315. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E38/__init__.py +0 -0
  316. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E4/__init__.py +0 -0
  317. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E4/e4_execute_immediate.sql +0 -0
  318. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E4/e4_if_not_exists.sql +0 -0
  319. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E4/e4_unexpected_token.sql +0 -0
  320. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E4/e4_unpivot.sql +0 -0
  321. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E4/test_e4.py +0 -0
  322. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E5/__init__.py +0 -0
  323. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E5/e5_cte_missing_source.sql +0 -0
  324. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E5/e5_multi_cte.sql +0 -0
  325. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E5/e5_nested_cte.sql +0 -0
  326. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E5/test_e5.py +0 -0
  327. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E5/test_e5_cte_projection.py +0 -0
  328. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E8/__init__.py +0 -0
  329. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E8/e8_dynamic_sources.sql +0 -0
  330. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E8/e8_seq_nextval.sql +0 -0
  331. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E8/e8_uuid.sql +0 -0
  332. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E8/test_e8.py +0 -0
  333. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E9/__init__.py +0 -0
  334. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E_aggregates/__init__.py +0 -0
  335. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E_aggregates/fixture_sum_absent_cross_schema.sql +0 -0
  336. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E_aggregates/fixture_sum_case_when.sql +0 -0
  337. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E_aggregates/fixture_sum_present_source.sql +0 -0
  338. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E_aggregates/test_e_aggregates.py +0 -0
  339. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E_date_functions/__init__.py +0 -0
  340. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E_date_functions/fixture_date_aliased.sql +0 -0
  341. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E_date_functions/fixture_date_unaliased.sql +0 -0
  342. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E_date_functions/fixture_datediff_unaliased.sql +0 -0
  343. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E_date_functions/fixture_year_unaliased.sql +0 -0
  344. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/E_date_functions/test_e_date_functions.py +0 -0
  345. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/README.md +0 -0
  346. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/__init__.py +0 -0
  347. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/anchors/__init__.py +0 -0
  348. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/anchors/fixture_etl.sql +0 -0
  349. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/anchors/fixture_omloopsnelheid.sql +0 -0
  350. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/anchors/fixture_semantic.sql +0 -0
  351. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/anchors/fixture_source.sql +0 -0
  352. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/anchors/test_anchor_ma_aantal_op_order.py +0 -0
  353. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/anchors/test_anchor_omloopsnelheid.py +0 -0
  354. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/conftest.py +0 -0
  355. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/snowflake/test_plan_review_gates.py +0 -0
  356. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/__init__.py +0 -0
  357. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/snowflake/__init__.py +0 -0
  358. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/snowflake/test_scripting_noise.py +0 -0
  359. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_BugB_escalation_uses_init_path.py +0 -0
  360. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_BugC_hook_upgrade.py +0 -0
  361. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_F2_install_skill.py +0 -0
  362. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_F2_skill_render.py +0 -0
  363. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_F2_uninstall_skill.py +0 -0
  364. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_T09_01_qualify_once.py +0 -0
  365. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_T09_02_ddl_skip.py +0 -0
  366. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_T09_04_subprocess_isolate.py +0 -0
  367. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_T09_06_log_verbosity.py +0 -0
  368. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_T35_config_external_consumers.py +0 -0
  369. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_aggregator.py +0 -0
  370. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_aggregator_skip.py +0 -0
  371. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_base_parser.py +0 -0
  372. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_branch_monitor.py +0 -0
  373. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_bulk_upsert_invariant.py +0 -0
  374. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_canonical_target_resolution.py +0 -0
  375. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_cli.py +0 -0
  376. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_cli_help.py +0 -0
  377. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_closure_depth.py +0 -0
  378. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_column_lineage_wiring.py +0 -0
  379. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_config.py +0 -0
  380. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_data_models.py +0 -0
  381. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_db_info.py +0 -0
  382. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_dominant_cause.py +0 -0
  383. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_duckdb_backend.py +0 -0
  384. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_duckdb_backend_shared.py +0 -0
  385. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_find_cmd.py +0 -0
  386. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_firstuser_findings.py +0 -0
  387. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_freshness_helper.py +0 -0
  388. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_gain_ratio.py +0 -0
  389. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_git_delta.py +0 -0
  390. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_git_hooks.py +0 -0
  391. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_git_hooks_notify.py +0 -0
  392. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_graph_backend.py +0 -0
  393. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_hard_kill_pool.py +0 -0
  394. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_hygiene_config_warning.py +0 -0
  395. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_include_working_tree.py +0 -0
  396. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_index_cmd.py +0 -0
  397. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_index_flags.py +0 -0
  398. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_index_progress.py +0 -0
  399. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_indexer_progress.py +0 -0
  400. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_indexer_quality.py +0 -0
  401. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_install.py +0 -0
  402. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_install_message.py +0 -0
  403. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_jobs.py +0 -0
  404. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_judgement.py +0 -0
  405. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_lineage_conversion.py +0 -0
  406. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_literal_column_skip.py +0 -0
  407. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_mcp_best_practices.py +0 -0
  408. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_mcp_control.py +0 -0
  409. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_mcp_stdio_smoke.py +0 -0
  410. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_metrics.py +0 -0
  411. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_noise_filter.py +0 -0
  412. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_parse_file_dependency_filter.py +0 -0
  413. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_parse_quality.py +0 -0
  414. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_parser.py +0 -0
  415. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_perf_scaling_guard.py +0 -0
  416. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_pr07_observability.py +0 -0
  417. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_queries_loader.py +0 -0
  418. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_read_client.py +0 -0
  419. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_resolve_pass2_passes_dependency_filter.py +0 -0
  420. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_schema_resolver.py +0 -0
  421. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_server.py +0 -0
  422. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_snowflake_strip_alter_set_tag.py +0 -0
  423. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_sprint_06_t04_t05.py +0 -0
  424. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_star_resolution_unit.py +0 -0
  425. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_star_schema_unit.py +0 -0
  426. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_submit_feedback.py +0 -0
  427. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_subprocess_isolate.py +0 -0
  428. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_t02_expression_name_extraction.py +0 -0
  429. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_t03_ddl_skip.py +0 -0
  430. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_timeout_cancel.py +0 -0
  431. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_tools_hints.py +0 -0
  432. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_tools_warnings.py +0 -0
  433. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_uninstall.py +0 -0
  434. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_unqualified_fallback.py +0 -0
  435. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_upsert_batch_invariant.py +0 -0
  436. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_walker.py +0 -0
  437. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_watcher.py +0 -0
  438. {sql_code_graph-1.4.0 → sql_code_graph-1.4.1}/tests/unit/test_writer_queue.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sql-code-graph
3
- Version: 1.4.0
3
+ Version: 1.4.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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sql-code-graph"
7
- version = "1.4.0"
7
+ version = "1.4.1"
8
8
  description = "SQL code graph analyzer and lineage tracer"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -1,5 +1,5 @@
1
1
  """SQL Code Graph - SQL lineage and dependency analysis tool."""
2
2
 
3
- __version__ = "1.4.0"
3
+ __version__ = "1.4.1"
4
4
 
5
5
  __all__ = ["__version__"]
@@ -139,6 +139,7 @@ def upstream( # noqa: B008
139
139
  console.print("[red]Error: --depth must be between 1 and 100[/red]")
140
140
  raise typer.Exit(1)
141
141
 
142
+ ref = ref.lower() # graph keys are lowercased at index time (C2 normalization)
142
143
  sql = _upstream_sql(depth, include_intermediate)
143
144
  results = run_read_routed(sql, {"ref": ref})
144
145
  if not results and len(ref.split(".")) >= 3:
@@ -175,6 +176,7 @@ def downstream( # noqa: B008
175
176
  console.print("[red]Error: --depth must be between 1 and 100[/red]")
176
177
  raise typer.Exit(1)
177
178
 
179
+ ref = ref.lower() # graph keys are lowercased at index time (C2 normalization)
178
180
  sql = _downstream_sql(depth, include_intermediate)
179
181
  results = run_read_routed(sql, {"ref": ref})
180
182
  if not results and len(ref.split(".")) >= 3:
@@ -291,7 +293,12 @@ def unused(
291
293
 
292
294
 
293
295
  def _bare_ref(ref: str) -> str:
294
- """Strip schema prefix from a ref string, keeping table.column."""
296
+ """Strip schema prefix from a ref string, keeping table.column.
297
+
298
+ Lowercases defensively so this is safe to call even if the caller did not
299
+ first fold the ref — graph keys are lowercased at index time (C2 normalization).
300
+ """
301
+ ref = ref.lower()
295
302
  parts = ref.split(".")
296
303
  if len(parts) >= 3:
297
304
  return ".".join(parts[1:])
@@ -515,6 +515,32 @@ class SqlParser(ABC):
515
515
  _walk(root)
516
516
  return edges
517
517
 
518
+ def _table_node_to_ref(self, table_node: Any) -> "TableRef | None":
519
+ """Convert a sqlglot exp.Table AST node to a TableRef.
520
+
521
+ Used by the #49 mis-bind override to enumerate candidate source tables
522
+ from a CTE body's FROM/JOIN once per CTE body (before the per-projection
523
+ loop). Does NOT call qualify/build_scope — preserves the O(1)-per-body
524
+ perf invariant.
525
+
526
+ Schema aliases are applied via _apply_table_alias so the emitted edges
527
+ carry the canonical (post-alias) table identity.
528
+
529
+ Args:
530
+ table_node: sqlglot.expressions.Table AST node
531
+
532
+ Returns:
533
+ TableRef with catalog/db/name extracted and alias-resolved, or None
534
+ if the alias resolution returns None (treated as an unresolvable ref).
535
+ """
536
+ return self._apply_table_alias(
537
+ TableRef(
538
+ catalog=table_node.catalog if table_node.catalog else None,
539
+ db=table_node.db if table_node.db else None,
540
+ name=table_node.name if table_node.name else str(table_node),
541
+ )
542
+ )
543
+
518
544
  def _lineage_node_to_table_ref(self, node: Any) -> "TableRef | None":
519
545
  """Extract a TableRef from a sqlglot LineageNode's source attribute.
520
546
 
@@ -987,6 +1013,20 @@ class SqlParser(ABC):
987
1013
  # string for every column rather than re-serializing O(N_cols) times.
988
1014
  cte_body_sql = cte_body.sql(dialect=self.DIALECT)
989
1015
 
1016
+ # Compute the candidate source-table set ONCE per CTE body
1017
+ # (before the per-projection loop) — never inside it.
1018
+ # Uses find_all(exp.Table) on the already-built AST; does NOT
1019
+ # call qualify/build_scope, preserving O(1) per CTE body.
1020
+ # This set is reused across all projections to detect ambiguity
1021
+ # (#49 mis-bind override).
1022
+ cte_source_tables: list[TableRef] = [
1023
+ ref
1024
+ for t in cte_body.find_all(exp.Table)
1025
+ if t.name # skip anonymous / subquery placeholders
1026
+ for ref in (self._table_node_to_ref(t),)
1027
+ if ref is not None
1028
+ ]
1029
+
990
1030
  # For each projection in the CTE, extract lineage.
991
1031
  # Only iterate projections from left branch for column names, but pass
992
1032
  # entire Union body to sg_lineage so sqlglot resolves both branches.
@@ -1010,6 +1050,48 @@ class SqlParser(ABC):
1010
1050
  )
1011
1051
  if not cte_col_name or cte_col_name == "*":
1012
1052
  continue
1053
+
1054
+ # #49 mis-bind override: detect bare (unqualified) columns
1055
+ # in a ≥2-table CTE body.
1056
+ #
1057
+ # sqlglot's sg_lineage (called with no schema and no scope)
1058
+ # re-qualifies the CTE body internally and mis-binds bare
1059
+ # columns to the last-joined table — emitting a confident
1060
+ # WRONG edge (confirmed live: bare `m` from fact_daily was
1061
+ # bound to dim_time). This is not a missing edge; it is an
1062
+ # incorrect one.
1063
+ #
1064
+ # Override: when any bare column appears inside the projection
1065
+ # expression AND the CTE body has ≥2 source tables, skip
1066
+ # sg_lineage for this projection and instead emit one
1067
+ # CTE_PROJECTION_AMBIGUOUS edge per candidate source table
1068
+ # (confidence=0.5). Over-attribution is the safe failure mode
1069
+ # for impact analysis; a wrong single edge is not acceptable.
1070
+ #
1071
+ # Single-table bodies: no ambiguity; existing path unchanged.
1072
+ # Qualified columns in any body: no bare columns; existing path.
1073
+ bare_cols_in_expr = [
1074
+ c
1075
+ for c in cte_col_expr.find_all(exp.Column)
1076
+ if not c.table # bare = no qualifier
1077
+ ]
1078
+ if bare_cols_in_expr and len(cte_source_tables) >= 2:
1079
+ # Emit one ambiguous edge per candidate source table.
1080
+ # bare_col.name is the column name to attribute;
1081
+ # for aggregates/CASE the bare col name is the leaf.
1082
+ bare_col_name = bare_cols_in_expr[0].name or cte_col_name
1083
+ dst_col_ref = ColumnRef(cte_dst_table, cte_col_name)
1084
+ for src_tbl in cte_source_tables:
1085
+ edges.append(
1086
+ LineageEdge(
1087
+ src=ColumnRef(src_tbl, bare_col_name),
1088
+ dst=dst_col_ref,
1089
+ transform="CTE_PROJECTION_AMBIGUOUS",
1090
+ confidence=0.5,
1091
+ )
1092
+ )
1093
+ continue # skip sg_lineage for this projection
1094
+
1013
1095
  try:
1014
1096
  # No schema: resolver.as_dict() {table:[cols]} triggers
1015
1097
  # sqlglot nesting-level errors on fresh string parses.
@@ -0,0 +1,234 @@
1
+ """#50 — analyze upstream/downstream case-fold integration tests (PR-2).
2
+
3
+ Confirmed live on the DWH (v1.4.0): ``analyze upstream "BA.WTFE_INKOOP_ORDER_IGDC.TA_HASH"``
4
+ returned "No results" while the lowercase form returned the full upstream table.
5
+ Root cause: ``analyze.py`` upstream/downstream and ``_bare_ref`` did not lowercase the
6
+ ``ref`` argument before querying — graph keys are lowercased at index time (C2 normalization).
7
+
8
+ Fix (PR-2): ``ref = ref.lower()`` at the top of both command functions, plus a defensive
9
+ ``ref = ref.lower()`` inside ``_bare_ref``.
10
+
11
+ These integration tests use a real DuckDB in-memory graph. They assert on the
12
+ **observable returned id sets** (not "no exception"), using a small fixture with a
13
+ real upstream chain.
14
+
15
+ Entry-point parity audit (recorded here per plan PR-2):
16
+ - CLI ``analyze.py`` upstream/downstream: fixed by this PR (were missing ``.lower()``).
17
+ - CLI ``analyze.py`` _bare_ref: defensive ``.lower()`` added by this PR.
18
+ - MCP ``tools.py``: already fully case-folded via ``_parse_column_ref`` (line ~323,
19
+ ``col_ref.lower()``) and direct ``.lower()`` calls at lines ~798, ~845, ~902, ~978,
20
+ ~1725. No MCP changes required.
21
+ - CLI ``find.py``: already folds at lines 19 and 41.
22
+ - CLI ``analyze.py`` ``impact`` (table-level): out of #50's stated scope; uses table
23
+ names not column refs. Flagged as a separate follow-up if needed.
24
+
25
+ After this PR, CLI analyze upstream/downstream and MCP are parity-correct on case folding.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ from unittest.mock import patch
31
+
32
+ import pytest
33
+ from typer.testing import CliRunner
34
+
35
+ from sqlcg.cli.commands.analyze import _bare_ref
36
+ from sqlcg.cli.main import app
37
+ from sqlcg.core.duckdb_backend import DuckDBBackend
38
+ from sqlcg.indexer.indexer import Indexer
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # Fixture SQL corpus
42
+ #
43
+ # Simple 2-hop upstream chain:
44
+ # mart.fact_enriched.m ← staging.src_raw.val
45
+ # Through a single CTE hop.
46
+ # ---------------------------------------------------------------------------
47
+
48
+ _DDL_SQL = """\
49
+ CREATE TABLE mart.fact_enriched (m NUMBER, k VARCHAR);
50
+ """
51
+
52
+ _ETL_SQL = """\
53
+ INSERT INTO mart.fact_enriched (m, k)
54
+ WITH raw AS (SELECT val AS m, key AS k FROM staging.src_raw)
55
+ SELECT m, k FROM raw;
56
+ """
57
+
58
+ runner = CliRunner()
59
+
60
+
61
+ @pytest.fixture
62
+ def db():
63
+ """Fresh in-memory DuckDB backend with schema initialised."""
64
+ backend = DuckDBBackend(":memory:")
65
+ backend.init_schema()
66
+ yield backend
67
+ backend.close()
68
+
69
+
70
+ @pytest.fixture
71
+ def indexed_db(db, tmp_path):
72
+ """Index the fixture corpus; return the backend."""
73
+ (tmp_path / "ddl.sql").write_text(_DDL_SQL)
74
+ (tmp_path / "etl.sql").write_text(_ETL_SQL)
75
+ Indexer().index_repo(tmp_path, dialect=None, db=db, use_git=False)
76
+ return db
77
+
78
+
79
+ # ---------------------------------------------------------------------------
80
+ # Helpers
81
+ # ---------------------------------------------------------------------------
82
+
83
+
84
+ def _run_upstream(db: DuckDBBackend, ref: str) -> list[dict]:
85
+ """Invoke analyze upstream via Typer, routing run_read_routed to the in-memory db.
86
+
87
+ Patches run_read_routed to call db.run_read directly, so we exercise the
88
+ real SQL against the indexed in-memory graph — while the command function's
89
+ ``ref = ref.lower()`` case-fold is in effect.
90
+ """
91
+ from sqlcg.cli.commands.analyze import _upstream_sql
92
+
93
+ def _route(sql: str, params: dict, db_path=None) -> list[dict]:
94
+ return db.run_read(sql, params)
95
+
96
+ with patch("sqlcg.cli.commands.analyze.run_read_routed", side_effect=_route):
97
+ with patch("sqlcg.server.noise_filter.NoiseFilter.from_config") as mock_nf:
98
+ nf = mock_nf.return_value
99
+ nf.is_noise.return_value = False
100
+ runner.invoke(app, ["analyze", "upstream", ref])
101
+
102
+ # Return raw rows from the DB using the lowercased ref (the command path result).
103
+ query = _upstream_sql(5, include_intermediate=False)
104
+ return db.run_read(query, {"ref": ref.lower()})
105
+
106
+
107
+ def _run_downstream(db: DuckDBBackend, ref: str) -> list[dict]:
108
+ """Invoke analyze downstream via Typer, routing run_read_routed to the in-memory db."""
109
+ from sqlcg.cli.commands.analyze import _downstream_sql
110
+
111
+ def _route(sql: str, params: dict, db_path=None) -> list[dict]:
112
+ return db.run_read(sql, params)
113
+
114
+ with patch("sqlcg.cli.commands.analyze.run_read_routed", side_effect=_route):
115
+ with patch("sqlcg.server.noise_filter.NoiseFilter.from_config") as mock_nf:
116
+ nf = mock_nf.return_value
117
+ nf.is_noise.return_value = False
118
+ runner.invoke(app, ["analyze", "downstream", ref])
119
+
120
+ query = _downstream_sql(5, include_intermediate=False)
121
+ return db.run_read(query, {"ref": ref.lower()})
122
+
123
+
124
+ # ---------------------------------------------------------------------------
125
+ # Tests — upstream case-fold (real graph, observable ids)
126
+ # ---------------------------------------------------------------------------
127
+
128
+
129
+ def test_uppercase_upstream_anchor_returns_same_ids_as_lowercase(indexed_db):
130
+ """UPPERCASE anchor returns the identical non-empty upstream id set as lowercase.
131
+
132
+ This is the confirmed DWH regression: uppercase ref passed to run_read_routed
133
+ found nothing because graph keys are stored lowercase. After the fix
134
+ (ref = ref.lower() at top of upstream()), the fold happens before the query.
135
+
136
+ We test observably: both forms produce the same non-empty id set.
137
+ """
138
+ lowercase_rows = _run_upstream(indexed_db, "mart.fact_enriched.m")
139
+ uppercase_rows = _run_upstream(indexed_db, "MART.FACT_ENRICHED.M")
140
+
141
+ lowercase_ids = {r["id"] for r in lowercase_rows}
142
+ uppercase_ids = {r["id"] for r in uppercase_rows}
143
+
144
+ assert lowercase_ids, (
145
+ "Baseline lowercase upstream returned no results — fixture or indexer issue."
146
+ )
147
+ assert uppercase_ids, (
148
+ "UPPERCASE upstream returned no results while lowercase returned results — "
149
+ "the case-fold fix (ref = ref.lower()) is missing from analyze.upstream()."
150
+ )
151
+ assert uppercase_ids == lowercase_ids, (
152
+ f"UPPERCASE and lowercase upstream returned different id sets.\n"
153
+ f" lowercase: {sorted(lowercase_ids)}\n"
154
+ f" uppercase: {sorted(uppercase_ids)}"
155
+ )
156
+ # Both must contain the physical source column from the fixture.
157
+ assert "staging.src_raw.val" in lowercase_ids, (
158
+ f"Expected staging.src_raw.val in upstream ids.\nGot: {sorted(lowercase_ids)}"
159
+ )
160
+
161
+
162
+ def test_mixedcase_upstream_anchor_returns_same_ids_as_lowercase(indexed_db):
163
+ """Mixed-case anchor (e.g. 'Mart.Fact_Enriched.M') returns the same upstream ids."""
164
+ lowercase_rows = _run_upstream(indexed_db, "mart.fact_enriched.m")
165
+ mixedcase_rows = _run_upstream(indexed_db, "Mart.Fact_Enriched.M")
166
+
167
+ lowercase_ids = {r["id"] for r in lowercase_rows}
168
+ mixedcase_ids = {r["id"] for r in mixedcase_rows}
169
+
170
+ assert mixedcase_ids, "Mixed-case upstream returned no results — the case-fold fix is missing."
171
+ assert mixedcase_ids == lowercase_ids, (
172
+ f"Mixed-case and lowercase upstream returned different id sets.\n"
173
+ f" lowercase: {sorted(lowercase_ids)}\n"
174
+ f" mixed-case: {sorted(mixedcase_ids)}"
175
+ )
176
+
177
+
178
+ # ---------------------------------------------------------------------------
179
+ # Tests — downstream case-fold (real graph, observable ids)
180
+ # ---------------------------------------------------------------------------
181
+
182
+
183
+ def test_uppercase_downstream_anchor_returns_same_ids_as_lowercase(indexed_db):
184
+ """UPPERCASE anchor returns the identical non-empty downstream id set as lowercase."""
185
+ lowercase_rows = _run_downstream(indexed_db, "staging.src_raw.val")
186
+ uppercase_rows = _run_downstream(indexed_db, "STAGING.SRC_RAW.VAL")
187
+
188
+ lowercase_ids = {r["id"] for r in lowercase_rows}
189
+ uppercase_ids = {r["id"] for r in uppercase_rows}
190
+
191
+ assert lowercase_ids, (
192
+ "Baseline lowercase downstream returned no results — fixture or indexer issue."
193
+ )
194
+ assert uppercase_ids, (
195
+ "UPPERCASE downstream returned no results while lowercase returned results — "
196
+ "the case-fold fix (ref = ref.lower()) is missing from analyze.downstream()."
197
+ )
198
+ assert uppercase_ids == lowercase_ids, (
199
+ f"UPPERCASE and lowercase downstream returned different id sets.\n"
200
+ f" lowercase: {sorted(lowercase_ids)}\n"
201
+ f" uppercase: {sorted(uppercase_ids)}"
202
+ )
203
+ assert "mart.fact_enriched.m" in lowercase_ids, (
204
+ f"Expected mart.fact_enriched.m in downstream ids.\nGot: {sorted(lowercase_ids)}"
205
+ )
206
+
207
+
208
+ # ---------------------------------------------------------------------------
209
+ # Tests — _bare_ref defensive lower
210
+ # ---------------------------------------------------------------------------
211
+
212
+
213
+ def test_bare_ref_lowercases_3part_uppercase_ref():
214
+ """_bare_ref applied to a 3-part UPPERCASE ref returns a lowercase bare ref.
215
+
216
+ The defensive ``ref = ref.lower()`` inside ``_bare_ref`` ensures the helper is
217
+ safe to call independently of the caller's folding, matching graph key casing.
218
+ """
219
+ result = _bare_ref("BA.WTFE_INKOOP_ORDER_IGDC.TA_HASH")
220
+ assert result == "wtfe_inkoop_order_igdc.ta_hash", (
221
+ f"_bare_ref did not lowercase: got '{result}'"
222
+ )
223
+
224
+
225
+ def test_bare_ref_lowercases_mixedcase_ref():
226
+ """_bare_ref on a mixed-case 3-part ref returns a lowercase bare ref."""
227
+ result = _bare_ref("Mart.Fact_Enriched.M")
228
+ assert result == "fact_enriched.m", f"_bare_ref did not lowercase: got '{result}'"
229
+
230
+
231
+ def test_bare_ref_already_lowercase_is_unchanged():
232
+ """_bare_ref on an already-lowercase ref is idempotent."""
233
+ result = _bare_ref("mart.fact_enriched.m")
234
+ assert result == "fact_enriched.m"