sql-code-graph 1.1.3__tar.gz → 1.2.2__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.
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/ARCHITECTURE_REVIEW.md +198 -1
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/CLAUDE.md +21 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/PKG-INFO +11 -1
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/README.md +10 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/docs/cli.md +98 -8
- sql_code_graph-1.2.2/plan/fix_downstream_sink_location.md +119 -0
- sql_code_graph-1.2.2/plan/v1_2_0_read_proxy.md +597 -0
- sql_code_graph-1.2.2/plan/v1_2_1_bugfix.md +669 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/pyproject.toml +1 -1
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/__init__.py +1 -1
- sql_code_graph-1.2.2/src/sqlcg/cli/commands/analyze.py +327 -0
- sql_code_graph-1.2.2/src/sqlcg/cli/commands/db.py +187 -0
- sql_code_graph-1.2.2/src/sqlcg/cli/commands/find.py +78 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/cli/commands/gain.py +13 -11
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/core/config.py +15 -13
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/indexer/indexer.py +91 -11
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/lineage/aggregator.py +17 -45
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/parsers/ansi_parser.py +2 -2
- sql_code_graph-1.2.2/src/sqlcg/server/read_client.py +192 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/server/server.py +97 -18
- sql_code_graph-1.2.2/tests/integration/test_cross_file_lineage.py +125 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_indexer_commits.py +7 -2
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_pr3_kind_tagging.py +24 -19
- sql_code_graph-1.2.2/tests/integration/test_read_via_server.py +229 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_readonly_under_lock.py +31 -22
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_star_resolution.py +10 -2
- sql_code_graph-1.2.2/tests/integration/test_user_surface_recall_guard.py +455 -0
- sql_code_graph-1.2.2/tests/unit/test_aggregator.py +68 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_aggregator_skip.py +21 -30
- sql_code_graph-1.2.2/tests/unit/test_canonical_target_resolution.py +270 -0
- sql_code_graph-1.2.2/tests/unit/test_db_info.py +142 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_find_cmd.py +19 -28
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_freshness_helper.py +20 -21
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_gain_ratio.py +5 -12
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_hygiene_config_warning.py +2 -4
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_noise_filter.py +8 -2
- sql_code_graph-1.2.2/tests/unit/test_read_client.py +234 -0
- sql_code_graph-1.2.2/tests/unit/test_resolve_pass2_passes_dependency_filter.py +174 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_star_schema_unit.py +26 -25
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_unqualified_fallback.py +9 -24
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/uv.lock +1 -1
- sql_code_graph-1.1.3/src/sqlcg/cli/commands/analyze.py +0 -325
- sql_code_graph-1.1.3/src/sqlcg/cli/commands/db.py +0 -181
- sql_code_graph-1.1.3/src/sqlcg/cli/commands/find.py +0 -81
- sql_code_graph-1.1.3/tests/integration/test_cross_file_lineage.py +0 -129
- sql_code_graph-1.1.3/tests/unit/test_aggregator.py +0 -55
- sql_code_graph-1.1.3/tests/unit/test_db_info.py +0 -173
- sql_code_graph-1.1.3/tests/unit/test_resolve_pass2_passes_dependency_filter.py +0 -149
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/.claude/agents/api-documenter.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/.claude/agents/architect-planner.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/.claude/agents/architect-reviewer.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/.claude/agents/code-reviewer.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/.claude/agents/developer.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/.claude/agents/plan-reviewer.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/.claude/agents/sprint-planner.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/.github/workflows/benchmark.yml +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/.github/workflows/e2e-tests.yml +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/.github/workflows/release.yml +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/.github/workflows/test.yml +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/.gitignore +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/.pre-commit-config.yaml +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/.sqlcgignore +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/CHANGELOG.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/docs/AIRBNB_PARSE_REPORT.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/e2e_firstuser_report.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/e2e_run_20260528_101413.output +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/main.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/WORKFLOW.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/blueprint.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/bundle_claude_skill.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/feature_34_unused_presentation_segregation.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/feature_35_external_downstream_injection.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/feature_F2_bundle_claude_skill.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/fix_dynamic_table_parsing.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/fix_expand_qualify_perf.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/fix_firstuser_findings.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/fix_schema_case_mismatch.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/hygiene_config_path_and_survivors.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/investigation_e5_e4.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/living_codebase_resync.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/measurements/schema_comparison_with_schema.json +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/measurements/schema_comparison_without_schema.json +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/measurements/sprint_08_changelogs_fullindex.json +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/measurements/sprint_08_fullcorpus_index.json +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/measurements/sprint_pool_300s_plan.json +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/parse_diagnostics.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/parsing_errors_experiment.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/snowflake_en_test_suite.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sprint_01.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sprint_01_deployment_pypi.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sprint_02.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sprint_02_v0.3.0_core.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sprint_03.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sprint_03_v0.3.1_postmortem.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sprint_04_column_lineage.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sprint_04_column_lineage_fix.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sprint_05_star_resolution.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sprint_06_lineage_coverage.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sprint_07_open_ecodes.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sprint_07_perf_and_live_test.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sprint_08_perf_upsert.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sprint_09_lineage_coverage.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sprint_10_anchor_tools.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sprint_11_v1.0.2_bugfix.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sprint_12_v1.1.0.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sprint_13_v1.1.0_cluster_b.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sprint_3.1_postmortem.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/sqlcg.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/trust_layer.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/v1.1.0_cluster_b_provenance_trust.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/v1.1.0_live_graph_freshness.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/v1.1.1_batch_upsert_perf.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/v1_1_2_bugfix.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/plan/v1_1_3_union_cte_star.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/profile.html +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/pyrightconfig.json +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/scripts/collect_parse_errors.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/scripts/generate_cli_docs.sh +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/__main__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/cli/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/cli/commands/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/cli/commands/git.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/cli/commands/index.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/cli/commands/install.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/cli/commands/mcp.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/cli/commands/reindex.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/cli/commands/report.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/cli/commands/uninstall.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/cli/commands/watch.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/cli/main.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/core/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/core/freshness.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/core/graph_db.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/core/jobs.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/core/kuzu_backend.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/core/neo4j_backend.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/core/queries.cypher +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/core/queries.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/core/schema.cypher +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/core/schema.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/indexer/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/indexer/dbt_adapter.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/indexer/error_classify.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/indexer/git_delta.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/indexer/pool.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/indexer/walker.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/indexer/watcher.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/lineage/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/lineage/schema_resolver.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/metrics/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/metrics/store.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/parsers/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/parsers/base.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/parsers/bigquery_parser.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/parsers/postgres_parser.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/parsers/registry.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/parsers/snowflake_parser.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/parsers/tsql_parser.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/server/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/server/control.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/server/exceptions.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/server/models.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/server/noise_filter.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/server/skill.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/server/tools.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/utils/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/utils/hashing.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/utils/ignore.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/src/sqlcg/utils/logging.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/adversarial/200_join.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/adversarial/500_union.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/bench_indexer.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/conftest.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/golden_corpus/snowflake/case_normalization.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/golden_corpus/snowflake/colon_cast.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/golden_corpus/snowflake/colon_reserved_word.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/golden_corpus/snowflake/copy_into.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/golden_corpus/snowflake/create_procedure.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/golden_corpus/snowflake/identifier_dynamic.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/golden_corpus/snowflake/lateral_flatten.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/golden_corpus/snowflake/qualify.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/golden_corpus/snowflake/scripting_block.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/golden_corpus/snowflake/three_part.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/tpch/q01.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/tpch/q02.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/tpch/q03.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/tpch/q04.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/benchmarks/tpch/q05.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/e2e/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/e2e/conftest.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/e2e/test_F2_skill_install_e2e.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/e2e/test_airbnb_e2e.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/e2e/test_cli_index.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/e2e/test_git_hook_install.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/e2e/test_golden_lineage.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/e2e/test_mcp_lifecycle.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/e2e/test_mcp_tools.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/e2e/test_parse_diagnostics_cli.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/e2e/test_star_resolution_e2e.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/e2e/test_watch.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/airbnb/dim_hosts_cleansed.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/airbnb/dim_listings_cleansed.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/airbnb/fct_reviews.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/airbnb/mart_fullmoon_reviews.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/airbnb/raw_hosts.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/airbnb/raw_listings.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/airbnb/raw_reviews.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/airbnb/src_hosts.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/airbnb/src_listings.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/airbnb/src_reviews.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/bigquery/.gitkeep +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/jaffle_shop/customers.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/jaffle_shop/orders.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/jaffle_shop/raw_orders.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/snowflake/base_tables.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/snowflake/reports.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/snowflake/views.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/star_corpus/ddl_src.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/star_corpus/ddl_tgt.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/star_corpus/etl_alias_star.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/star_corpus/etl_star.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/synthetic/base_tables.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/synthetic/reports.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/fixtures/synthetic/views.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/snowflake/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/snowflake/test_insert_select.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_T34_presentation_segregation.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_T35_external_consumers.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_anchor_tools.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_bulk_upsert.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_column_lineage_e2e.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_cte_recall_guard.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_cte_schema_alias_guard.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_cte_source_node_invariant.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_dialect_matrix.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_e36_xfile_regression_guard.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_freshness_mcp.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_hygiene_config_root_reconciliation.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_indexer_batching.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_indexer_to_graph.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_live_anchors.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_parse_diagnostics.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_pr1_confidence_reason.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_pr2_source_location.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_reindex_via_server.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_resync.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_temp_table_lineage.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/integration/test_union_cte_star_recall_guard.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/perf/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/perf/test_perf.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E10/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E11/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E12/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E12/e12_json_path.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E12/e12_lateral_flatten.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E12/test_e12.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E13/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E14/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E15/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E16/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E16/e16_merge.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E16/e16_merge_delete.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E16/test_e16.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E17/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E18/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E18/e18_decode.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E18/e18_iff_decode.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E18/e18_nvl2.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E18/test_e18.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E19/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E2/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E2/e2_expr_alias.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E2/e2_function_alias.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E2/e2_multiply_alias.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E2/test_e2.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E20/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E21/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E21/e21_alias_forward_ref.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E21/e21_three_level_chain.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E21/test_e21.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E22/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E23/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E23/e23_stored_proc.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E23/e23_stored_proc_multi.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E23/test_e23.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E24/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E25/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E25/e25_cross_db.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E25/e25_two_part.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E25/test_e25.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E25/test_e25_full_id.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E26/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E27/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E27/e27_nested_udf.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E27/e27_udf.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E27/test_e27.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E28/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E29/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E3/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E3/e3_alter_table.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E3/e3_create_sequence.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E3/e3_ddl_only.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E3/test_e3.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E30/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E31/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E32/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E33/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E34/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E35/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E36/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E36/e36_temp_multi_use.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E36/e36_temp_table.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E36/test_e36.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E36/test_e36_xfile.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E37/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E38/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E4/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E4/e4_execute_immediate.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E4/e4_if_not_exists.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E4/e4_unexpected_token.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E4/e4_unpivot.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E4/test_e4.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E5/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E5/e5_cte_missing_source.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E5/e5_multi_cte.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E5/e5_nested_cte.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E5/test_e5.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E5/test_e5_cte_projection.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E8/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E8/e8_dynamic_sources.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E8/e8_seq_nextval.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E8/e8_uuid.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E8/test_e8.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E9/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E_aggregates/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E_aggregates/fixture_sum_absent_cross_schema.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E_aggregates/fixture_sum_case_when.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E_aggregates/fixture_sum_present_source.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E_aggregates/test_e_aggregates.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E_date_functions/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E_date_functions/fixture_date_aliased.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E_date_functions/fixture_date_unaliased.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E_date_functions/fixture_datediff_unaliased.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E_date_functions/fixture_year_unaliased.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/E_date_functions/test_e_date_functions.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/README.md +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/anchors/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/anchors/fixture_etl.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/anchors/fixture_omloopsnelheid.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/anchors/fixture_semantic.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/anchors/fixture_source.sql +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/anchors/test_anchor_ma_aantal_op_order.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/anchors/test_anchor_omloopsnelheid.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/conftest.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/snowflake/test_plan_review_gates.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/snowflake/__init__.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/snowflake/test_scripting_noise.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_F2_install_skill.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_F2_skill_render.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_F2_uninstall_skill.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_T09_01_qualify_once.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_T09_02_ddl_skip.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_T09_04_subprocess_isolate.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_T09_06_log_verbosity.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_T35_config_external_consumers.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_base_parser.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_branch_monitor.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_bulk_upsert_invariant.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_cli.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_cli_help.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_closure_depth.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_column_lineage_wiring.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_config.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_data_models.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_dominant_cause.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_firstuser_findings.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_git_delta.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_git_hooks.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_git_hooks_notify.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_graph_backend.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_hard_kill_pool.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_include_working_tree.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_index_cmd.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_index_flags.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_index_progress.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_indexer_progress.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_indexer_quality.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_install.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_install_message.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_jobs.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_judgement.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_kuzu_backend.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_kuzu_lock.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_lineage_conversion.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_literal_column_skip.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_mcp_best_practices.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_mcp_control.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_mcp_stdio_smoke.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_metrics.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_parse_file_dependency_filter.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_parse_quality.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_parser.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_perf_scaling_guard.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_pr07_observability.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_queries_loader.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_schema_resolver.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_server.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_snowflake_strip_alter_set_tag.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_sprint_06_t04_t05.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_star_resolution_unit.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_submit_feedback.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_subprocess_isolate.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_t02_expression_name_extraction.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_t03_ddl_skip.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_timeout_cancel.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_tools_hints.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_tools_warnings.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_uninstall.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_upsert_batch_invariant.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_walker.py +0 -0
- {sql_code_graph-1.1.3 → sql_code_graph-1.2.2}/tests/unit/test_watcher.py +0 -0
|
@@ -478,7 +478,15 @@ both concrete backends override it — the blueprint does not enforce this.
|
|
|
478
478
|
Risk: HIGH. A failed re-index leaves orphaned graph nodes that produce incorrect
|
|
479
479
|
lineage results until the next full re-index.
|
|
480
480
|
|
|
481
|
-
### 3.2 [HIGH] `CrossFileAggregator.resolve_pass2` re-opens files without error handling
|
|
481
|
+
### 3.2 ~~[HIGH]~~ **RESOLVED (v1.2.1)** `CrossFileAggregator.resolve_pass2` re-opens files without error handling
|
|
482
|
+
|
|
483
|
+
**Resolution (2026-06-02, v1.2.1, PR #48):** `resolve_pass2` was deleted entirely. It had
|
|
484
|
+
zero production call sites — the production pass-2 path in `index_repo` reimplements the
|
|
485
|
+
dep-filter + reparse logic inline (worker calls `parse_file(..., dependency_filter=...)`
|
|
486
|
+
in [`pool.py`](src/sqlcg/indexer/pool.py)), and that path already handles file-read errors
|
|
487
|
+
defensively. The dead method (and the four tests that exercised it) were the same
|
|
488
|
+
test-measures-a-dead-path blind-spot class as #40. Tests realigned to drive `index_repo`
|
|
489
|
+
end-to-end. See [`v1_2_1_bugfix.md`](plan/v1_2_1_bugfix.md) Phase 5.
|
|
482
490
|
|
|
483
491
|
```python
|
|
484
492
|
def resolve_pass2(self, parser, parsed):
|
|
@@ -2947,4 +2955,193 @@ editor owns the stdio process lifecycle and v1.1 has no launcher to re-spawn it
|
|
|
2947
2955
|
owner-only, but any same-uid local process can send `stop`/`reindex`).
|
|
2948
2956
|
- **Freshness on every tool result by default** — v1.1 ships it gated/off to
|
|
2949
2957
|
protect hot-path latency; default-on pending an overhead measurement.
|
|
2958
|
+
|
|
2959
|
+
## 18. v1.2.1 — Lineage recall guards + canonical INSERT target + CTE filter fixes
|
|
2960
|
+
|
|
2961
|
+
Review date: 2026-06-02 · Reconciled by: architect-planner · Branch:
|
|
2962
|
+
`feat/v1.2.1-bugfix` · PR #48 · Verdict: **PLAN-COMPLIANT (PASS)**
|
|
2963
|
+
|
|
2964
|
+
> **Bundled release note (2026-06-02):** the v1.2.1 bugfix scope (this section) and the
|
|
2965
|
+
> downstream-sink `file:line` fix (§19.2) ship as a **single `v1.2.2` release** — the
|
|
2966
|
+
> lower `v1.2.1` tag is skipped (precedent: v1.1.2→v1.1.3). Both change sets live on
|
|
2967
|
+
> `feat/v1.2.1-bugfix` (PR #48, fast-forwarded to `b5bf7a3`); version is `1.2.2`. The
|
|
2968
|
+
> combined plan-compliance verdict (PASS, both scopes) is recorded in
|
|
2969
|
+
> [`v1_2_1_bugfix.md`](plan/v1_2_1_bugfix.md) → "Bundled v1.2.2 release compliance".
|
|
2970
|
+
|
|
2971
|
+
Patch release fixing the residual user-facing lineage defects (#44 canonical-name
|
|
2972
|
+
split, #45 `file:line=?` + `cte_*` leak), repairing the v1.2.0 read-proxy filter
|
|
2973
|
+
regression (the #39 read-half), deleting the dead `resolve_pass2` (resolves §3.2),
|
|
2974
|
+
and landing the #40 user-surface regression-guard suite. Plan:
|
|
2975
|
+
[`v1_2_1_bugfix.md`](plan/v1_2_1_bugfix.md) (full phase-by-phase compliance log appended there).
|
|
2976
|
+
|
|
2977
|
+
### 18.1 #44 — canonical INSERT-target resolution
|
|
2978
|
+
|
|
2979
|
+
A new `CrossFileAggregator.canonical_by_bare` index (bare name → sole DDL `full_id`)
|
|
2980
|
+
plus `_ambiguous_bare` (names defined in >1 schema, never rewritten) is built in
|
|
2981
|
+
`register_pass1` from DDL tables only. It is threaded `index_repo` →
|
|
2982
|
+
`_upsert_file_batch` → `_build_file_rows`, where an unqualified / non-DDL INSERT
|
|
2983
|
+
target whose bare name resolves to a sole DDL canonical is rewritten to share that
|
|
2984
|
+
node's identity (`qualified` == DDL `full_id`, all fields derived from the canonical),
|
|
2985
|
+
keeping `kind:"table"`. The fix lands in the node-emission loop, **not** the deleted
|
|
2986
|
+
`resolve_pass2` and **not** the per-column lineage hot path — pure row-dict mutation,
|
|
2987
|
+
no new `execute()`. Degrades to a no-op when `canonical_by_bare is None`
|
|
2988
|
+
(single-file `reindex_file` / `resync_changed` paths) — known limitation, re-index is
|
|
2989
|
+
the migration path per CLAUDE.md.
|
|
2990
|
+
|
|
2991
|
+
### 18.2 #45 — `file:line` recall + structural `cte_*` suppression
|
|
2992
|
+
|
|
2993
|
+
- **#45.1**: `analyze upstream/downstream` now bind location from the source's
|
|
2994
|
+
*outgoing* `COLUMN_LINEAGE` edge and aggregate `WITH … min(start_line), min(file_path)`
|
|
2995
|
+
**before** `LIMIT 100`, so multi-hop sources carry a real `file:line` and the limit
|
|
2996
|
+
counts distinct sources, not edge-fanout rows.
|
|
2997
|
+
- **#45.2**: chosen mechanism is **graph-truth, not a name heuristic** — unresolved
|
|
2998
|
+
synthetic INSERT targets are emitted `kind:"derived"` (the #44 emission change), and
|
|
2999
|
+
the restored `kind IN ['table','external']` read filter excludes them. No name-prefix
|
|
3000
|
+
belt was needed.
|
|
3001
|
+
|
|
3002
|
+
### 18.3 Two implementation invariants discovered during the fix
|
|
3003
|
+
|
|
3004
|
+
Both are now load-bearing; recorded so a future refactor does not silently undo them.
|
|
3005
|
+
|
|
3006
|
+
1. **KuzuDB: a `WHERE` clause inside `OPTIONAL MATCH` filters the match attempt, not
|
|
3007
|
+
the surrounding row.** To filter rows on an optionally-matched node's property, an
|
|
3008
|
+
explicit `WITH <carried>, t WHERE …` must follow. `analyze._kind_filter` uses this
|
|
3009
|
+
form (`OPTIONAL MATCH (t:SqlTable {…}) WITH <alias>, t WHERE t.kind IS NULL OR …`).
|
|
3010
|
+
The `IS NULL` branch is intentional: it keeps node-less physical sources (the #39
|
|
3011
|
+
tail) while CTE/derived intermediates (`kind='cte'`/`'derived'`) are excluded.
|
|
3012
|
+
Guarded by TC1/TC4 in [`test_user_surface_recall_guard.py`](tests/integration/test_user_surface_recall_guard.py).
|
|
3013
|
+
2. **`_flush_row_batch` dedup must prefer structural kinds over the default `'table'`.**
|
|
3014
|
+
A CTE alias is emitted twice in a batch — first `kind='table'` (incidental source
|
|
3015
|
+
reference) then `kind='cte'` (CTE destination). The pre-existing first-seen-wins
|
|
3016
|
+
dedup kept the wrong one, defeating #45.2. New Rule 2: `{'cte','derived','external'}`
|
|
3017
|
+
beats default `'table'` (Rule 1 — DDL provenance via `defined_in_file` — still takes
|
|
3018
|
+
precedence). This logic lives in the documented `_flush_row_batch` perf hot path but
|
|
3019
|
+
is pure dict comparison in the existing dedup loop — no change to flush cardinality,
|
|
3020
|
+
batch/scaling guards stay green (CLAUDE.md performance invariants intact).
|
|
3021
|
+
|
|
3022
|
+
### 18.4 Resolved / out-of-scope
|
|
3023
|
+
|
|
3024
|
+
- **§3.2 `resolve_pass2`** — RESOLVED (deleted; see §3.2 update).
|
|
3025
|
+
- **Residual #38 (UNION-ALL sibling CTEs)** — already fixed in 1.1.3; live DWH
|
|
3026
|
+
re-verification (80→47 `cte_insert` islands) recorded in the plan; the surviving 47
|
|
3027
|
+
are likely legitimately source-less computed measures, not a regression. Shape B of
|
|
3028
|
+
the #40 guard now asserts a computed-measure projection so a real gap there reds the guard.
|
|
3029
|
+
- **#28 server read-only-by-default + reindex escalation** — out of 1.2.1; recommended
|
|
3030
|
+
as a separate minor (1.3.0). The read half is already handled by the v1.2.0 routing proxy.
|
|
2950
3031
|
- **`connected_clients` fidelity** — best-effort `1` today; no real counter.
|
|
3032
|
+
|
|
3033
|
+
## 19. v1.2.1 live DWH re-verification — two residual findings (2026-06-02)
|
|
3034
|
+
|
|
3035
|
+
Source: live verification of the v1.2.1 fixes against the full DWH corpus
|
|
3036
|
+
(`../dwh`, 1,335 SQL files, clean index ~1:56). **The v1.2.1 fixes themselves
|
|
3037
|
+
PASSED** — both findings below are residual / design-limit behaviour surfaced by
|
|
3038
|
+
the run, not regressions in PR #48. Corpus graph snapshot (kind-typed `SqlTable`):
|
|
3039
|
+
2,222 `table` / 383 `cte` / 272 `derived`.
|
|
3040
|
+
|
|
3041
|
+
### 19.1 [KEEP — LOW] Residual scratch-object leak into the default `analyze` surface
|
|
3042
|
+
|
|
3043
|
+
**Measurement.** The #45.2 fix essentially closed the `cte_` leak it targeted:
|
|
3044
|
+
of nodes still typed `kind='table'`, only 3 carry a `cte_` prefix (vs 383 now
|
|
3045
|
+
correctly `kind='cte'`). The broader leak persists: **495 of 2,222 `table`-kind
|
|
3046
|
+
nodes (~22%) carry scratch-object name prefixes** — 471 `tmp_*`, 21 `temp_*`,
|
|
3047
|
+
3 `cte_*` — plus ~575 unqualified single-token nodes (a mix of real bare tables
|
|
3048
|
+
and CTE aliases). These surface in default (filtered) `analyze` output because the
|
|
3049
|
+
filter is correctly kind-based (graph-truth) but these aliases were never classified
|
|
3050
|
+
`cte`/`derived` at parse/aggregation time. This is the known #38 islands residual
|
|
3051
|
+
(s18.4 / `47 cte_insert islands`); the leak is now `tmp_*`-dominated, not
|
|
3052
|
+
`cte_*`-dominated. See memory `project_issue38_cte_filter_regression`.
|
|
3053
|
+
|
|
3054
|
+
**Decision: KEEP as a future ticket, LOW priority.** Rationale tied to existing
|
|
3055
|
+
decisions:
|
|
3056
|
+
- The standing **"no name-prefix filtering"** decision (s18.2; CLAUDE.md) is correct
|
|
3057
|
+
and stays. The fix therefore **cannot** live in the filter — it must happen at
|
|
3058
|
+
**classification time** in the aggregator/parser, exactly like the #44 synthetic
|
|
3059
|
+
INSERT-target → `kind='derived'` emission change (s18.1). A `CREATE [OR REPLACE]
|
|
3060
|
+
TEMP/TRANSIENT TABLE tmp_x AS SELECT …` whose target is a scratch object should be
|
|
3061
|
+
emitted `kind='derived'` at `_build_file_rows` time, the same place #44 already
|
|
3062
|
+
rewrites node identity. 492/495 of the leak (`tmp_*`/`temp_*`) are this CTAS-into-
|
|
3063
|
+
scratch shape — addressable by classifying on the parsed CTAS-into-temp statement
|
|
3064
|
+
kind (DDL-derived structural truth), **not** on the name prefix.
|
|
3065
|
+
- **Non-goal for the ticket:** the ~575 unqualified single-token nodes. They are a
|
|
3066
|
+
genuine mix of real bare tables and CTE aliases; auto-classifying them risks
|
|
3067
|
+
hiding real physical tables from the default surface (a false-negative worse than
|
|
3068
|
+
the current cosmetic leak). They stay `kind='table'` until a structural signal
|
|
3069
|
+
(CTE membership, CTAS target) is available — never a name guess.
|
|
3070
|
+
|
|
3071
|
+
**Risk/reward.** Reward: cleaner default `analyze` surface, ~22% fewer scratch
|
|
3072
|
+
intermediates leaking; closes the visible tail of #38. Risk: medium — must classify
|
|
3073
|
+
on parsed statement structure (TEMP/TRANSIENT CTAS target), and must not reclassify
|
|
3074
|
+
a real `tmp_`-named physical table that is read by a non-CTAS statement; needs a guard
|
|
3075
|
+
asserting a real table named `tmp_*` is still surfaced. Effort: medium (one emission
|
|
3076
|
+
site, reuses #44 plumbing, plus a behavioural guard). Below the open HIGH §3.x items
|
|
3077
|
+
and Finding 19.2 in the ranking. Recorded here; no plan stub drafted yet (pull when
|
|
3078
|
+
a lineage-coverage sprint next touches `_build_file_rows`).
|
|
3079
|
+
|
|
3080
|
+
### 19.2 [RESOLVED in v1.2.2] `file:line='?'` on downstream terminal sinks
|
|
3081
|
+
|
|
3082
|
+
**Measurement.** The #45.1 fix rebinds a result's location from its **outgoing**
|
|
3083
|
+
`COLUMN_LINEAGE` edge ("where is this column used as a source"). For *downstream*
|
|
3084
|
+
traces this means terminal sinks render `?`: a sink has no outgoing edge, so
|
|
3085
|
+
`min(q.start_line)`/`min(q.file_path)` are NULL. Corpus measurement: of 36,892
|
|
3086
|
+
distinct columns reachable as a downstream result, **29,827 (81%) are sinks → render
|
|
3087
|
+
`file:line='?'`**. The fix populates only the locatable 19%. Upstream is unaffected
|
|
3088
|
+
(an upstream source legitimately has an outgoing edge). The TC6 guard asserts non-null
|
|
3089
|
+
`file:line` on one specific *located* case, so it passed despite the 81%.
|
|
3090
|
+
|
|
3091
|
+
**Decision: KEEP — pick up next.** This is a genuine bug, **not** deliberate
|
|
3092
|
+
semantics. Analysis tied to #45.1's own intent:
|
|
3093
|
+
- #45.1 chose the outgoing edge so a *source*'s location reads as "where it is
|
|
3094
|
+
produced/consumed as a source" — correct for the **upstream** direction. The same
|
|
3095
|
+
pattern was copied verbatim into the **downstream** query
|
|
3096
|
+
([`analyze.py`](src/sqlcg/cli/commands/analyze.py) lines 134–137, fallback 146–149):
|
|
3097
|
+
`OPTIONAL MATCH (dst)-[dstedge]->()`. For a downstream *result*, the natural,
|
|
3098
|
+
user-meaningful location is the query that **produced** the column — i.e. the
|
|
3099
|
+
**incoming** edge `()-[dstedge]->(dst)`, not the outgoing one.
|
|
3100
|
+
- **Key lever (confirmed in code):** every one of the 36,892 columns has an incoming
|
|
3101
|
+
`COLUMN_LINEAGE` edge by definition — that is how it became a downstream result —
|
|
3102
|
+
and that producing query carries a real `start_line`/`file_path`. Flipping the two
|
|
3103
|
+
downstream `OPTIONAL MATCH` arrows from `(dst)-[dstedge]->()` to
|
|
3104
|
+
`()-[dstedge]->(dst)` binds location from the producing query, turning ~81% of `?`
|
|
3105
|
+
rows into a real `file:line`. Upstream stays on the outgoing edge unchanged.
|
|
3106
|
+
|
|
3107
|
+
**Risk/reward.** Reward: HIGH — ~81% of downstream result rows flip from `?` to a
|
|
3108
|
+
real `file:line` on a corpus this shape. Effort: LOW — a ~2-line arrow flip in two
|
|
3109
|
+
downstream queries (primary + bare-name fallback), no schema/index/perf change, no
|
|
3110
|
+
parser change. Risk: LOW and bounded — only the downstream direction; upstream and
|
|
3111
|
+
the kind-filter (s18.3) untouched. The one trap: TC6 passes today on a located case,
|
|
3112
|
+
so it does **not** catch the regression — the ticket must add a guard asserting a
|
|
3113
|
+
**terminal sink** (no outgoing edge) renders a real `file:line`, not `?`.
|
|
3114
|
+
Plan stub drafted: [`fix_downstream_sink_location.md`](plan/fix_downstream_sink_location.md).
|
|
3115
|
+
Suggested release: patch (1.2.2) or fold into the next lineage minor.
|
|
3116
|
+
|
|
3117
|
+
**RESOLVED — shipped in v1.2.2 (bundled with v1.2.1; see §18 bundled-release note).**
|
|
3118
|
+
Both downstream queries in [`analyze.py`](src/sqlcg/cli/commands/analyze.py) (primary
|
|
3119
|
+
L134, bare-name fallback L146) now bind location from the **incoming**
|
|
3120
|
+
`()-[dstedge:COLUMN_LINEAGE]->(dst)` edge — the producing query — so terminal sinks
|
|
3121
|
+
carry a real `file:line`. Upstream (L77/L89) keeps the outgoing-edge binding, unchanged.
|
|
3122
|
+
Guarded by the TC6b terminal-sink test in
|
|
3123
|
+
[`test_user_surface_recall_guard.py`](tests/integration/test_user_surface_recall_guard.py),
|
|
3124
|
+
demonstrably RED on master before the fix.
|
|
3125
|
+
|
|
3126
|
+
### 19.3 Cross-finding note
|
|
3127
|
+
|
|
3128
|
+
Both findings share the v1.2.1 root theme: the *graph store* is correct (edges intact,
|
|
3129
|
+
kinds mostly right); the defects live in the **graph→user presentation layer**
|
|
3130
|
+
(filter classification for 19.1, location binding for 19.2) — the same blind-spot
|
|
3131
|
+
class as #38/#40 where anchors traversing raw edges pass while the user-facing query
|
|
3132
|
+
path is wrong. Any guard for either must drive the **user-facing `analyze` path**,
|
|
3133
|
+
not raw `COLUMN_LINEAGE` traversal.
|
|
3134
|
+
|
|
3135
|
+
### 19.4 [KEEP — LOW] TC6b guard reconstructs the query instead of running `analyze downstream`
|
|
3136
|
+
|
|
3137
|
+
Raised by the plan-reviewer on the §19.2 sink fix. The TC6b terminal-sink guard's helper
|
|
3138
|
+
`_downstream_filtered_query()` in
|
|
3139
|
+
[`test_user_surface_recall_guard.py`](tests/integration/test_user_surface_recall_guard.py)
|
|
3140
|
+
**reconstructs the downstream Cypher query string** rather than invoking
|
|
3141
|
+
`analyze.downstream()` end-to-end. It therefore can drift from the production query if
|
|
3142
|
+
[`analyze.py`](src/sqlcg/cli/commands/analyze.py) changes and the helper is not updated in
|
|
3143
|
+
lockstep — exactly the §19.3 blind-spot (a guard not driving the true user-facing path).
|
|
3144
|
+
The fix held for v1.2.2 (the helper mirrors the shipped query), but the divergence risk is
|
|
3145
|
+
structural. **Future hardening:** replace the string-reconstruction helper with a
|
|
3146
|
+
`CliRunner`-based guard that runs the real `analyze downstream` command and asserts the
|
|
3147
|
+
rendered output. LOW priority; below §19.1 and the open §3.x items. No plan stub drafted.
|
|
@@ -59,6 +59,27 @@ This project uses `uv`. Never activate a virtualenv manually, never use `pip`, n
|
|
|
59
59
|
| Lint | `rtk uv run ruff check src tests` |
|
|
60
60
|
| Format | `uv run ruff format src tests` |
|
|
61
61
|
|
|
62
|
+
## Releasing (version bump + tag)
|
|
63
|
+
|
|
64
|
+
Every feature/fix PR that ships bumps the version; after it merges to `master`, tag the release.
|
|
65
|
+
Do **all** of this — the tag is the step most easily forgotten.
|
|
66
|
+
|
|
67
|
+
1. **Bump the version** (in the feature branch, before the PR merges):
|
|
68
|
+
- [`pyproject.toml`](pyproject.toml) `version = "X.Y.Z"`
|
|
69
|
+
- [`src/sqlcg/__init__.py`](src/sqlcg/__init__.py) `__version__ = "X.Y.Z"`
|
|
70
|
+
- `uv lock` (refreshes the lockfile's own version entry)
|
|
71
|
+
- **SemVer**: new capability/surface → **minor** (`1.1.x` → `1.2.0`); bug fix only → **patch**.
|
|
72
|
+
No backward-compat shims, so an additive feature is still minor (not major) when nothing breaks.
|
|
73
|
+
2. **Merge the PR to `master`** (squash or `--merge` per the existing `Merge #NN` history).
|
|
74
|
+
3. **Tag the merge commit on `master`** — **annotated**, `v`-prefixed, message `vX.Y.Z — <one-line summary>`:
|
|
75
|
+
```bash
|
|
76
|
+
git checkout master && git pull --ff-only origin master
|
|
77
|
+
git tag -a vX.Y.Z -m "vX.Y.Z — <summary>" <merge-commit-sha>
|
|
78
|
+
git push origin vX.Y.Z
|
|
79
|
+
```
|
|
80
|
+
All existing tags are annotated and look like `v1.1.3` (never bare `1.1.3`). Confirm with
|
|
81
|
+
`git tag -l --format='%(contents)' vX.Y.Z` and verify it points at the master merge commit.
|
|
82
|
+
|
|
62
83
|
## Target scale — serve both ends of the spectrum
|
|
63
84
|
|
|
64
85
|
This project must work correctly and comfortably at two very different scales:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sql-code-graph
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.2
|
|
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
|
|
@@ -267,6 +267,16 @@ sqlcg mcp restart # stop the server (client must respawn it
|
|
|
267
267
|
sqlcg version # show installed version
|
|
268
268
|
```
|
|
269
269
|
|
|
270
|
+
### Reads while the server is running (v1.2.0)
|
|
271
|
+
|
|
272
|
+
KuzuDB allows a single writer, so while the MCP server is live it holds the
|
|
273
|
+
database lock. CLI **read** commands (`find`, `analyze`, `db info`, `list-repos`,
|
|
274
|
+
`gain`) automatically route their query through the running server over its
|
|
275
|
+
control socket and return rows as usual — no flag, no config. When no server is
|
|
276
|
+
running they open the database directly, exactly as before. If the server is
|
|
277
|
+
mid-reindex the read waits for it to finish rather than failing with
|
|
278
|
+
"Database is locked".
|
|
279
|
+
|
|
270
280
|
## Supported dialects
|
|
271
281
|
|
|
272
282
|
sqlcg is built on [sqlglot](https://github.com/tobymao/sqlglot), so other dialects
|
|
@@ -229,6 +229,16 @@ sqlcg mcp restart # stop the server (client must respawn it
|
|
|
229
229
|
sqlcg version # show installed version
|
|
230
230
|
```
|
|
231
231
|
|
|
232
|
+
### Reads while the server is running (v1.2.0)
|
|
233
|
+
|
|
234
|
+
KuzuDB allows a single writer, so while the MCP server is live it holds the
|
|
235
|
+
database lock. CLI **read** commands (`find`, `analyze`, `db info`, `list-repos`,
|
|
236
|
+
`gain`) automatically route their query through the running server over its
|
|
237
|
+
control socket and return rows as usual — no flag, no config. When no server is
|
|
238
|
+
running they open the database directly, exactly as before. If the server is
|
|
239
|
+
mid-reindex the read waits for it to finish rather than failing with
|
|
240
|
+
"Database is locked".
|
|
241
|
+
|
|
232
242
|
## Supported dialects
|
|
233
243
|
|
|
234
244
|
sqlcg is built on [sqlglot](https://github.com/tobymao/sqlglot), so other dialects
|
|
@@ -17,7 +17,7 @@ bash scripts/generate_cli_docs.sh
|
|
|
17
17
|
| `watch` | Watch a directory and re-index on SQL file changes. |
|
|
18
18
|
| `gain` | Show metrics and feedback analytics. |
|
|
19
19
|
| `report` | Generate a metrics report with FP clusters and parse error patterns. |
|
|
20
|
-
| `install` | Register sqlcg as an MCP server in Claude Code
|
|
20
|
+
| `install` | Register sqlcg as an MCP server in Claude Code. |
|
|
21
21
|
| `uninstall` | Uninstall sqlcg from Claude Code and optionally clean up resources. |
|
|
22
22
|
| `version` | Show version. |
|
|
23
23
|
| `db` | Database management commands |
|
|
@@ -71,13 +71,15 @@ Schema aliases (staging schema → canonical schema) can be configured in
|
|
|
71
71
|
| --- | --- | --- | --- | --- | --- |
|
|
72
72
|
| --dialect, -d | TEXT | No | No | | SQL dialect (or 'auto' to read from .sqlcg.toml) |
|
|
73
73
|
| --dbt-manifest | PATH | No | No | | Path to dbt manifest |
|
|
74
|
-
| --timeout-per-file | INTEGER | No | No |
|
|
74
|
+
| --timeout-per-file | INTEGER | No | No | 10 | Timeout per file in seconds |
|
|
75
75
|
| --buffer-pool-size | INTEGER | No | No | 0 | KuzuDB buffer pool size in MB (0 = default). Set to 256-512 on memory-constrained machines. |
|
|
76
76
|
| --batch-size | INTEGER | No | No | 50 | Files per KuzuDB transaction in the upsert pass. Default 50 balances commit-overhead reduction (vs. legacy per-file commits) against per-batch memory cost. Lower values are safer for memory-constrained machines; higher values give marginal speedup at the cost of larger working sets. Set to 1 to reproduce legacy per-file commit behaviour. |
|
|
77
77
|
| --no-ddl | BOOLEAN | No | No | False | Skip table-node upserts for DDL-only files |
|
|
78
78
|
| --quiet, -q | BOOLEAN | No | No | False | Suppress summary console output |
|
|
79
|
+
| --verbose, -v | BOOLEAN | No | No | False | Print parse warnings to stderr instead of log file |
|
|
79
80
|
| --debug | BOOLEAN | No | No | False | Show detailed log output during indexing |
|
|
80
81
|
| --profile / --no-profile | BOOLEAN | No | No | False | Emit per-stage timing after indexing |
|
|
82
|
+
| --include-working-tree | BOOLEAN | No | No | False | Index the working tree including uncommitted changes. Marks freshness as 'indexed with working-tree changes'. |
|
|
81
83
|
|
|
82
84
|
## `sqlcg reindex`
|
|
83
85
|
|
|
@@ -106,7 +108,8 @@ build — run 'sqlcg db reset && sqlcg db init && sqlcg index <path>' to re-init
|
|
|
106
108
|
| --dialect, -d | TEXT | No | No | | SQL dialect (or 'auto' to read from .sqlcg.toml) |
|
|
107
109
|
| --quiet, -q | BOOLEAN | No | No | False | Suppress summary output |
|
|
108
110
|
| --batch-size | INTEGER | No | No | 50 | Files per KuzuDB transaction (same default as index command) |
|
|
109
|
-
| --timeout-per-file | INTEGER | No | No |
|
|
111
|
+
| --timeout-per-file | INTEGER | No | No | 10 | Per-file parse timeout in seconds |
|
|
112
|
+
| --notify | BOOLEAN | No | No | False | If a server is live on this DB, route the reindex through the server (avoids lock contention). Falls back to direct write if no server is found. |
|
|
110
113
|
|
|
111
114
|
## `sqlcg watch`
|
|
112
115
|
|
|
@@ -181,7 +184,10 @@ If no metrics database exists, prints a message and exits 0.
|
|
|
181
184
|
sqlcg install [OPTIONS]
|
|
182
185
|
```
|
|
183
186
|
|
|
184
|
-
Register sqlcg as an MCP server in Claude Code
|
|
187
|
+
Register sqlcg as an MCP server in Claude Code.
|
|
188
|
+
|
|
189
|
+
Runs ``claude mcp add -s user sql-code-graph <cmd> <args>`` when the
|
|
190
|
+
``claude`` CLI is on PATH; otherwise writes ~/.claude.json directly.
|
|
185
191
|
|
|
186
192
|
Also provisions a Claude skill file (SKILL.md) at the chosen location.
|
|
187
193
|
Pass --scope project or --scope global to specify where the skill is written.
|
|
@@ -333,7 +339,7 @@ Find a table by name.
|
|
|
333
339
|
|
|
334
340
|
| Option | Type | Required | Repeatable | Default | Description |
|
|
335
341
|
| --- | --- | --- | --- | --- | --- |
|
|
336
|
-
|
|
|
342
|
+
| --raw | BOOLEAN | No | No | False | Disable noise filtering on results |
|
|
337
343
|
|
|
338
344
|
## `sqlcg find column`
|
|
339
345
|
|
|
@@ -347,7 +353,7 @@ Find a column by table.column reference.
|
|
|
347
353
|
|
|
348
354
|
| Option | Type | Required | Repeatable | Default | Description |
|
|
349
355
|
| --- | --- | --- | --- | --- | --- |
|
|
350
|
-
|
|
|
356
|
+
| --raw | BOOLEAN | No | No | False | Disable noise filtering on results |
|
|
351
357
|
|
|
352
358
|
## `sqlcg find pattern`
|
|
353
359
|
|
|
@@ -394,6 +400,8 @@ Trace upstream column lineage.
|
|
|
394
400
|
| Option | Type | Required | Repeatable | Default | Description |
|
|
395
401
|
| --- | --- | --- | --- | --- | --- |
|
|
396
402
|
| --depth | INTEGER | No | No | 5 | Maximum traversal depth |
|
|
403
|
+
| --raw | BOOLEAN | No | No | False | Disable noise filtering on results |
|
|
404
|
+
| --include-intermediate | BOOLEAN | No | No | False | Include CTE/derived intermediate nodes |
|
|
397
405
|
|
|
398
406
|
## `sqlcg analyze downstream`
|
|
399
407
|
|
|
@@ -408,6 +416,8 @@ Trace downstream column lineage.
|
|
|
408
416
|
| Option | Type | Required | Repeatable | Default | Description |
|
|
409
417
|
| --- | --- | --- | --- | --- | --- |
|
|
410
418
|
| --depth | INTEGER | No | No | 5 | Maximum traversal depth |
|
|
419
|
+
| --raw | BOOLEAN | No | No | False | Disable noise filtering on results |
|
|
420
|
+
| --include-intermediate | BOOLEAN | No | No | False | Include CTE/derived intermediate nodes |
|
|
411
421
|
|
|
412
422
|
## `sqlcg analyze impact`
|
|
413
423
|
|
|
@@ -421,7 +431,7 @@ Show all queries impacted by a table.
|
|
|
421
431
|
|
|
422
432
|
| Option | Type | Required | Repeatable | Default | Description |
|
|
423
433
|
| --- | --- | --- | --- | --- | --- |
|
|
424
|
-
|
|
|
434
|
+
| --raw | BOOLEAN | No | No | False | Disable noise filtering on results |
|
|
425
435
|
|
|
426
436
|
## `sqlcg analyze failures`
|
|
427
437
|
|
|
@@ -431,6 +441,9 @@ sqlcg analyze failures [OPTIONS]
|
|
|
431
441
|
|
|
432
442
|
List files that failed to parse, with their dominant cause (E-code bucket).
|
|
433
443
|
|
|
444
|
+
Valid --cause buckets (from highest to lowest severity):
|
|
445
|
+
timeout, E8, E3, E2, E5, E1, qualify_failed, func_fallback, pure_ddl_skip.
|
|
446
|
+
|
|
434
447
|
Requires a graph indexed with sqlcg >= v3 (schema version 3). Re-index
|
|
435
448
|
with 'sqlcg db reset && sqlcg index <path>' if the graph was built with
|
|
436
449
|
an earlier version.
|
|
@@ -439,7 +452,7 @@ an earlier version.
|
|
|
439
452
|
|
|
440
453
|
| Option | Type | Required | Repeatable | Default | Description |
|
|
441
454
|
| --- | --- | --- | --- | --- | --- |
|
|
442
|
-
| --cause | TEXT | No | No | | Filter by E-code bucket
|
|
455
|
+
| --cause | TEXT | No | No | | Filter by E-code bucket. Valid values: timeout, E8, E3, E2, E5, E1, qualify_failed, func_fallback, pure_ddl_skip |
|
|
443
456
|
| --limit | INTEGER | No | No | 100 | Maximum rows to return |
|
|
444
457
|
|
|
445
458
|
## `sqlcg analyze unused`
|
|
@@ -455,6 +468,7 @@ Find tables with no query references.
|
|
|
455
468
|
| Option | Type | Required | Repeatable | Default | Description |
|
|
456
469
|
| --- | --- | --- | --- | --- | --- |
|
|
457
470
|
| --threshold | INTEGER | No | No | 0 | Minimum reference count threshold |
|
|
471
|
+
| --raw | BOOLEAN | No | No | False | Disable noise filtering on results |
|
|
458
472
|
|
|
459
473
|
## `sqlcg mcp`
|
|
460
474
|
|
|
@@ -471,6 +485,9 @@ MCP server commands
|
|
|
471
485
|
| `setup` | Print or write MCP server config JSON. |
|
|
472
486
|
| `start` | Start the MCP server. |
|
|
473
487
|
| `best-practices` | Print MCP tool best-practices (the fact/heuristic boundary). |
|
|
488
|
+
| `status` | Print server status JSON (connects to control socket). |
|
|
489
|
+
| `stop` | Stop the running MCP server gracefully. |
|
|
490
|
+
| `restart` | Stop the server. The client (editor) must respawn. |
|
|
474
491
|
|
|
475
492
|
## `sqlcg mcp setup`
|
|
476
493
|
|
|
@@ -480,6 +497,11 @@ sqlcg mcp setup [OPTIONS]
|
|
|
480
497
|
|
|
481
498
|
Print or write MCP server config JSON.
|
|
482
499
|
|
|
500
|
+
--print (default): print the JSON snippet for manual insertion.
|
|
501
|
+
--write: write to ~/.claude.json under mcpServers.user (the correct path
|
|
502
|
+
for Claude Code — not settings.json, which Claude Code does not read
|
|
503
|
+
for MCP servers).
|
|
504
|
+
|
|
483
505
|
### Options
|
|
484
506
|
|
|
485
507
|
| Option | Type | Required | Repeatable | Default | Description |
|
|
@@ -517,6 +539,72 @@ that have not installed the skill.
|
|
|
517
539
|
| --- | --- | --- | --- | --- | --- |
|
|
518
540
|
| _none_ | | | | | |
|
|
519
541
|
|
|
542
|
+
## `sqlcg mcp status`
|
|
543
|
+
|
|
544
|
+
```bash
|
|
545
|
+
sqlcg mcp status [OPTIONS]
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
Print server status JSON (connects to control socket).
|
|
549
|
+
|
|
550
|
+
Returns JSON with fields: running, pid, db_path, indexed_sha, head_sha,
|
|
551
|
+
stale_by_commits, connected_clients, uptime when a server is live.
|
|
552
|
+
|
|
553
|
+
When no server is found: {"running": false}.
|
|
554
|
+
When the PID file exists with a live process but the socket is unavailable:
|
|
555
|
+
{"running": true, "degraded": "socket unavailable", ...}.
|
|
556
|
+
|
|
557
|
+
R3 (stale socket): if the socket file exists but the server is not
|
|
558
|
+
responding (ConnectionRefusedError / FileNotFoundError), falls through
|
|
559
|
+
to the PID-file probe — never hangs or errors on a dead socket.
|
|
560
|
+
|
|
561
|
+
### Options
|
|
562
|
+
|
|
563
|
+
| Option | Type | Required | Repeatable | Default | Description |
|
|
564
|
+
| --- | --- | --- | --- | --- | --- |
|
|
565
|
+
| _none_ | | | | | |
|
|
566
|
+
|
|
567
|
+
## `sqlcg mcp stop`
|
|
568
|
+
|
|
569
|
+
```bash
|
|
570
|
+
sqlcg mcp stop [OPTIONS]
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
Stop the running MCP server gracefully.
|
|
574
|
+
|
|
575
|
+
Sends a ``stop`` op via the control socket; waits up to 5 s for the
|
|
576
|
+
socket file to disappear (confirming clean exit). Falls back to SIGTERM
|
|
577
|
+
on the PID-file PID if the socket is unavailable.
|
|
578
|
+
|
|
579
|
+
R3 (stale socket): ``ConnectionRefusedError`` / ``FileNotFoundError`` are
|
|
580
|
+
caught — never hangs on a dead socket.
|
|
581
|
+
|
|
582
|
+
### Options
|
|
583
|
+
|
|
584
|
+
| Option | Type | Required | Repeatable | Default | Description |
|
|
585
|
+
| --- | --- | --- | --- | --- | --- |
|
|
586
|
+
| _none_ | | | | | |
|
|
587
|
+
|
|
588
|
+
## `sqlcg mcp restart`
|
|
589
|
+
|
|
590
|
+
```bash
|
|
591
|
+
sqlcg mcp restart [OPTIONS]
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
Stop the server. The client (editor) must respawn.
|
|
595
|
+
|
|
596
|
+
v1.1 cannot re-parent an editor-spawned stdio process. This command
|
|
597
|
+
stops the current server and prints guidance for the user to restart
|
|
598
|
+
the MCP server via their editor's MCP configuration.
|
|
599
|
+
|
|
600
|
+
True auto-restart (re-parenting stdio) is deferred to v1.2.
|
|
601
|
+
|
|
602
|
+
### Options
|
|
603
|
+
|
|
604
|
+
| Option | Type | Required | Repeatable | Default | Description |
|
|
605
|
+
| --- | --- | --- | --- | --- | --- |
|
|
606
|
+
| _none_ | | | | | |
|
|
607
|
+
|
|
520
608
|
## `sqlcg git`
|
|
521
609
|
|
|
522
610
|
```bash
|
|
@@ -542,6 +630,8 @@ Install git hooks for sqlcg integration.
|
|
|
542
630
|
Writes a post-checkout hook that triggers incremental resync after branch switches
|
|
543
631
|
and a post-merge hook that triggers resync after pulls/merges.
|
|
544
632
|
Idempotent: running multiple times produces one hook entry per hook.
|
|
633
|
+
The hooks embed the absolute path of the installing sqlcg binary so version skew
|
|
634
|
+
between the installed binary and the hook command is avoided.
|
|
545
635
|
|
|
546
636
|
### Options
|
|
547
637
|
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Feature Plan: Downstream sink `file:line` recall
|
|
2
|
+
|
|
3
|
+
> Status: **plan-reviewed — READY FOR IMPLEMENTATION.**
|
|
4
|
+
> Source: ARCHITECTURE_REVIEW.md §19.2 (v1.2.1 live DWH re-verification, 2026-06-02).
|
|
5
|
+
|
|
6
|
+
## Summary
|
|
7
|
+
|
|
8
|
+
`analyze downstream` renders `file:line='?'` for ~81% of result rows (29,827 of
|
|
9
|
+
36,892 distinct downstream columns on the DWH corpus) because terminal sinks have no
|
|
10
|
+
*outgoing* `COLUMN_LINEAGE` edge to bind location from. Bind downstream-result
|
|
11
|
+
location from the **incoming** edge (the producing query) instead, flipping ~81% of
|
|
12
|
+
`?` rows to a real `file:line`.
|
|
13
|
+
|
|
14
|
+
## Scope
|
|
15
|
+
|
|
16
|
+
### In Scope
|
|
17
|
+
- The two downstream queries in [`analyze.py`](src/sqlcg/cli/commands/analyze.py)
|
|
18
|
+
(`downstream` primary + bare-name fallback): change the location-binding
|
|
19
|
+
`OPTIONAL MATCH` from `(dst)-[dstedge]->()` to `()-[dstedge:COLUMN_LINEAGE]->(dst)`.
|
|
20
|
+
- A behavioural guard asserting a **terminal sink** (a column with no outgoing edge)
|
|
21
|
+
renders a real `file:line`, not `?`.
|
|
22
|
+
|
|
23
|
+
### Non-Goals
|
|
24
|
+
- The upstream queries (`<-[…]-(src)` + `(src)-[srcedge]->()`). The outgoing-edge
|
|
25
|
+
binding is **correct** for upstream — do not touch lines 73–94 of `analyze.py`.
|
|
26
|
+
- Finding 19.1 (scratch-object classification leak) — separate, LOW-priority ticket.
|
|
27
|
+
- Any change to the kind-filter (`_kind_filter`, s18.3), schema, indexes, or parser.
|
|
28
|
+
- Re-binding `q.start_line`/`q.file_path` semantics anywhere else (server tools).
|
|
29
|
+
|
|
30
|
+
## Design
|
|
31
|
+
|
|
32
|
+
### Query change (the load-bearing edit)
|
|
33
|
+
|
|
34
|
+
Current downstream binding ([`analyze.py`](src/sqlcg/cli/commands/analyze.py)
|
|
35
|
+
lines 134–137, and the bare-name fallback 146–149):
|
|
36
|
+
|
|
37
|
+
```cypher
|
|
38
|
+
OPTIONAL MATCH (dst)-[dstedge:COLUMN_LINEAGE]->()
|
|
39
|
+
OPTIONAL MATCH (q:SqlQuery {id: dstedge.query_id})
|
|
40
|
+
WITH dst, min(q.start_line) AS line, min(q.file_path) AS file
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Change the first arrow to bind from the **producing** query (incoming edge):
|
|
44
|
+
|
|
45
|
+
```cypher
|
|
46
|
+
OPTIONAL MATCH ()-[dstedge:COLUMN_LINEAGE]->(dst)
|
|
47
|
+
OPTIONAL MATCH (q:SqlQuery {id: dstedge.query_id})
|
|
48
|
+
WITH dst, min(q.start_line) AS line, min(q.file_path) AS file
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Rationale: every downstream result `dst` has an incoming edge by definition (that is
|
|
52
|
+
how it became reachable), and that edge's `query_id` points at the query that
|
|
53
|
+
produced the column — the user-meaningful "where is this column written" location.
|
|
54
|
+
A terminal sink has no *outgoing* edge (today's `?`) but always has an incoming one.
|
|
55
|
+
|
|
56
|
+
Keep `OPTIONAL MATCH` (not `MATCH`) so a `dst` whose incoming edge has a NULL/missing
|
|
57
|
+
`query_id` still returns a row (degrades to `?` rather than dropping the row).
|
|
58
|
+
|
|
59
|
+
### Data Models / Dependencies
|
|
60
|
+
None. No schema, index, or dependency change.
|
|
61
|
+
|
|
62
|
+
## Implementation Steps
|
|
63
|
+
|
|
64
|
+
### Phase 1: Query rewrite
|
|
65
|
+
**Step 1.1**: Flip the location-binding arrow in both downstream queries.
|
|
66
|
+
- Files affected: [`analyze.py`](src/sqlcg/cli/commands/analyze.py) (downstream
|
|
67
|
+
primary ~134, bare-name fallback ~146).
|
|
68
|
+
- Acceptance: both queries bind `()-[dstedge]->(dst)`; upstream queries unchanged
|
|
69
|
+
(grep confirms `(src)-[srcedge]->()` still present at lines 77 / 89).
|
|
70
|
+
|
|
71
|
+
### Phase 2: Guard
|
|
72
|
+
**Step 2.1**: Add `test_tc6b_downstream_sink_location_present` to
|
|
73
|
+
[`test_user_surface_recall_guard.py`](tests/integration/test_user_surface_recall_guard.py).
|
|
74
|
+
- The terminal sink to use from the existing A–D corpus is **`mart.fact_kpi.measure`**.
|
|
75
|
+
It is the final INSERT target — it has no outgoing `COLUMN_LINEAGE` edge (nothing
|
|
76
|
+
downstream consumes it), but it IS reachable as a downstream result from
|
|
77
|
+
`staging.src_a.x`. Confirm this via `MATCH (c:SqlColumn {id: 'mart.fact_kpi.measure'})-
|
|
78
|
+
[:COLUMN_LINEAGE]->() RETURN count(*)` returning 0 before relying on it.
|
|
79
|
+
- Build the downstream query using `_kind_filter("dst", include_intermediate=False)` —
|
|
80
|
+
the same helper `analyze downstream` calls — so the guard drives the production query
|
|
81
|
+
path, not raw edge traversal (ARCHITECTURE_REVIEW.md §19.3).
|
|
82
|
+
- Start the traversal from `staging.src_a.x` (a physical source) and assert that the
|
|
83
|
+
row for `mart.fact_kpi.measure` carries a non-null `file` field. Assert observable
|
|
84
|
+
output (non-null `file` value), not "no exception raised".
|
|
85
|
+
- Acceptance: the new test is RED against the current outgoing-edge query on master and
|
|
86
|
+
GREEN after the Step 1.1 flip. Verify by running it before and after the edit.
|
|
87
|
+
|
|
88
|
+
## Test Strategy
|
|
89
|
+
- Integration: the new TC6b guard on the in-fixture sink (above).
|
|
90
|
+
- Manual/live (optional, recorded in plan postmortem, not committed — DWH e2e is
|
|
91
|
+
gitignored): re-run `analyze downstream` on a DWH column and confirm the `?` rate
|
|
92
|
+
drops from ~81% toward ~0% for sinks.
|
|
93
|
+
- Regression: full `uv run pytest` — confirm TC6 (upstream) and the kind-filter
|
|
94
|
+
guards (TC1/TC4) stay green; no perf/scaling guard touched (query-only change).
|
|
95
|
+
|
|
96
|
+
## Acceptance Criteria
|
|
97
|
+
- [ ] Both downstream queries bind location from the incoming `COLUMN_LINEAGE` edge.
|
|
98
|
+
- [ ] Upstream queries are unchanged (outgoing-edge binding preserved).
|
|
99
|
+
- [ ] A terminal-sink downstream result renders a real `file:line` (TC6b green).
|
|
100
|
+
- [ ] TC6b is demonstrably RED on `master` before the fix (proves it guards the bug).
|
|
101
|
+
- [ ] Full test suite green; no perf/scaling guard regression.
|
|
102
|
+
- [ ] Version bumped to **1.2.2** (patch) per CLAUDE.md release process.
|
|
103
|
+
**Bundling decision (2026-06-02, supersedes the original standalone plan):** this
|
|
104
|
+
sink fix and the v1.2.1 bugfix scope ship together as a single **v1.2.2** release;
|
|
105
|
+
the lower `v1.2.1` tag is **skipped** (precedent: v1.1.2→v1.1.3). The bundled work
|
|
106
|
+
lives on `feat/v1.2.1-bugfix` (PR #48, fast-forwarded to `b5bf7a3`). The combined
|
|
107
|
+
plan-compliance verdict is recorded in
|
|
108
|
+
[`v1_2_1_bugfix.md`](v1_2_1_bugfix.md) under "Bundled v1.2.2 release compliance".
|
|
109
|
+
|
|
110
|
+
## Risks and Mitigations
|
|
111
|
+
- **Risk: a `dst` with multiple incoming edges from different queries.** `min()`
|
|
112
|
+
aggregation already collapses fanout (same pattern as #45.1); pick the
|
|
113
|
+
lowest-line producing query deterministically. Mitigation: keep the
|
|
114
|
+
`WITH dst, min(...), min(...)` aggregate-before-`LIMIT` shape exactly as upstream.
|
|
115
|
+
- **Risk: TC6 false-confidence (it passes on a located case).** Mitigation: Phase 2
|
|
116
|
+
explicitly targets a *sink* — `mart.fact_kpi.measure` with zero outgoing edges —
|
|
117
|
+
the case TC6 misses; prove RED-on-master before the fix.
|
|
118
|
+
- **Risk: scope creep into server tools.** Out of scope — this ticket is the CLI
|
|
119
|
+
`analyze downstream` path only.
|