sql-code-graph 1.2.2__tar.gz → 1.3.0__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.2.2 → sql_code_graph-1.3.0}/ARCHITECTURE_REVIEW.md +133 -2
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/PKG-INFO +1 -1
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/docs/cli.md +14 -1
- sql_code_graph-1.3.0/plan/fix_issue29_live_test_followups.md +442 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/pyproject.toml +1 -1
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/__init__.py +1 -1
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/db.py +23 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/git.py +11 -4
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/index.py +167 -4
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/mcp.py +70 -3
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/reindex.py +146 -76
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/kuzu_backend.py +10 -6
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/metrics/store.py +48 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/server/server.py +165 -70
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/server/tools.py +155 -14
- sql_code_graph-1.3.0/src/sqlcg/server/writer.py +634 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_mcp_lifecycle.py +29 -9
- sql_code_graph-1.3.0/tests/integration/test_dialect_auto_resolution.py +178 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_read_via_server.py +7 -1
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_reindex_via_server.py +60 -28
- sql_code_graph-1.3.0/tests/integration/test_single_writer_queue.py +382 -0
- sql_code_graph-1.3.0/tests/unit/test_BugB_escalation_uses_init_path.py +101 -0
- sql_code_graph-1.3.0/tests/unit/test_BugC_hook_upgrade.py +184 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_git_hooks_notify.py +29 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_kuzu_lock.py +4 -4
- sql_code_graph-1.3.0/tests/unit/test_writer_queue.py +323 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/uv.lock +1 -1
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.claude/agents/api-documenter.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.claude/agents/architect-planner.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.claude/agents/architect-reviewer.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.claude/agents/code-reviewer.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.claude/agents/developer.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.claude/agents/plan-reviewer.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.claude/agents/sprint-planner.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.github/workflows/benchmark.yml +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.github/workflows/e2e-tests.yml +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.github/workflows/release.yml +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.github/workflows/test.yml +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.gitignore +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.pre-commit-config.yaml +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.sqlcgignore +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/CHANGELOG.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/CLAUDE.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/README.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/docs/AIRBNB_PARSE_REPORT.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/e2e_firstuser_report.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/e2e_run_20260528_101413.output +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/main.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/WORKFLOW.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/blueprint.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/bundle_claude_skill.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/feature_34_unused_presentation_segregation.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/feature_35_external_downstream_injection.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/feature_F2_bundle_claude_skill.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/fix_downstream_sink_location.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/fix_dynamic_table_parsing.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/fix_expand_qualify_perf.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/fix_firstuser_findings.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/fix_schema_case_mismatch.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/hygiene_config_path_and_survivors.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/investigation_e5_e4.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/living_codebase_resync.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/measurements/schema_comparison_with_schema.json +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/measurements/schema_comparison_without_schema.json +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/measurements/sprint_08_changelogs_fullindex.json +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/measurements/sprint_08_fullcorpus_index.json +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/measurements/sprint_pool_300s_plan.json +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/parse_diagnostics.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/parsing_errors_experiment.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/snowflake_en_test_suite.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_01.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_01_deployment_pypi.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_02.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_02_v0.3.0_core.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_03.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_03_v0.3.1_postmortem.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_04_column_lineage.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_04_column_lineage_fix.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_05_star_resolution.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_06_lineage_coverage.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_07_open_ecodes.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_07_perf_and_live_test.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_08_perf_upsert.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_09_lineage_coverage.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_10_anchor_tools.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_11_v1.0.2_bugfix.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_12_v1.1.0.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_13_v1.1.0_cluster_b.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_3.1_postmortem.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sqlcg.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/trust_layer.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/v1.1.0_cluster_b_provenance_trust.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/v1.1.0_live_graph_freshness.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/v1.1.1_batch_upsert_perf.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/v1_1_2_bugfix.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/v1_1_3_union_cte_star.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/v1_2_0_read_proxy.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/v1_2_1_bugfix.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/profile.html +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/pyrightconfig.json +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/scripts/collect_parse_errors.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/scripts/generate_cli_docs.sh +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/__main__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/analyze.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/find.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/gain.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/install.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/report.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/uninstall.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/watch.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/main.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/config.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/freshness.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/graph_db.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/jobs.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/neo4j_backend.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/queries.cypher +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/queries.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/schema.cypher +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/schema.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/indexer/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/indexer/dbt_adapter.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/indexer/error_classify.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/indexer/git_delta.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/indexer/indexer.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/indexer/pool.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/indexer/walker.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/indexer/watcher.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/lineage/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/lineage/aggregator.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/lineage/schema_resolver.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/metrics/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/parsers/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/parsers/ansi_parser.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/parsers/base.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/parsers/bigquery_parser.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/parsers/postgres_parser.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/parsers/registry.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/parsers/snowflake_parser.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/parsers/tsql_parser.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/server/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/server/control.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/server/exceptions.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/server/models.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/server/noise_filter.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/server/read_client.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/server/skill.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/utils/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/utils/hashing.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/utils/ignore.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/utils/logging.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/adversarial/200_join.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/adversarial/500_union.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/bench_indexer.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/conftest.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/case_normalization.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/colon_cast.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/colon_reserved_word.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/copy_into.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/create_procedure.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/identifier_dynamic.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/lateral_flatten.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/qualify.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/scripting_block.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/three_part.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/tpch/q01.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/tpch/q02.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/tpch/q03.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/tpch/q04.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/tpch/q05.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/conftest.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_F2_skill_install_e2e.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_airbnb_e2e.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_cli_index.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_git_hook_install.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_golden_lineage.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_mcp_tools.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_parse_diagnostics_cli.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_star_resolution_e2e.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_watch.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/dim_hosts_cleansed.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/dim_listings_cleansed.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/fct_reviews.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/mart_fullmoon_reviews.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/raw_hosts.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/raw_listings.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/raw_reviews.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/src_hosts.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/src_listings.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/src_reviews.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/bigquery/.gitkeep +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/jaffle_shop/customers.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/jaffle_shop/orders.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/jaffle_shop/raw_orders.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/snowflake/base_tables.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/snowflake/reports.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/snowflake/views.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/star_corpus/ddl_src.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/star_corpus/ddl_tgt.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/star_corpus/etl_alias_star.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/star_corpus/etl_star.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/synthetic/base_tables.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/synthetic/reports.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/synthetic/views.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/snowflake/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/snowflake/test_insert_select.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_T34_presentation_segregation.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_T35_external_consumers.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_anchor_tools.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_bulk_upsert.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_column_lineage_e2e.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_cross_file_lineage.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_cte_recall_guard.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_cte_schema_alias_guard.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_cte_source_node_invariant.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_dialect_matrix.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_e36_xfile_regression_guard.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_freshness_mcp.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_hygiene_config_root_reconciliation.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_indexer_batching.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_indexer_commits.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_indexer_to_graph.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_live_anchors.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_parse_diagnostics.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_pr1_confidence_reason.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_pr2_source_location.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_pr3_kind_tagging.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_readonly_under_lock.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_resync.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_star_resolution.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_temp_table_lineage.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_union_cte_star_recall_guard.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_user_surface_recall_guard.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/perf/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/perf/test_perf.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E10/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E11/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E12/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E12/e12_json_path.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E12/e12_lateral_flatten.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E12/test_e12.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E13/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E14/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E15/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E16/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E16/e16_merge.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E16/e16_merge_delete.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E16/test_e16.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E17/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E18/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E18/e18_decode.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E18/e18_iff_decode.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E18/e18_nvl2.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E18/test_e18.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E19/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E2/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E2/e2_expr_alias.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E2/e2_function_alias.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E2/e2_multiply_alias.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E2/test_e2.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E20/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E21/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E21/e21_alias_forward_ref.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E21/e21_three_level_chain.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E21/test_e21.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E22/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E23/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E23/e23_stored_proc.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E23/e23_stored_proc_multi.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E23/test_e23.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E24/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E25/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E25/e25_cross_db.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E25/e25_two_part.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E25/test_e25.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E25/test_e25_full_id.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E26/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E27/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E27/e27_nested_udf.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E27/e27_udf.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E27/test_e27.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E28/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E29/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E3/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E3/e3_alter_table.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E3/e3_create_sequence.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E3/e3_ddl_only.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E3/test_e3.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E30/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E31/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E32/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E33/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E34/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E35/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E36/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E36/e36_temp_multi_use.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E36/e36_temp_table.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E36/test_e36.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E36/test_e36_xfile.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E37/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E38/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E4/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E4/e4_execute_immediate.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E4/e4_if_not_exists.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E4/e4_unexpected_token.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E4/e4_unpivot.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E4/test_e4.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E5/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E5/e5_cte_missing_source.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E5/e5_multi_cte.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E5/e5_nested_cte.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E5/test_e5.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E5/test_e5_cte_projection.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E8/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E8/e8_dynamic_sources.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E8/e8_seq_nextval.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E8/e8_uuid.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E8/test_e8.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E9/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_aggregates/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_aggregates/fixture_sum_absent_cross_schema.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_aggregates/fixture_sum_case_when.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_aggregates/fixture_sum_present_source.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_aggregates/test_e_aggregates.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_date_functions/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_date_functions/fixture_date_aliased.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_date_functions/fixture_date_unaliased.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_date_functions/fixture_datediff_unaliased.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_date_functions/fixture_year_unaliased.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_date_functions/test_e_date_functions.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/README.md +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/anchors/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/anchors/fixture_etl.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/anchors/fixture_omloopsnelheid.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/anchors/fixture_semantic.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/anchors/fixture_source.sql +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/anchors/test_anchor_ma_aantal_op_order.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/anchors/test_anchor_omloopsnelheid.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/conftest.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/test_plan_review_gates.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/snowflake/__init__.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/snowflake/test_scripting_noise.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_F2_install_skill.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_F2_skill_render.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_F2_uninstall_skill.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_T09_01_qualify_once.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_T09_02_ddl_skip.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_T09_04_subprocess_isolate.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_T09_06_log_verbosity.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_T35_config_external_consumers.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_aggregator.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_aggregator_skip.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_base_parser.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_branch_monitor.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_bulk_upsert_invariant.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_canonical_target_resolution.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_cli.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_cli_help.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_closure_depth.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_column_lineage_wiring.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_config.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_data_models.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_db_info.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_dominant_cause.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_find_cmd.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_firstuser_findings.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_freshness_helper.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_gain_ratio.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_git_delta.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_git_hooks.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_graph_backend.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_hard_kill_pool.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_hygiene_config_warning.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_include_working_tree.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_index_cmd.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_index_flags.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_index_progress.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_indexer_progress.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_indexer_quality.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_install.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_install_message.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_jobs.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_judgement.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_kuzu_backend.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_lineage_conversion.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_literal_column_skip.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_mcp_best_practices.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_mcp_control.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_mcp_stdio_smoke.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_metrics.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_noise_filter.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_parse_file_dependency_filter.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_parse_quality.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_parser.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_perf_scaling_guard.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_pr07_observability.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_queries_loader.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_read_client.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_resolve_pass2_passes_dependency_filter.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_schema_resolver.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_server.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_snowflake_strip_alter_set_tag.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_sprint_06_t04_t05.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_star_resolution_unit.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_star_schema_unit.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_submit_feedback.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_subprocess_isolate.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_t02_expression_name_extraction.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_t03_ddl_skip.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_timeout_cancel.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_tools_hints.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_tools_warnings.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_uninstall.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_unqualified_fallback.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_upsert_batch_invariant.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_walker.py +0 -0
- {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_watcher.py +0 -0
|
@@ -3026,8 +3026,13 @@ Both are now load-bearing; recorded so a future refactor does not silently undo
|
|
|
3026
3026
|
re-verification (80→47 `cte_insert` islands) recorded in the plan; the surviving 47
|
|
3027
3027
|
are likely legitimately source-less computed measures, not a regression. Shape B of
|
|
3028
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** —
|
|
3030
|
-
|
|
3029
|
+
- **#28 server read-only-by-default + reindex escalation** — **IMPLEMENTED in 1.3.0**
|
|
3030
|
+
(#29 single-writer queue + RO→RW escalation; plan
|
|
3031
|
+
[`fix_issue_29_single_writer_queue.md`](plan/fix_issue_29_single_writer_queue.md)).
|
|
3032
|
+
The read half was already handled by the v1.2.0 routing proxy; 1.3.0 adds the write half:
|
|
3033
|
+
the server serves RO after a startup RW schema-ensure window, and escalates RO→RW only for
|
|
3034
|
+
the duration of a single-writer drain. See §20 for the compliance postmortem and two
|
|
3035
|
+
accepted/flagged deviations.
|
|
3031
3036
|
- **`connected_clients` fidelity** — best-effort `1` today; no real counter.
|
|
3032
3037
|
|
|
3033
3038
|
## 19. v1.2.1 live DWH re-verification — two residual findings (2026-06-02)
|
|
@@ -3145,3 +3150,129 @@ The fix held for v1.2.2 (the helper mirrors the shipped query), but the divergen
|
|
|
3145
3150
|
structural. **Future hardening:** replace the string-reconstruction helper with a
|
|
3146
3151
|
`CliRunner`-based guard that runs the real `analyze downstream` command and asserts the
|
|
3147
3152
|
rendered output. LOW priority; below §19.1 and the open §3.x items. No plan stub drafted.
|
|
3153
|
+
|
|
3154
|
+
## 20. v1.3.0 — Single-Writer Queue + RO→RW Escalation (#29 / #28) (2026-06-04)
|
|
3155
|
+
|
|
3156
|
+
Implements the long-deferred §18.4 "#28 server read-only-by-default + reindex escalation"
|
|
3157
|
+
item. Plan: [`fix_issue_29_single_writer_queue.md`](plan/fix_issue_29_single_writer_queue.md).
|
|
3158
|
+
Branch `feat/fix-issue-29-single-writer-queue`, 8 commits (T-01…T-08). Test suite
|
|
3159
|
+
1009 passed / 7 skipped / 1 xfailed; pyright clean; ruff clean.
|
|
3160
|
+
|
|
3161
|
+
### 20.1 What shipped (plan-compliance verdict)
|
|
3162
|
+
|
|
3163
|
+
The five phases landed as planned. The hard invariants were honoured **in the
|
|
3164
|
+
control-socket / drain path**:
|
|
3165
|
+
|
|
3166
|
+
- **B1 (no co-existence window)** — Steps 2.2 (drain task + escalation primitive) and 2.3
|
|
3167
|
+
(retire the inline `reindex` op) landed in **one commit** (`62c19cd`). The `reindex` and
|
|
3168
|
+
`index` socket-op handlers in [`server.py`](src/sqlcg/server/server.py) `_control_socket_task`
|
|
3169
|
+
enqueue onto `WriterQueue` and never call `backend_ref()`; the drain task is the sole
|
|
3170
|
+
backend consumer, resolving the backend under `backend_lock` via `escalate_to_rw`.
|
|
3171
|
+
- **B2 (shutdown vs. drain ordering)** — `_stop_watcher`
|
|
3172
|
+
([`server.py`](src/sqlcg/server/server.py) L394-410) sets `shutdown_requested` then
|
|
3173
|
+
acquires `backend_lock` **before** `shutdown_backend()`; `de_escalate_to_ro`
|
|
3174
|
+
([`writer.py`](src/sqlcg/server/writer.py)) skips the RO reopen when the event is set.
|
|
3175
|
+
Both guarantees (a)+(b) present.
|
|
3176
|
+
- **B3 (status framing atomic)** — server-side `status` framing (Step 4.1) and the CLI
|
|
3177
|
+
recv-exactly parse (Step 4.2) shipped in one commit (`af2c85d`); `mcp_stop`'s
|
|
3178
|
+
`s.recv(128)` and the unframed `stop` reply are untouched.
|
|
3179
|
+
- **W3 (`from=null`)** — the `reindex` op stores `from_sha=None`; the drain resolves the
|
|
3180
|
+
stored SHA via `rw.get_indexed_sha()` and HEAD via `git rev-parse`, with an actionable
|
|
3181
|
+
"no prior index" error on a never-indexed DB. The CLI refusal was removed.
|
|
3182
|
+
- **W5 (`done:true` sentinel)** — both server emit and CLI recv loops key off
|
|
3183
|
+
`done == true`, not EOF; failure relays `ok:false, done:true, error:…`.
|
|
3184
|
+
- **W6 (`find_lock_holder` public)** — promoted in `kuzu_backend.py`, in-module call site
|
|
3185
|
+
updated, imported by `writer.py`.
|
|
3186
|
+
- **W7 (drain exception handler)** — the drain's `except Exception` clears `_active`
|
|
3187
|
+
(`mark_active_failed`), relays an `ok:false,done:true` terminal frame to waiters, and the
|
|
3188
|
+
loop survives; `EscalationLockError` keeps its dedicated C3 path with the ERROR log.
|
|
3189
|
+
|
|
3190
|
+
### 20.2 Deviation 1 — MCP `index_repo` tool inline escalation (FLAGGED — follow-up required)
|
|
3191
|
+
|
|
3192
|
+
**What the developer added (not in the plan):** a `_serving_ro` flag +
|
|
3193
|
+
`_get_or_escalate_rw` / `_de_escalate_to_ro_from_tool` helpers in
|
|
3194
|
+
[`tools.py`](src/sqlcg/server/tools.py) so the MCP `index_repo` **tool** (invoked over the
|
|
3195
|
+
stdio transport, distinct from the CLI `index` socket op) escalates RO→RW inline when the
|
|
3196
|
+
server is serving RO.
|
|
3197
|
+
|
|
3198
|
+
**Verdict: the intent is consistent with the single-writer model, but the implementation
|
|
3199
|
+
violates the plan's central invariant and leaks the write lock on failure. Track as a
|
|
3200
|
+
v1.3.x follow-up, not a release blocker (the happy path works and tests are green), but
|
|
3201
|
+
it must be fixed before it is relied on under concurrency.**
|
|
3202
|
+
|
|
3203
|
+
Three concrete problems:
|
|
3204
|
+
|
|
3205
|
+
1. **Escalation outside `backend_lock` (breaks B1/OD-7).** The plan's load-bearing invariant
|
|
3206
|
+
is "once escalation exists, **no op may operate on a backend resolved outside
|
|
3207
|
+
`backend_lock`**; the only backend consumer is the drain task." The MCP `index_repo` tool
|
|
3208
|
+
escalates and writes with **no lock held** — a second path that can swap
|
|
3209
|
+
`tools._backend` concurrently with a `query` op or a drain. This is exactly the class of
|
|
3210
|
+
bug B1 was written to prevent.
|
|
3211
|
+
2. **RW-lock leak on the failure path.** `_de_escalate_to_ro_from_tool` is called only on the
|
|
3212
|
+
success branch ([`tools.py`](src/sqlcg/server/tools.py) L613-614), **not in a `finally`**.
|
|
3213
|
+
Any exception during `index_repo` / `upsert_node` / `run_read` / `upsert_edge` re-raises
|
|
3214
|
+
(L634) with the backend still RW — the exclusive write lock stays held, blocking all
|
|
3215
|
+
subsequent routed reads until the server is restarted. (Contrast the drain path, which
|
|
3216
|
+
de-escalates in a `finally`.)
|
|
3217
|
+
3. **Dead `_set_backend_lock` / `_backend_lock`.** `_set_backend_lock`
|
|
3218
|
+
([`tools.py`](src/sqlcg/server/tools.py) L117) and the `_backend_lock` global are
|
|
3219
|
+
**defined but never called** — a zero-value stub that looks like the intended lock plumbing
|
|
3220
|
+
for the tool escalation but was never wired. This is the CLAUDE.md "every new method must
|
|
3221
|
+
have a grep-confirmed call site" rule unmet, and the "function defined but never invoked"
|
|
3222
|
+
smell.
|
|
3223
|
+
|
|
3224
|
+
**Follow-up (recommended):** route the MCP `index_repo` tool write through the same
|
|
3225
|
+
`WriterQueue`/drain as the CLI `index` op (so there is genuinely one writer), or — minimally —
|
|
3226
|
+
acquire `backend_lock` for the escalation and move the de-escalation into a `finally`. There
|
|
3227
|
+
is **no test** for this path today (no test references `_get_or_escalate_rw` / `_serving_ro`),
|
|
3228
|
+
so the leak and the lock-bypass are unguarded. A regression test asserting (a) the tool
|
|
3229
|
+
escalates under the lock and (b) a raising `index_repo` de-escalates back to RO is required
|
|
3230
|
+
with the fix.
|
|
3231
|
+
|
|
3232
|
+
### 20.3 Deviation 2 — Step 4.3 "queued behind N" display (ACCEPTED, minor)
|
|
3233
|
+
|
|
3234
|
+
The plan's Step 4.3 specified that a client queued behind other work prints
|
|
3235
|
+
`queued behind: <op> (done/total files) — position N` before attaching to live progress.
|
|
3236
|
+
The developer implemented the `done:true` terminal sentinel and the progress-bar attach
|
|
3237
|
+
fully, but **omitted the "queued behind … position N" pre-active display**: the CLI receives
|
|
3238
|
+
the `{queued:true, position:N}` frame and the subsequent progress frames, but the queued
|
|
3239
|
+
frame is silently consumed (it carries no `files_total`, so the progress loop renders
|
|
3240
|
+
nothing for it) — see [`index.py`](src/sqlcg/cli/commands/index.py) L307-311.
|
|
3241
|
+
|
|
3242
|
+
**Verdict: ACCEPTED as a cosmetic shortfall.** The functional contract (enqueue, attach,
|
|
3243
|
+
stream progress, terminate on `done:true`, exit non-zero on `ok:false`) is intact; only the
|
|
3244
|
+
human-readable "you are behind N others" hint is missing. The `position` is already on the
|
|
3245
|
+
wire, so completing it is a one-line render in the existing loop. Track as a polish item,
|
|
3246
|
+
not a correctness gap.
|
|
3247
|
+
|
|
3248
|
+
### 20.4 Test-coverage gaps vs. the plan's Test Strategy (FLAGGED)
|
|
3249
|
+
|
|
3250
|
+
The plan's Test Strategy enumerated several **deterministic** integration tests that were
|
|
3251
|
+
not implemented as test functions, even though
|
|
3252
|
+
[`test_single_writer_queue.py`](tests/integration/test_single_writer_queue.py)'s module
|
|
3253
|
+
docstring lists B2 / W3 among its coverage (the docstring overstates what is asserted):
|
|
3254
|
+
|
|
3255
|
+
- **B2 stop-mid-drain** (in-flight write commits; `tools._backend is None` after shutdown,
|
|
3256
|
+
no reopen-after-shutdown) — listed in the docstring, **no test function**.
|
|
3257
|
+
- **W3** arg-level assertion (`resync_changed(from=stored SHA, to=HEAD)`) and the
|
|
3258
|
+
never-indexed "no prior index" error — **no test** (the drain code is present and exercised
|
|
3259
|
+
indirectly by Scenario A, but the W3-specific arg/branch assertions are missing).
|
|
3260
|
+
- **W7** drain-body-raises → `_active` cleared + `ok:false,done:true` terminal frame + loop
|
|
3261
|
+
survives — **no test**.
|
|
3262
|
+
- **Reads block during a drain** (OD-1, deterministic `anyio.Event` ordering) — **no test**.
|
|
3263
|
+
- **B1 structural guard** (the `reindex` op body contains no pre-lock `backend_ref()`,
|
|
3264
|
+
mirroring the perf-invariant guard discipline) — **no test**.
|
|
3265
|
+
- **Escalation transition on real Kuzu** (second in-process RO holder forces
|
|
3266
|
+
`EscalationLockError`) — **no test** (only the injected-`opener` unit tests cover retry).
|
|
3267
|
+
|
|
3268
|
+
What **is** covered: fresh-DB init, stale-schema refusal (Step 1.4), `db reset` refusal
|
|
3269
|
+
(Step 3.4), framed `status` + `writer_queue` shape (Steps 4.1/4.2), metrics persisted +
|
|
3270
|
+
`SQLCG_METRICS=0` suppression (Step 4.5), the escalation retry/backoff + ERROR-log unit tests
|
|
3271
|
+
(Steps 1.2/4.4), the coalescing rules (Step 2.1), and the drain-runs-`resync_changed`-once
|
|
3272
|
+
invariant guard (Scenario A in `test_reindex_via_server.py`).
|
|
3273
|
+
|
|
3274
|
+
**Verdict:** these are real plan deviations (the named deterministic guards were specified
|
|
3275
|
+
and not delivered), but the shipped behaviour is exercised indirectly and the suite is green,
|
|
3276
|
+
so they are **follow-up hardening**, not a ship blocker. They should be filed alongside the
|
|
3277
|
+
Deviation-1 fix — the B2/W7/reads-block tests in particular guard the exact concurrency
|
|
3278
|
+
contract that Deviation 1 currently bypasses.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sql-code-graph
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
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
|
|
@@ -62,6 +62,13 @@ sqlcg index [OPTIONS] PATH
|
|
|
62
62
|
|
|
63
63
|
Index SQL files in a directory.
|
|
64
64
|
|
|
65
|
+
When a server is live on this DB, the index is routed through the server's
|
|
66
|
+
control socket so the DB is never opened directly (avoids lock contention).
|
|
67
|
+
Use --detach to enqueue and return immediately (fire-and-forget).
|
|
68
|
+
|
|
69
|
+
With no server live, falls back to the direct-write path unchanged
|
|
70
|
+
(zero-config small-repo invariant).
|
|
71
|
+
|
|
65
72
|
Schema aliases (staging schema → canonical schema) can be configured in
|
|
66
73
|
.sqlcg.toml under sqlcg.schema_aliases, e.g. da_tmp = "da".
|
|
67
74
|
|
|
@@ -80,6 +87,7 @@ Schema aliases (staging schema → canonical schema) can be configured in
|
|
|
80
87
|
| --debug | BOOLEAN | No | No | False | Show detailed log output during indexing |
|
|
81
88
|
| --profile / --no-profile | BOOLEAN | No | No | False | Emit per-stage timing after indexing |
|
|
82
89
|
| --include-working-tree | BOOLEAN | No | No | False | Index the working tree including uncommitted changes. Marks freshness as 'indexed with working-tree changes'. |
|
|
90
|
+
| --detach | BOOLEAN | No | No | False | When routing through a live server, return immediately after enqueueing (fire-and-forget). Default is to wait for the index to complete. |
|
|
83
91
|
|
|
84
92
|
## `sqlcg reindex`
|
|
85
93
|
|
|
@@ -548,7 +556,12 @@ sqlcg mcp status [OPTIONS]
|
|
|
548
556
|
Print server status JSON (connects to control socket).
|
|
549
557
|
|
|
550
558
|
Returns JSON with fields: running, pid, db_path, indexed_sha, head_sha,
|
|
551
|
-
stale_by_commits, connected_clients, uptime when a server
|
|
559
|
+
stale_by_commits, connected_clients, uptime, writer_queue when a server
|
|
560
|
+
is live.
|
|
561
|
+
|
|
562
|
+
The status response is length-prefixed framed (v1.3.0, B3) so large
|
|
563
|
+
writer_queue payloads are received in full — the client uses the
|
|
564
|
+
recv-exactly makefile+readline+read(n) pattern, NOT a single recv(4096).
|
|
552
565
|
|
|
553
566
|
When no server is found: {"running": false}.
|
|
554
567
|
When the PID file exists with a live process but the socket is unavailable:
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
# Feature Plan: #29 Live-Test Follow-up Fixes (fold into PR #51 / v1.3.0)
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
Three correctness bugs (plus one cosmetic message fix) surfaced during live-DWH
|
|
5
|
+
testing of the open PR #51 branch `feat/fix-issue-29-single-writer-queue`
|
|
6
|
+
(v1.3.0 — single-writer queue + RO→RW escalation). These fold into the **unreleased**
|
|
7
|
+
PR #51; the version stays **1.3.0** (no separate bump — see Version note below).
|
|
8
|
+
|
|
9
|
+
Source of findings: GitHub issue [#29](https://github.com/Warhorze/sql-code-graph/issues/29)
|
|
10
|
+
live-test comment. Bug B is already documented as an architecture follow-up in
|
|
11
|
+
`ARCHITECTURE_REVIEW.md` §20.2 ("Deviation 1 — MCP `index_repo` tool inline escalation").
|
|
12
|
+
|
|
13
|
+
## Version note (RESOLVED — stays 1.3.0)
|
|
14
|
+
These are bug fixes to code that has **not yet shipped** (PR #51 has no reviews and is
|
|
15
|
+
not merged). Per CLAUDE.md SemVer, the version stays **1.3.0** — there is no released
|
|
16
|
+
1.3.0 to patch against, so no `1.3.1`. Do **not** bump `pyproject.toml` / `__init__.py`
|
|
17
|
+
/ `uv.lock`. Decision confirmed (see Resolved Decisions §D1): fold into PR #51, version
|
|
18
|
+
unchanged.
|
|
19
|
+
|
|
20
|
+
## Scope
|
|
21
|
+
|
|
22
|
+
### In Scope
|
|
23
|
+
- **Bug A** — resolve `--dialect auto` (and any repo-config-derived client-side value)
|
|
24
|
+
**before** building the socket WriterRequest in `reindex.py` and `index.py`.
|
|
25
|
+
- **Bug B** — store the path `init_backend(db_path)` received in a module-level variable
|
|
26
|
+
and use it for all escalation/de-escalation in `tools.py`; audit every `get_db_path()`
|
|
27
|
+
call in `tools.py` / `writer.py` / `server.py` for the same default-path leak.
|
|
28
|
+
- **Bug C** — `install-hooks` upgrades an sqlcg-owned hook in place when the rendered
|
|
29
|
+
content differs from the current template; prints `Upgraded git hook: …`. True
|
|
30
|
+
idempotency (identical rendered content) stays a silent skip. Foreign-hook warning
|
|
31
|
+
path unchanged.
|
|
32
|
+
- **Minor D** — reword the hook fallback message from "server busy/locked" to a
|
|
33
|
+
failure-agnostic "reindex failed".
|
|
34
|
+
- Regression tests for A, B, C, D that assert **observable output** (payload contents,
|
|
35
|
+
default-path untouched, stdout message, hook file content).
|
|
36
|
+
|
|
37
|
+
### Non-Goals
|
|
38
|
+
- **The kuzu 0.11.3 `KU_UNREACHABLE` crash** (csr_node_group.cpp:411 on a second
|
|
39
|
+
consecutive reindex). It reproduces on the direct no-server path on a fresh DB →
|
|
40
|
+
upstream/pre-existing, tracked separately in the issue comment. **No kuzu upgrade,
|
|
41
|
+
no DB-suspect guard in this plan.**
|
|
42
|
+
- The **deeper §20.2 escalation-architecture fixes** (escalation outside `backend_lock`,
|
|
43
|
+
RW-lock leak on the `index_repo` failure path, the dead `_set_backend_lock` /
|
|
44
|
+
`_backend_lock` stub). Bug B as scoped by the maintainer is *narrow*: "use the init
|
|
45
|
+
path, audit `get_db_path`". §20.2 demands more. See "Design — Bug B" for the boundary
|
|
46
|
+
and the explicit decision; the full §20.2 remediation is **out of scope here** and
|
|
47
|
+
remains a tracked v1.3.x follow-up (resolved decision D3).
|
|
48
|
+
- No version bump (see Version note).
|
|
49
|
+
- **Performance invariants are untouched.** None of these fixes go near
|
|
50
|
+
[`base.py`](src/sqlcg/parsers/base.py) or [`indexer.py`](src/sqlcg/indexer/indexer.py)
|
|
51
|
+
hot paths. The diff is confined to two CLI commands, `tools.py`, `git.py`, and tests.
|
|
52
|
+
The developer must confirm `git diff --stat` shows zero changes to `base.py` and to
|
|
53
|
+
`indexer.py`'s `_extract_column_lineage` / `_upsert_parsed_file` / `_build_file_rows`
|
|
54
|
+
/ `_flush_row_batch`.
|
|
55
|
+
|
|
56
|
+
## Design
|
|
57
|
+
|
|
58
|
+
### Bug A — `--dialect auto` leaks through the routed reindex/index
|
|
59
|
+
|
|
60
|
+
**Root cause (verified in code):**
|
|
61
|
+
- [`reindex.py`](src/sqlcg/cli/commands/reindex.py) calls
|
|
62
|
+
`_try_route_reindex_via_server(... dialect=dialect ...)` at **lines 86-93**, which sends
|
|
63
|
+
`payload["dialect"] = dialect` (line 227). The `if dialect == "auto": dialect = get_dialect(path)`
|
|
64
|
+
resolution is at **lines 98-99 — AFTER** the routing call. So when a server is live the
|
|
65
|
+
WriterRequest carries the literal `"auto"`.
|
|
66
|
+
- [`index.py`](src/sqlcg/cli/commands/index.py) has the **same ordering bug**:
|
|
67
|
+
`_try_route_index_via_server(... dialect=dialect ...)` at **lines 113-118**
|
|
68
|
+
(payload at line 230) runs **before** the `if dialect == "auto"` resolution at
|
|
69
|
+
**lines 155-156**. The routed full index *appeared* correct in live testing only
|
|
70
|
+
because the synthetic DDL happened to parse acceptably; the code order is still wrong
|
|
71
|
+
and must be fixed for parity.
|
|
72
|
+
|
|
73
|
+
**Server-side confirmation (no fix needed there):** the drain loop
|
|
74
|
+
([`writer.py`](src/sqlcg/server/writer.py) lines 528-605) passes `req.dialect`
|
|
75
|
+
verbatim to `index_repo` / `resync_changed`. There is no server-side `auto` resolution
|
|
76
|
+
— the server cannot resolve `auto` because `get_dialect(path)` reads the repo's
|
|
77
|
+
`.sqlcg.toml` and that is a client-side concern. The fix **must** be client-side.
|
|
78
|
+
`"Unknown dialect 'auto'"` is raised downstream by sqlglot's dialect lookup once the
|
|
79
|
+
literal `"auto"` reaches the parser.
|
|
80
|
+
|
|
81
|
+
**Fix:** in **both** `reindex.py` and `index.py`, move the
|
|
82
|
+
`if dialect == "auto": dialect = get_dialect(path)` resolution **above** the
|
|
83
|
+
`_try_route_*_via_server(...)` call so the routing payload always carries a concrete
|
|
84
|
+
dialect (or an explicit `None`/named dialect the user passed). After the move:
|
|
85
|
+
- `reindex.py`: resolve dialect right after `path = path.resolve()` (line 78), before
|
|
86
|
+
line 86. `get_dialect` is already imported in the function-local import at line 73.
|
|
87
|
+
- `index.py`: resolve dialect right after `path = path.resolve()` (line 110), before
|
|
88
|
+
line 113. `get_dialect` is already imported at module level (line 19).
|
|
89
|
+
- The duplicate resolution further down (reindex.py 98-99, index.py 155-156) is then
|
|
90
|
+
**removed** (it becomes dead — the value is already resolved). Confirm via grep that
|
|
91
|
+
exactly one `if dialect == "auto"` remains per file, and it is before the route call.
|
|
92
|
+
|
|
93
|
+
**Boundary note:** "auto" is the only repo-config-derived value carried in the payload
|
|
94
|
+
today (`root`, `from`, `to`, `wait` are not config-derived). No other client-side
|
|
95
|
+
`.sqlcg.toml` lookup needs hoisting. Document this so the developer does not over-reach.
|
|
96
|
+
|
|
97
|
+
### Bug B — RW escalation ignores the path `init_backend()` received
|
|
98
|
+
|
|
99
|
+
**Root cause (verified in code):**
|
|
100
|
+
- [`tools.py`](src/sqlcg/server/tools.py) `init_backend(db_path)` (line 128) opens the
|
|
101
|
+
backend on `path = db_path or str(get_db_path())` (line 151) but stores **only**
|
|
102
|
+
`_backend` and `_serving_ro` (lines 168-169). The `path` is **not** retained.
|
|
103
|
+
- The MCP tool `index_repo` (line 547) recomputes `db_path_str = str(get_db_path())`
|
|
104
|
+
(**line 579**) and passes that to `_get_or_escalate_rw` (line 581) and
|
|
105
|
+
`_de_escalate_to_ro_from_tool` (line 614). So escalation opens whatever
|
|
106
|
+
`get_db_path()` returns — i.e. the default `~/.sqlcg/graph.db` — **not** the DB
|
|
107
|
+
`init_backend` actually opened.
|
|
108
|
+
|
|
109
|
+
**Live impact (reproduced):** `tests/e2e/test_mcp_tools.py` does
|
|
110
|
+
`init_backend(str(tmp_path / "test.db"))` (line 30) then `index_repo(...)` (line 33).
|
|
111
|
+
The escalation opened the user's real `~/.sqlcg/graph.db`, wrote
|
|
112
|
+
`tests/fixtures/synthetic` into it during a pytest run, and held the live DB lock —
|
|
113
|
+
blocking a real server start.
|
|
114
|
+
|
|
115
|
+
**Fix:**
|
|
116
|
+
1. Add a module-level variable `_init_db_path: str | None = None` next to `_backend`
|
|
117
|
+
/ `_serving_ro` (tools.py lines 101-114).
|
|
118
|
+
2. In `init_backend`, set `_init_db_path = path` (the resolved `db_path or
|
|
119
|
+
str(get_db_path())`) under the existing `global` declaration (line 150 — add
|
|
120
|
+
`_init_db_path` to it).
|
|
121
|
+
3. In `shutdown_backend` (line 181), reset `_init_db_path = None` alongside the other
|
|
122
|
+
globals (add to the `global` at line 187).
|
|
123
|
+
4. Add a private accessor `_escalation_db_path() -> str` that returns `_init_db_path`
|
|
124
|
+
when set, else `str(get_db_path())` (preserves the no-`init_backend` direct-call
|
|
125
|
+
path used by some unit tests). **This is a new function — it MUST have a grep-confirmed
|
|
126
|
+
call site before the PR opens** (the `index_repo` tool is its call site; see step).
|
|
127
|
+
5. In `index_repo` (line 579), replace `db_path_str = str(get_db_path())` with
|
|
128
|
+
`db_path_str = _escalation_db_path()`. The downstream `_get_or_escalate_rw(db_path_str, …)`
|
|
129
|
+
(581) and `_de_escalate_to_ro_from_tool(db_path_str)` (614) then use the init path.
|
|
130
|
+
|
|
131
|
+
**Audit of every `get_db_path()` call (verified list):**
|
|
132
|
+
| File:line | Context | Verdict |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `tools.py:14` | import | n/a |
|
|
135
|
+
| `tools.py:151` | `init_backend` fallback when `db_path is None` | **correct** — this is the source of truth; capture it into `_init_db_path` |
|
|
136
|
+
| `tools.py:579` | `index_repo` escalation | **LEAK — fix** (step 5) |
|
|
137
|
+
| `writer.py` `escalate_to_rw` / `de_escalate_to_ro` | take `db_path: str` as a parameter; the drain passes the `drain_loop(db_path=…)` argument (writer.py 510-511, 628-629) | **correct** — drain path threads the real db_path; do not change |
|
|
138
|
+
| `server.py:140,221` | status reply `db_path` display | display-only; reflects `db_path or _get_db_path()` from the server CLI arg — **correct** |
|
|
139
|
+
| `server.py:509,525` | `_run_with_control` resolves `_db_path_obj = Path(db_path) if db_path else get_db_path()` and threads it into `drain_loop` | **correct** — the drain receives the server's actual db_path |
|
|
140
|
+
|
|
141
|
+
So the **only** leak is `tools.py:579` (the MCP `index_repo` tool). The drain/CLI-op
|
|
142
|
+
path is already correct because `db_path` is threaded as a parameter, not re-fetched.
|
|
143
|
+
|
|
144
|
+
**§20.2 boundary (explicit):** this fix corrects the *path* used by the existing
|
|
145
|
+
`index_repo` tool escalation. It does **not** move escalation under `backend_lock`, does
|
|
146
|
+
**not** add a `finally`-based de-escalation, and does **not** wire/remove the dead
|
|
147
|
+
`_set_backend_lock` / `_backend_lock` stub. Those are the §20.2 remediation, scoped out
|
|
148
|
+
here (resolved decision D3). The Bug B fix makes the tool target the correct DB; it does not claim
|
|
149
|
+
to make the tool concurrency-safe.
|
|
150
|
+
|
|
151
|
+
### Bug C — `install-hooks` silently refuses to upgrade old sqlcg hooks
|
|
152
|
+
|
|
153
|
+
**Root cause (verified in code):** [`git.py`](src/sqlcg/cli/commands/git.py)
|
|
154
|
+
`_install_single_hook` (line 91): when the file exists and `spec.sentinel in existing_content`
|
|
155
|
+
(line 103), it returns silently (line 105). The sentinel string (e.g.
|
|
156
|
+
`# sqlcg post-checkout hook`) is **identical across versions**, so a v1.2.x hook (no
|
|
157
|
+
`--notify`, blocks `git checkout` ~2 min against a live server) survives a v1.3.0
|
|
158
|
+
`install-hooks` with exit 0 and no output.
|
|
159
|
+
|
|
160
|
+
**Fix:** the sentinel proves sqlcg ownership. When the sentinel is present:
|
|
161
|
+
- Render the current template: `script = spec.script_template.format(sqlcg_bin=sqlcg_bin)`
|
|
162
|
+
(this already happens at line 99). The rendered script embeds the resolved binary path,
|
|
163
|
+
so **the comparison MUST be against the fully rendered `script`**, not the raw template.
|
|
164
|
+
- If `existing_content == script` → identical → **silent skip** (true idempotency, R9).
|
|
165
|
+
- If `existing_content != script` → sqlcg-owned but stale → **overwrite** (`write_text` +
|
|
166
|
+
`chmod(0o755)`) and print `[green]Upgraded git hook:[/green] .git/hooks/<filename>`.
|
|
167
|
+
|
|
168
|
+
When the sentinel is **absent** (foreign hook) → the existing warning + manual-append
|
|
169
|
+
path (lines 106-118) is **unchanged**.
|
|
170
|
+
|
|
171
|
+
**Comparison subtlety:** `existing_content` is the raw file text; `script` is the
|
|
172
|
+
rendered template. A hook installed by a different sqlcg version differs in the command
|
|
173
|
+
line (e.g. missing `--notify`, or the old "server busy/locked" message — see Minor D),
|
|
174
|
+
so the `!=` will fire and trigger the upgrade. A hook installed by the *current* binary
|
|
175
|
+
from the *same* resolved path is byte-identical → silent skip. Note the binary path is
|
|
176
|
+
embedded, so two installs from different binary locations will (correctly) re-render and
|
|
177
|
+
upgrade.
|
|
178
|
+
|
|
179
|
+
### Minor D — misleading hook fallback message
|
|
180
|
+
|
|
181
|
+
**Root cause (verified in code):** both hook templates in `_HOOKS`
|
|
182
|
+
([`git.py`](src/sqlcg/cli/commands/git.py) lines 36-37 post-checkout, lines 53 & 56
|
|
183
|
+
post-merge) print `sqlcg: graph not updated (server busy/locked) -- run 'sqlcg mcp status'`
|
|
184
|
+
on **any** reindex non-zero exit, including a KuzuDB storage crash (KU_UNREACHABLE), which
|
|
185
|
+
has nothing to do with the server being busy.
|
|
186
|
+
|
|
187
|
+
**Fix:** reword all three occurrences to a failure-agnostic message:
|
|
188
|
+
`sqlcg: graph not updated (reindex failed) -- run 'sqlcg mcp status'`.
|
|
189
|
+
|
|
190
|
+
**Interaction with Bug C:** changing the template text changes the **rendered** content,
|
|
191
|
+
so every previously-installed hook (with the old message) now differs from the new
|
|
192
|
+
template → Bug C's upgrade path will (correctly) overwrite it on the next `install-hooks`.
|
|
193
|
+
This is the intended behaviour and is exactly why C and D ship together. The
|
|
194
|
+
`test_git_hooks_notify.py` content guards
|
|
195
|
+
([`tests/unit/test_git_hooks_notify.py`](tests/unit/test_git_hooks_notify.py)) assert the
|
|
196
|
+
old "server busy/locked" string — they MUST be updated to the new wording (see Test
|
|
197
|
+
Strategy; not a regression, a deliberate text change).
|
|
198
|
+
|
|
199
|
+
### Docs impact
|
|
200
|
+
`docs/cli.md` is auto-generated by
|
|
201
|
+
[`scripts/generate_cli_docs.sh`](scripts/generate_cli_docs.sh) from **command docstrings
|
|
202
|
+
and option metadata only**. Verified: it does **not** embed `install-hooks` stdout
|
|
203
|
+
messages nor the hook template text (the `install-hooks` section at docs/cli.md:635-653
|
|
204
|
+
is the docstring + the `--repo` option). Bug C changes only **stdout** (a new
|
|
205
|
+
`console.print`), and Minor D changes only the **hook template string** — neither touches
|
|
206
|
+
a docstring or an option. **No `docs/cli.md` regeneration is required.** The developer
|
|
207
|
+
must confirm by diffing: `bash scripts/generate_cli_docs.sh` then `git diff docs/cli.md`
|
|
208
|
+
must show **no change**. (If it does, a docstring was inadvertently touched — investigate.)
|
|
209
|
+
|
|
210
|
+
## Implementation Steps
|
|
211
|
+
|
|
212
|
+
### Phase 1: Bug A — client-side dialect resolution before routing
|
|
213
|
+
**Step 1.1**: In `reindex.py`, move `if dialect == "auto": dialect = get_dialect(path)`
|
|
214
|
+
to immediately after `path = path.resolve()` (after line 78, before the route call at
|
|
215
|
+
line 86). Remove the now-dead resolution at lines 98-99.
|
|
216
|
+
- Files: [`reindex.py`](src/sqlcg/cli/commands/reindex.py)
|
|
217
|
+
- Acceptance: `_try_route_reindex_via_server` receives a resolved dialect; grep shows
|
|
218
|
+
exactly one `if dialect == "auto"` in the file, positioned before line ~86.
|
|
219
|
+
|
|
220
|
+
**Step 1.2**: In `index.py`, move `if dialect == "auto": dialect = get_dialect(path)`
|
|
221
|
+
to immediately after `path = path.resolve()` (after line 110, before the route call at
|
|
222
|
+
line 113). Remove the now-dead resolution at lines 155-156.
|
|
223
|
+
- Files: [`index.py`](src/sqlcg/cli/commands/index.py)
|
|
224
|
+
- Acceptance: `_try_route_index_via_server` receives a resolved dialect; grep shows
|
|
225
|
+
exactly one `if dialect == "auto"` in the file, positioned before line ~113.
|
|
226
|
+
|
|
227
|
+
### Phase 2: Bug B — escalation uses the init path
|
|
228
|
+
**Step 2.1**: Add `_init_db_path: str | None = None` module global (tools.py ~line 114);
|
|
229
|
+
set it in `init_backend` (add to `global` at 150; assign after computing `path` at 151);
|
|
230
|
+
reset to `None` in `shutdown_backend` (add to `global` at 187).
|
|
231
|
+
- Files: [`tools.py`](src/sqlcg/server/tools.py)
|
|
232
|
+
- Acceptance: `init_backend(p)` then reading the module global returns `p`;
|
|
233
|
+
`shutdown_backend()` clears it.
|
|
234
|
+
|
|
235
|
+
**Step 2.2**: Add `_escalation_db_path() -> str` returning `_init_db_path or str(get_db_path())`.
|
|
236
|
+
Replace `db_path_str = str(get_db_path())` at line 579 with `db_path_str = _escalation_db_path()`.
|
|
237
|
+
- Files: [`tools.py`](src/sqlcg/server/tools.py)
|
|
238
|
+
- Acceptance: grep shows `_escalation_db_path` defined once and called at exactly one
|
|
239
|
+
site (`index_repo`); no behavioural change when `init_backend` was never called
|
|
240
|
+
(falls back to `get_db_path()`).
|
|
241
|
+
|
|
242
|
+
### Phase 3: Bug C + Minor D — hook upgrade + reworded message
|
|
243
|
+
**Step 3.1 (Minor D)**: Reword the fallback message in all three template occurrences
|
|
244
|
+
to `sqlcg: graph not updated (reindex failed) -- run 'sqlcg mcp status'`.
|
|
245
|
+
- Files: [`git.py`](src/sqlcg/cli/commands/git.py) (lines 36-37, 53, 56)
|
|
246
|
+
- Acceptance: no occurrence of `server busy/locked` remains in `git.py`.
|
|
247
|
+
|
|
248
|
+
**Step 3.2 (Bug C)**: In `_install_single_hook`, replace the silent-skip branch (lines
|
|
249
|
+
103-105) with: if sentinel present and `existing_content == script` → silent return; if
|
|
250
|
+
sentinel present and content differs → `write_text(script)` + `chmod(0o755)` + print
|
|
251
|
+
`Upgraded git hook: .git/hooks/<filename>`. Foreign-hook branch unchanged.
|
|
252
|
+
- Files: [`git.py`](src/sqlcg/cli/commands/git.py)
|
|
253
|
+
- Acceptance: identical rendered content → no output, file unchanged; sentinel-present
|
|
254
|
+
stale content → file overwritten with current `script`, "Upgraded git hook:" printed;
|
|
255
|
+
no-sentinel file → unchanged warning path.
|
|
256
|
+
|
|
257
|
+
### Phase 4: Tests + verification
|
|
258
|
+
**Step 4.1**: Bug A regression — assert the WriterRequest never carries `"auto"`.
|
|
259
|
+
**Step 4.2**: Bug B regression — `init_backend(tmp)` + escalating `index_repo` must not
|
|
260
|
+
touch the default path.
|
|
261
|
+
**Step 4.3**: Bug C/D tests — upgrade overwrites stale hook + prints; idempotent skip;
|
|
262
|
+
foreign warning unchanged; new message present.
|
|
263
|
+
**Step 4.4**: In `test_git_hooks_notify.py`, add a new negative assertion that
|
|
264
|
+
`"server busy/locked"` does NOT appear in any hook template. No existing guard
|
|
265
|
+
asserts the old wording (current tests cover `--notify`, `>&2`, and `|| true`
|
|
266
|
+
absence only), so nothing needs updating — only the new negative assertion is added.
|
|
267
|
+
Also add a positive assertion that all templates contain `"(reindex failed)"`.
|
|
268
|
+
**Step 4.5**: Run perf invariant guards + full suite + ruff + pyright; confirm
|
|
269
|
+
`git diff --stat` excludes `base.py` and indexer hot paths; confirm
|
|
270
|
+
`generate_cli_docs.sh` produces no `docs/cli.md` diff.
|
|
271
|
+
|
|
272
|
+
## Test Strategy
|
|
273
|
+
|
|
274
|
+
### Unit tests
|
|
275
|
+
- **Bug A** (`tests/integration/`, new file `test_dialect_auto_resolution.py` — these
|
|
276
|
+
tests require a live Unix-socket thread to capture the on-wire payload and belong in
|
|
277
|
+
`tests/integration/`, not `tests/unit/`): call
|
|
278
|
+
`reindex_cmd(..., dialect="auto")` and `index_cmd(..., dialect="auto")` with a live
|
|
279
|
+
fake socket server (reuse the in-process accept pattern from
|
|
280
|
+
[`tests/integration/test_reindex_via_server.py`](tests/integration/test_reindex_via_server.py)
|
|
281
|
+
lines 114-255 — a thread that accepts, reads the framed payload, decodes JSON). Assert
|
|
282
|
+
`json.loads(payload)["dialect"] != "auto"` and equals `get_dialect(repo_root)` (use a
|
|
283
|
+
fixture repo with a `.sqlcg.toml` declaring e.g. `dialect = "postgres"` so the assertion
|
|
284
|
+
is non-vacuous — `assert payload["dialect"] == "postgres"`). Add the symmetric index
|
|
285
|
+
case. **Observable assertion: the exact dialect string on the wire.**
|
|
286
|
+
- **Bug B** (`tests/unit/` or `tests/e2e/test_mcp_tools.py`): `init_backend(str(tmp_path /
|
|
287
|
+
"test.db"))`, then call `index_repo(fixture)`. Assert the default path is never touched.
|
|
288
|
+
Two complementary observable checks (use both):
|
|
289
|
+
1. `monkeypatch` `sqlcg.server.tools.get_db_path` to a sentinel that **raises** if called
|
|
290
|
+
during escalation (proves `_escalation_db_path` used `_init_db_path`, not
|
|
291
|
+
`get_db_path()`). Note `init_backend` itself calls `get_db_path` only on the
|
|
292
|
+
`db_path is None` branch — since we pass a path, it must not be called at all in this
|
|
293
|
+
test → the raising sentinel is safe across the whole call.
|
|
294
|
+
2. Record the default `~/.sqlcg/graph.db` mtime (or its non-existence) before and after;
|
|
295
|
+
assert unchanged / still-absent. Guards the live-observed symptom directly.
|
|
296
|
+
**Teardown requirement**: call `shutdown_backend()` after `index_repo` returns (e.g.
|
|
297
|
+
in a `finally` block or pytest fixture teardown) so the `_init_db_path` module global
|
|
298
|
+
is reset between tests. Without this, a later `init_backend(None)` call in a subsequent
|
|
299
|
+
test inherits the stale path and produces a misleading pass.
|
|
300
|
+
- **Bug C** (`tests/unit/test_git_hooks.py` — existing, extend it): three cases —
|
|
301
|
+
(a) pre-write a stale sqlcg hook (sentinel + old "server busy/locked" text), run
|
|
302
|
+
`install-hooks`, assert file now equals the current rendered template AND stdout contains
|
|
303
|
+
`Upgraded git hook:`; (b) write the current rendered hook, run again, assert no output and
|
|
304
|
+
byte-identical file (idempotency); (c) write a foreign hook (no sentinel), assert the
|
|
305
|
+
warning path fires and the file is unchanged. **Observable: file content + captured stdout.**
|
|
306
|
+
- **Minor D** (`tests/unit/test_git_hooks_notify.py` — existing): add a new
|
|
307
|
+
negative assertion that no hook template contains `"server busy/locked"`. No
|
|
308
|
+
existing guard asserts the old wording (current tests only check `--notify`, `>&2`,
|
|
309
|
+
and absence of `|| true`), so nothing needs updating — only two assertions are added:
|
|
310
|
+
one asserting all templates contain `"(reindex failed)"` and one asserting none
|
|
311
|
+
contain `"server busy/locked"`.
|
|
312
|
+
|
|
313
|
+
### Integration / e2e
|
|
314
|
+
- The existing [`tests/integration/test_reindex_via_server.py`](tests/integration/test_reindex_via_server.py)
|
|
315
|
+
and `tests/e2e/test_mcp_tools.py` must stay green; `test_mcp_tools.py` is the file that
|
|
316
|
+
surfaced Bug B live, so its green state after the fix is itself a guard.
|
|
317
|
+
|
|
318
|
+
### Invariant guards (must stay green, untouched)
|
|
319
|
+
[`test_perf_scaling_guard.py`](tests/unit/test_perf_scaling_guard.py),
|
|
320
|
+
[`test_T09_01_qualify_once.py`](tests/unit/test_T09_01_qualify_once.py),
|
|
321
|
+
[`test_bulk_upsert_invariant.py`](tests/unit/test_bulk_upsert_invariant.py),
|
|
322
|
+
[`test_upsert_batch_invariant.py`](tests/unit/test_upsert_batch_invariant.py).
|
|
323
|
+
|
|
324
|
+
## Acceptance Criteria
|
|
325
|
+
- [ ] **A**: With a live server, `sqlcg reindex --dialect auto <repo>` sends a WriterRequest
|
|
326
|
+
whose `dialect` is the resolved dialect (never `"auto"`); same for `sqlcg index
|
|
327
|
+
--dialect auto`. Regression test asserts the on-wire payload.
|
|
328
|
+
- [ ] **A**: Each of `reindex.py` / `index.py` has exactly one `if dialect == "auto"`
|
|
329
|
+
block, positioned before the socket-route call.
|
|
330
|
+
- [ ] **B**: `init_backend(tmp)` followed by an escalating `index_repo` never opens,
|
|
331
|
+
writes, or locks the default `~/.sqlcg/graph.db`. Regression test proves it via a
|
|
332
|
+
raising-sentinel monkeypatch AND an mtime/absence check.
|
|
333
|
+
- [ ] **B**: `_escalation_db_path` has exactly one grep-confirmed call site.
|
|
334
|
+
- [ ] **B**: every `get_db_path()` call in `tools.py`/`writer.py`/`server.py` is either
|
|
335
|
+
the captured init source (tools.py:151), a display value, or threaded as a parameter
|
|
336
|
+
— the audit table holds.
|
|
337
|
+
- [ ] **C**: `install-hooks` over a stale sqlcg-owned hook overwrites it and prints
|
|
338
|
+
`Upgraded git hook: .git/hooks/<name>`; over an identical hook it is silent and the
|
|
339
|
+
file is byte-unchanged; over a foreign (no-sentinel) hook it prints the unchanged
|
|
340
|
+
warning and leaves the file untouched.
|
|
341
|
+
- [ ] **C**: the upgrade comparison is against the fully **rendered** template (binary
|
|
342
|
+
path embedded), not the raw template.
|
|
343
|
+
- [ ] **D**: no hook template prints "server busy/locked"; all three print
|
|
344
|
+
`(reindex failed)`. `test_git_hooks_notify.py` updated accordingly.
|
|
345
|
+
- [ ] Version stays 1.3.0 (no bump to `pyproject.toml` / `__init__.py` / `uv.lock`).
|
|
346
|
+
- [ ] `git diff --stat` shows no change to `base.py` or to `indexer.py`'s hot-path methods.
|
|
347
|
+
- [ ] `bash scripts/generate_cli_docs.sh` yields no `docs/cli.md` diff.
|
|
348
|
+
- [ ] Full suite + ruff + pyright clean; the four invariant guards green.
|
|
349
|
+
|
|
350
|
+
## Risks and Mitigations
|
|
351
|
+
| Risk | Mitigation |
|
|
352
|
+
|---|---|
|
|
353
|
+
| Bug B fix mistaken for full §20.2 remediation | Plan explicitly scopes Bug B to "use init path + audit"; §20.2 lock/finally/stub work is out of scope (resolved decision D3). Acceptance criteria do not claim concurrency safety. |
|
|
354
|
+
| Bug C upgrade compares raw template, not rendered → false "identical" or false "upgrade" | Step 3.2 + acceptance criterion mandate comparing the rendered `script`; binary path is embedded. |
|
|
355
|
+
| Minor D text change unexpectedly fails existing hook-content tests | Step 4.4 updates `test_git_hooks_notify.py` deliberately; this is an intended text change, not a regression. |
|
|
356
|
+
| Bug A: removing the second `auto` resolution leaves a stale path on a code path that bypasses routing | The route call returns early when handled; the direct path now relies on the hoisted resolution. Both reindex.py and index.py keep exactly one resolution that runs unconditionally before routing — verified the route call does not consume/clear `dialect`. |
|
|
357
|
+
| Hoisting `get_dialect` in `reindex.py` uses the function-local import | `get_dialect` is already in the line-73 import tuple; the hoisted call sits after that import. Confirm the import precedes the new call site. |
|
|
358
|
+
|
|
359
|
+
## Rollout / Rollback
|
|
360
|
+
- All four fixes fold into PR #51 (no separate PR, no separate version). Per MEMORY
|
|
361
|
+
"Separate PRs, don't fold": this PR has **no reviews yet**, so folding follow-up fixes
|
|
362
|
+
for the same unreleased feature into it is appropriate (the rule targets folding new
|
|
363
|
+
work into an *already-reviewed* PR).
|
|
364
|
+
- Rollback: revert the four commits; the v1.3.0 single-writer feature is unaffected
|
|
365
|
+
(these are additive corrections).
|
|
366
|
+
|
|
367
|
+
### Resolved Decisions (2026-06-04 — shepherd, user-approved)
|
|
368
|
+
All three pre-implementation questions are resolved. None blocks implementation.
|
|
369
|
+
|
|
370
|
+
- **D1 — Version stays 1.3.0, no bump.** These fixes fold into the unreleased v1.3.0
|
|
371
|
+
PR #51 (no reviews yet). Do not touch `pyproject.toml` / `__init__.py` / `uv.lock`.
|
|
372
|
+
See Version note above.
|
|
373
|
+
- **D2 — Bug C overwrite prints a line (not silent).** On the sentinel-present /
|
|
374
|
+
content-differs upgrade path, print `Upgraded git hook: .git/hooks/<name>`. Silent
|
|
375
|
+
upgrades of executable hooks are worse than one line of output. True idempotency
|
|
376
|
+
(identical rendered content) remains a silent skip.
|
|
377
|
+
- **D3 — §20.2 escalation-architecture remediation stays OUT of scope.** This plan
|
|
378
|
+
covers bugs A-D only. The deeper `ARCHITECTURE_REVIEW.md` §20.2 work — (a) escalation
|
|
379
|
+
outside `backend_lock`, (b) RW-lock leak on the `index_repo` failure path (no
|
|
380
|
+
`finally`), (c) the dead `_set_backend_lock` / `_backend_lock` stub — remains a tracked
|
|
381
|
+
separate v1.3.x follow-up. The §20.2 cross-reference is retained in Non-Goals and
|
|
382
|
+
Design — Bug B so the linkage is not lost.
|
|
383
|
+
|
|
384
|
+
## Plan Compliance — 2026-06-04 — PASS
|
|
385
|
+
|
|
386
|
+
Compliance check run by architect-planner against commits `8e1e09b` (Bug A),
|
|
387
|
+
`45643c8` (Bug B), `29d21ee` (Bug C + Minor D), with test setup in `1d4db7a`.
|
|
388
|
+
Diff base `daa7389..HEAD`, excluding plan-docs commits (`5dce5d4`, `05ac62d`,
|
|
389
|
+
`7936818`).
|
|
390
|
+
|
|
391
|
+
**Verdict: PASS — all steps implemented as planned; no deviations.**
|
|
392
|
+
|
|
393
|
+
### Step-by-step verification
|
|
394
|
+
|
|
395
|
+
- **Step 1.1 / 1.2 (Bug A)** — PASS. `if dialect == "auto": dialect = get_dialect(path)`
|
|
396
|
+
hoisted above the route call in both files; the duplicate lower resolution removed.
|
|
397
|
+
Grep confirms exactly one `auto` block per file, positioned before the route call
|
|
398
|
+
(`reindex.py:84`, `index.py:116`).
|
|
399
|
+
- **Step 2.1 (Bug B globals)** — PASS. `_init_db_path: str | None = None` added; set in
|
|
400
|
+
`init_backend` (added to `global`, assigned after `path = db_path or str(get_db_path())`);
|
|
401
|
+
reset to `None` in `shutdown_backend` (added to `global`).
|
|
402
|
+
- **Step 2.2 (Bug B accessor)** — PASS. `_escalation_db_path()` defined once, returns
|
|
403
|
+
`_init_db_path or str(get_db_path())`; called at exactly one site (`tools.py:607` in
|
|
404
|
+
`index_repo`), replacing `str(get_db_path())`. Grep-confirmed single call site.
|
|
405
|
+
- **Step 3.1 (Minor D)** — PASS. All three template occurrences reworded to
|
|
406
|
+
`(reindex failed)`; zero `server busy/locked` remain in `git.py`.
|
|
407
|
+
- **Step 3.2 (Bug C)** — PASS. Silent-skip branch replaced: identical rendered `script`
|
|
408
|
+
→ silent return; sentinel-present + differing content → `write_text(script)` +
|
|
409
|
+
`chmod(0o755)` + `Upgraded git hook:` print. Comparison is against the fully rendered
|
|
410
|
+
`script` (binary path embedded), per D2. Foreign-hook warning path unchanged.
|
|
411
|
+
- **Steps 4.1–4.4 (tests)** — PASS. All acceptance tests assert observable output, not
|
|
412
|
+
weakened:
|
|
413
|
+
- Bug A: asserts exact on-wire `payload["dialect"]` (`postgres` / `snowflake`) — non-vacuous.
|
|
414
|
+
- Bug B: raising-sentinel monkeypatch AND default-DB mtime/absence check, with
|
|
415
|
+
`shutdown_backend()` teardown to reset the global (per plan).
|
|
416
|
+
- Bug C: three cases (stale→upgrade+print, identical→silent+byte-unchanged,
|
|
417
|
+
foreign→warning+unchanged) assert file content + captured stdout.
|
|
418
|
+
- Minor D: negative (`no "server busy/locked"`) + positive (`"(reindex failed)"`)
|
|
419
|
+
assertions added in both `test_BugC_hook_upgrade.py` and `test_git_hooks_notify.py`.
|
|
420
|
+
- **Step 4.5 (verification)** — PASS:
|
|
421
|
+
- Full suite (`--ignore=tests/benchmarks`): 1026 passed, 7 skipped, 3 xfailed, 0 failures.
|
|
422
|
+
- 9 acceptance tests: all green (2 Bug A + 2 Bug B + 5 Bug C/D).
|
|
423
|
+
- ruff: clean. pyright: 0 errors / 0 warnings.
|
|
424
|
+
- `bash scripts/generate_cli_docs.sh` → `docs/cli.md`: no diff.
|
|
425
|
+
- `git diff --stat`: `base.py` and `indexer.py` UNTOUCHED.
|
|
426
|
+
|
|
427
|
+
### Resolved-decision verification
|
|
428
|
+
|
|
429
|
+
- **D1 (version stays 1.3.0)** — HONORED. No diff to `pyproject.toml`,
|
|
430
|
+
`src/sqlcg/__init__.py`, or `uv.lock`.
|
|
431
|
+
- **D2 (Bug C prints, not silent; idempotency stays silent)** — HONORED. Upgrade prints
|
|
432
|
+
`Upgraded git hook:`; identical rendered content is a silent skip.
|
|
433
|
+
- **D3 (§20.2 remediation out of scope)** — HONORED. Bug B fix changes only the escalation
|
|
434
|
+
*path*; it does not move escalation under `backend_lock`, does not add a `finally`-based
|
|
435
|
+
de-escalation, and does not wire or remove the dead `_set_backend_lock` /
|
|
436
|
+
`_backend_lock` stub (still present, untouched).
|
|
437
|
+
|
|
438
|
+
### Scope
|
|
439
|
+
|
|
440
|
+
In-scope files only: `git.py`, `index.py`, `reindex.py`, `tools.py`, and tests
|
|
441
|
+
(`test_dialect_auto_resolution.py`, `test_BugB_escalation_uses_init_path.py`,
|
|
442
|
+
`test_BugC_hook_upgrade.py`, `test_git_hooks_notify.py`). No out-of-scope source touched.
|