Skip to content

fix(parsing): 列制約で書かれた外部キーを抽出する——FK45本を0本と報告していた - #42

Merged
yohey-w merged 8 commits into
mainfrom
fix/sql-column-level-foreign-keys
Sep 14, 2026
Merged

yohey-w merged 8 commits into
mainfrom
fix/sql-column-level-foreign-keys

Conversation

@yohey-w

@yohey-w yohey-w commented Sep 13, 2026

Copy link
Copy Markdown
Owner

2026-08-22 のローカル修正(未 push だったもの)を PR 化。codd/parsing/schemas.py の FK 抽出が列レベル制約(REFERENCES を列定義側に書く形)を拾わず、実プロジェクトで FK 45本を 0本と報告していた。テスト tests/test_sql_column_level_foreign_keys.py を追加。

main(9b75802)への rebase は未実施・独立レビュー後にマージ。

🤖 Generated with Claude Code

https://claude.ai/code/session_01S3gncgj4ehJSy8fjaegVZK

@github-actions

Copy link
Copy Markdown

CoDD Audit

❌ Audit failed to run. Check workflow logs.

@github-actions

github-actions Bot commented Sep 13, 2026 •

Copy link
Copy Markdown

Coverage Matrix Report

Project: /home/runner/work/codd-dev/codd-dev
Mode: text-grep
Generated: 2026-09-14T13:08:15.003017Z

Lexicon Axis Status Hits
ai_governance_eu_act Classification rules for high-risk AI systems unknown 0
ai_governance_eu_act Prohibited AI practices unknown 0
ai_governance_eu_act Requirements for high-risk AI systems unknown 0
ai_governance_eu_act Human oversight unknown 0
ai_governance_eu_act Transparency obligations for providers and deployers of certain AI systems unknown 0
ai_governance_eu_act General-purpose AI models unknown 0
ai_governance_eu_act Conformity assessment unknown 0
ai_governance_eu_act Post-market monitoring by providers and post-market monitoring plan unknown 0
ai_governance_eu_act Fundamental rights impact assessment for high-risk AI systems unknown 0
ai_governance_eu_act Governance at Union and national level unknown 0
api_rate_limiting_caching rate_limit_strategy covered_text_match 1
api_rate_limiting_caching quota_management unknown 0
api_rate_limiting_caching throttling_response unknown 0
api_rate_limiting_caching cache_control_headers unknown 0
api_rate_limiting_caching etag_conditional_requests unknown 0
api_rate_limiting_caching cdn_edge_caching unknown 0
api_rate_limiting_caching idempotency_keys covered_text_match 1
api_rest_openapi openapi covered_text_match 25
api_rest_openapi info.version unknown 0
api_rest_openapi problem_details covered_text_match 149
api_rest_openapi query covered_text_match 17
api_rest_openapi security covered_text_match 14
api_rest_openapi schemas covered_text_match 57
api_rest_openapi content covered_text_match 85
api_rest_openapi responses covered_text_match 82
api_rest_openapi links unknown 0
api_rest_openapi idempotent covered_text_match 109
api_rest_openapi 429 unknown 0
api_rest_openapi Access-Control-Allow-Origin covered_text_match 8
api_rest_openapi Cache-Control unknown 0
api_rest_openapi parameters covered_text_match 193
api_rest_openapi callbacks unknown 0
babok stakeholder covered_text_match 2
babok goal covered_text_match 10
babok flow covered_text_match 45
babok issue covered_text_match 9
babok data covered_text_match 83
babok functional covered_text_match 21
babok non-functional covered_text_match 10
babok rule covered_text_match 14
babok constraint covered_text_match 35
babok acceptance covered_text_match 25
babok risk covered_text_match 73
babok assumption unknown 0
babok term covered_text_match 26
backend_event_cloudevents Context Attributes covered_text_match 1
backend_event_cloudevents REQUIRED Attributes covered_text_match 835
backend_event_cloudevents OPTIONAL Attributes covered_text_match 172
backend_event_cloudevents Type System covered_text_match 64
backend_event_cloudevents Extension Context Attributes unknown 0
backend_event_cloudevents Event Data covered_text_match 83
backend_event_cloudevents Protocol Binding unknown 0
backend_graphql schema covered_text_match 63
backend_graphql Type System covered_text_match 159
backend_graphql Type System Extensions unknown 0
backend_graphql query covered_text_match 54
backend_graphql mutation covered_text_match 28
backend_graphql subscription unknown 0
backend_graphql Fragments covered_text_match 1504
backend_graphql Variables covered_text_match 36
backend_graphql Directives covered_text_match 2
backend_graphql Introspection unknown 0
backend_graphql Response covered_text_match 93
backend_grpc_proto service covered_text_match 25
backend_grpc_proto message covered_text_match 70
backend_grpc_proto Scalar Value Types covered_text_match 35
backend_grpc_proto Enumerations covered_text_match 256
backend_grpc_proto stream covered_text_match 4
backend_grpc_proto status code covered_text_match 134
backend_grpc_proto Deadlines/Timeouts covered_text_match 2
backend_grpc_proto Metadata covered_text_match 16
compliance_hipaa Administrative safeguards unknown 0
compliance_hipaa Physical safeguards unknown 0
compliance_hipaa Technical safeguards covered_text_match 7
compliance_hipaa Risk analysis unknown 0
compliance_hipaa Access control unknown 0
compliance_hipaa Audit controls unknown 0
compliance_hipaa Integrity covered_text_match 7
compliance_hipaa Transmission security unknown 0
compliance_hipaa Breach notification unknown 0
compliance_hipaa Business associate contracts or other arrangements unknown 0
compliance_iso27001 context_organization unknown 0
compliance_iso27001 leadership unknown 0
compliance_iso27001 planning covered_text_match 3
compliance_iso27001 support covered_text_match 39
compliance_iso27001 operation covered_text_match 28
compliance_iso27001 performance_evaluation unknown 0
compliance_iso27001 improvement unknown 0
compliance_iso27001 risk_treatment_plan unknown 0
compliance_iso27001 SOA unknown 0
compliance_iso27001 access_control unknown 0
compliance_iso27001 cryptography unknown 0
compliance_iso27001 physical_security unknown 0
compliance_iso27001 supplier_relationships unknown 0
compliance_iso27001 incident_management unknown 0
compliance_pci_dss_4 Install and Maintain Network Security Controls unknown 0
compliance_pci_dss_4 Protect Stored Account Data unknown 0
compliance_pci_dss_4 Maintain a Vulnerability Management Program unknown 0
compliance_pci_dss_4 Restrict Access to System Components and Cardholder Data by Business Need to Know unknown 0
compliance_pci_dss_4 Regularly Monitor and Test Networks unknown 0
compliance_pci_dss_4 Support Information Security with Organizational Policies and Programs unknown 0
compliance_pci_dss_4 Protect Cardholder Data with Strong Cryptography During Transmission Over Open, Public Networks unknown 0
compliance_pci_dss_4 Develop and Maintain Secure Systems and Software unknown 0
compliance_pci_dss_4 Personnel screening unknown 0
compliance_pci_dss_4 Restrict Physical Access to Cardholder Data covered_text_match 3
compliance_pci_dss_4 Security incident response plan unknown 0
compliance_pci_dss_4 PCI DSS scope unknown 0
data_aggregation_policies collection_cardinality covered_text_match 3267
data_aggregation_policies test_data_variation unknown 0
data_aggregation_policies aggregation_function covered_text_match 115
data_aggregation_policies empty_partial_state covered_text_match 22
data_aggregation_policies grouping_dimension covered_text_match 11
data_aggregation_policies recency_selection covered_text_match 24
data_aggregation_policies source_traceability covered_text_match 173
data_eventsourcing_es_cqrs Event Sourcing unknown 0
data_eventsourcing_es_cqrs Event covered_text_match 25
data_eventsourcing_es_cqrs event store unknown 0
data_eventsourcing_es_cqrs Event Replay unknown 0
data_eventsourcing_es_cqrs Application State Storage covered_text_match 2
data_eventsourcing_es_cqrs Complete Rebuild unknown 0
data_eventsourcing_es_cqrs Temporal Query unknown 0
data_eventsourcing_es_cqrs CQRS pattern unknown 0
data_governance_appi_gdpr Lawfulness of processing unknown 0
data_governance_appi_gdpr Consent unknown 0
data_governance_appi_gdpr Purpose limitation unknown 0
data_governance_appi_gdpr Storage limitation unknown 0
data_governance_appi_gdpr Data subject rights unknown 0
data_governance_appi_gdpr Responsibility of the controller unknown 0
data_governance_appi_gdpr Processor unknown 0
data_governance_appi_gdpr Records of processing activities unknown 0
data_governance_appi_gdpr Data protection impact assessment unknown 0
data_governance_appi_gdpr Notification of a personal data breach unknown 0
data_governance_appi_gdpr Transfers of personal data to third countries or international organisations unknown 0
data_governance_appi_gdpr Data protection officer unknown 0
data_nosql_jsonschema JSON Schema Documents unknown 0
data_nosql_jsonschema Schema Vocabularies covered_text_match 7
data_nosql_jsonschema Meta-Schemas unknown 0
data_nosql_jsonschema Identifiers covered_text_match 3
data_nosql_jsonschema References covered_text_match 24
data_nosql_jsonschema Applicators covered_text_match 336
data_nosql_jsonschema Assertions covered_text_match 174
data_nosql_jsonschema Annotations covered_text_match 17
data_nosql_jsonschema Validation Keywords covered_text_match 52
data_nosql_jsonschema Format covered_text_match 30
data_relational_iso_sql schema definition covered_text_match 57
data_relational_iso_sql table definition unknown 0
data_relational_iso_sql data types covered_text_match 3
data_relational_iso_sql domain constraints covered_text_match 72
data_relational_iso_sql referential constraints covered_text_match 26
data_relational_iso_sql query expressions covered_text_match 218
data_relational_iso_sql data change statements covered_text_match 48
data_relational_iso_sql transaction statements covered_text_match 20
data_relational_iso_sql isolation levels unknown 0
data_relational_iso_sql views unknown 0
ddd_domain_driven_design ubiquitous_language unknown 0
ddd_domain_driven_design bounded_context unknown 0
ddd_domain_driven_design aggregate_design covered_text_match 5
ddd_domain_driven_design entity_value_object covered_text_match 2
ddd_domain_driven_design domain_events unknown 0
ddd_domain_driven_design repository_pattern covered_text_match 1
ddd_domain_driven_design application_service unknown 0
ddd_domain_driven_design context_mapping unknown 0
ddd_domain_driven_design anti_corruption_layer unknown 0
dora_sre_metrics deployment_frequency unknown 0
dora_sre_metrics lead_time_for_changes unknown 0
dora_sre_metrics change_failure_rate unknown 0
dora_sre_metrics mean_time_to_restore unknown 0
dora_sre_metrics slo_sli_definition unknown 0
dora_sre_metrics error_budget_policy unknown 0
dora_sre_metrics toil_reduction unknown 0
dora_sre_metrics incident_management unknown 0
i18n_unicode_cldr locale_tagging unknown 0
i18n_unicode_cldr character_encoding covered_text_match 6
i18n_unicode_cldr time_zone_handling covered_text_match 8
i18n_unicode_cldr number_currency_format unknown 0
i18n_unicode_cldr date_time_calendar unknown 0
i18n_unicode_cldr text_collation unknown 0
i18n_unicode_cldr rtl_bidi_support covered_text_match 9
i18n_unicode_cldr pluralization_rules covered_text_match 20
i18n_unicode_cldr translation_string_management covered_text_match 15
ml_model_cards model_details unknown 0
ml_model_cards intended_use unknown 0
ml_model_cards evaluation_factors covered_text_match 2
ml_model_cards performance_metrics covered_text_match 4
ml_model_cards training_data unknown 0
ml_model_cards ethical_considerations unknown 0
ml_model_cards caveats_recommendations covered_text_match 1
ml_model_cards model_versioning unknown 0
mobile_a11y_native touchscreens unknown 0
mobile_a11y_native small screen sizes unknown 0
mobile_a11y_native different input modalities unknown 0
mobile_a11y_native color contrast unknown 0
mobile_a11y_native larger text sizes unknown 0
mobile_a11y_native screen reader covered_text_match 1
mobile_a11y_native haptics unknown 0
mobile_a11y_native Reduce Motion unknown 0
mobile_android_material3 Color unknown 0
mobile_android_material3 Typography unknown 0
mobile_android_material3 Shape covered_text_match 3
mobile_android_material3 Elevation covered_text_match 1
mobile_android_material3 Motion covered_text_match 5
mobile_android_material3 Components covered_text_match 5
mobile_android_material3 Icons unknown 0
mobile_android_material3 Accessibility unknown 0
mobile_android_material3 Adaptive design covered_text_match 7
mobile_android_material3 Interaction covered_text_match 8
mobile_android_material3 Content design unknown 0
mobile_android_material3 Dynamic color unknown 0
mobile_ios_hig Navigation and search unknown 0
mobile_ios_hig Typography unknown 0
mobile_ios_hig Color unknown 0
mobile_ios_hig Accessibility unknown 0
mobile_ios_hig Playing haptics unknown 0
mobile_ios_hig Motion covered_text_match 3
mobile_ios_hig Inputs covered_text_match 3
mobile_ios_hig Layout covered_text_match 7
mobile_ios_hig Icons unknown 0
mobile_ios_hig Playing audio unknown 0
mobile_ios_hig Privacy unknown 0
mobile_ios_hig Feedback covered_text_match 9
mobile_security_masvs storage_security unknown 0
mobile_security_masvs crypto_best_practices unknown 0
mobile_security_masvs auth_session_management covered_text_match 17
mobile_security_masvs network_communication covered_text_match 14
mobile_security_masvs platform_interaction covered_text_match 1
mobile_security_masvs code_quality covered_text_match 47
mobile_security_masvs resilience covered_text_match 2
ops_cicd_pipeline declarative_config covered_text_match 19
ops_cicd_pipeline version_control unknown 0
ops_cicd_pipeline automated_apply covered_text_match 125
ops_cicd_pipeline continuous_reconciliation unknown 0
ops_cicd_pipeline drift_detection covered_text_match 8
ops_cicd_pipeline rollback unknown 0
ops_cicd_pipeline observability covered_text_match 1
ops_iac_terraform terraform covered_text_match 8
ops_iac_terraform provider covered_text_match 154
ops_iac_terraform resource covered_text_match 86
ops_iac_terraform data covered_text_match 86
ops_iac_terraform variable covered_text_match 295
ops_iac_terraform state covered_text_match 166
ops_iac_terraform module covered_text_match 376
ops_iac_terraform workspace covered_text_match 82
ops_iac_terraform backend covered_text_match 25
ops_iac_terraform Sentinel unknown 0
ops_kubernetes Workloads covered_text_match 30
ops_kubernetes Service covered_text_match 20
ops_kubernetes ConfigMap covered_text_match 85
ops_kubernetes PersistentVolume unknown 0
ops_kubernetes RBAC covered_text_match 40
ops_kubernetes Scheduling covered_text_match 1
ops_kubernetes ResourceQuota covered_text_match 6
ops_kubernetes Probe unknown 0
ops_kubernetes HorizontalPodAutoscaler covered_text_match 31
ops_kubernetes Events covered_text_match 30
ops_kubernetes Upgrade A Cluster unknown 0
ops_kubernetes Namespace covered_text_match 4
ops_observability_otel signals_traces covered_text_match 3
ops_observability_otel signals_metrics covered_text_match 19
ops_observability_otel signals_logs covered_text_match 1
ops_observability_otel context_propagation covered_text_match 13
ops_observability_otel resource_attributes covered_text_match 26
ops_observability_otel instrumentation covered_text_match 71
ops_observability_otel collector covered_text_match 2
ops_observability_otel semantic_conventions unknown 0
process_iso25010 functional_suitability unknown 0
process_iso25010 performance_efficiency unknown 0
process_iso25010 compatibility covered_text_match 5
process_iso25010 usability unknown 0
process_iso25010 reliability covered_text_match 6
process_iso25010 security covered_text_match 17
process_iso25010 maintainability unknown 0
process_iso25010 portability unknown 0
process_test_iso29119 test_concepts_definitions unknown 0
process_test_iso29119 test_processes unknown 0
process_test_iso29119 test_documentation unknown 0
process_test_iso29119 test_techniques unknown 0
process_test_iso29119 keyword_driven_testing unknown 0
process_test_iso29119 work_aided_software_testing unknown 0
twelve_factor_app codebase covered_text_match 12
twelve_factor_app dependencies covered_text_match 47
twelve_factor_app config covered_text_match 144
twelve_factor_app backing_services unknown 0
twelve_factor_app build_release_run covered_text_match 228
twelve_factor_app processes covered_text_match 1
twelve_factor_app port_binding unknown 0
twelve_factor_app concurrency covered_text_match 1
twelve_factor_app disposability unknown 0
twelve_factor_app dev_prod_parity unknown 0
twelve_factor_app logs covered_text_match 1
twelve_factor_app admin_processes unknown 0
web_a11y_wcag22_aa 1.1 Text Alternatives unknown 0
web_a11y_wcag22_aa 1.2 Time-based Media unknown 0
web_a11y_wcag22_aa 1.3 Adaptable unknown 0
web_a11y_wcag22_aa 1.4 Distinguishable unknown 0
web_a11y_wcag22_aa 2.1 Keyboard Accessible unknown 0
web_a11y_wcag22_aa 2.2 Enough Time unknown 0
web_a11y_wcag22_aa 2.3 Seizures and Physical Reactions unknown 0
web_a11y_wcag22_aa 2.4 Navigable unknown 0
web_a11y_wcag22_aa 2.5 Input Modalities unknown 0
web_a11y_wcag22_aa 3.1 Readable unknown 0
web_a11y_wcag22_aa 3.2 Predictable unknown 0
web_a11y_wcag22_aa 3.3 Input Assistance unknown 0
web_a11y_wcag22_aa 4.1 Compatible unknown 0
web_authn_webauthn registration ceremony unknown 0
web_authn_webauthn authentication ceremony covered_text_match 5
web_authn_webauthn attestation unknown 0
web_authn_webauthn user verification covered_text_match 34
web_authn_webauthn credential management unknown 0
web_authn_webauthn extensions covered_text_match 5
web_browser_compat Newly available unknown 0
web_browser_compat Widely available unknown 0
web_browser_compat Limited availability unknown 0
web_browser_compat core browser set unknown 0
web_browser_compat Baseline threshold unknown 0
web_browser_compat polyfill covered_text_match 2
web_browser_compat progressive enhancement covered_text_match 20
web_forms_html5 input element covered_text_match 159
web_forms_html5 form element covered_text_match 338
web_forms_html5 client-side form validation covered_text_match 228
web_forms_html5 autocomplete covered_text_match 1490
web_forms_html5 label element covered_text_match 272
web_forms_html5 fieldset unknown 0
web_forms_html5 inputmode covered_text_match 14
web_forms_html5 form submission covered_text_match 14
web_forms_html5 File Upload state covered_text_match 226
web_performance_core_web_vitals Largest Contentful Paint (LCP) unknown 0
web_performance_core_web_vitals Interaction to Next Paint (INP) covered_text_match 8
web_performance_core_web_vitals Cumulative Layout Shift (CLS) unknown 0
web_performance_core_web_vitals Time to First Byte (TTFB) unknown 0
web_performance_core_web_vitals First Contentful Paint (FCP) covered_text_match 32
web_performance_core_web_vitals Time to Interactive (TTI) unknown 0
web_pwa_manifest manifest members covered_text_match 248
web_pwa_manifest icons covered_text_match 122
web_pwa_manifest display covered_text_match 104
web_pwa_manifest start_url covered_text_match 625
web_pwa_manifest scope covered_text_match 22
web_pwa_manifest theme_color covered_text_match 17
web_pwa_manifest shortcuts covered_text_match 29
web_pwa_manifest share_target covered_text_match 153
web_responsive width unknown 0
web_responsive orientation unknown 0
web_responsive prefers-color-scheme covered_text_match 11
web_responsive prefers-reduced-motion covered_text_match 2
web_responsive resolution covered_text_match 9
web_responsive hover covered_text_match 14
web_responsive pointer covered_text_match 23
web_responsive aspect-ratio unknown 0
web_security_owasp broken_access_control unknown 0
web_security_owasp cryptographic_failures unknown 0
web_security_owasp injection covered_text_match 2
web_security_owasp insecure_design unknown 0
web_security_owasp security_misconfiguration unknown 0
web_security_owasp vulnerable_and_outdated_components unknown 0
web_security_owasp identification_and_authentication_failures unknown 0
web_security_owasp software_and_data_integrity_failures unknown 0
web_security_owasp security_logging_and_monitoring_failures unknown 0
web_security_owasp server_side_request_forgery_ssrf unknown 0
web_security_owasp authentication covered_text_match 5
web_security_owasp session_management unknown 0
web_security_owasp input_validation unknown 0
web_security_owasp output_encoding_and_injection_prevention unknown 0
web_seo_schemaorg Organization covered_text_match 12
web_seo_schemaorg Person covered_text_match 8
web_seo_schemaorg Product covered_text_match 7
web_seo_schemaorg Article covered_text_match 38
web_seo_schemaorg BreadcrumbList unknown 0
web_seo_schemaorg FAQPage covered_text_match 16
web_seo_schemaorg Event covered_text_match 25
web_seo_schemaorg VideoObject unknown 0

Totals: 369 axes, 176 covered signals (47.70%), 193 unknown.

SQLは外部キーを2通りで書ける:
  表制約:  FOREIGN KEY (a) REFERENCES parent (id)
  列制約:  a uuid not null references parent (id)   ← FOREIGN KEY 語が無い

従来は表制約しか拾わず、列制約だけで書かれたスキーマを
「参照整合性が無い」と誤って報告していた(実例: FK45本を0本と要約)。
列制約は PostgreSQL / MySQL / SQLite いずれでも一般的な書き方。

あわせて2件:
- SQLコメントを除去してから解析(列定義の直前にコメントが挟まると
  その列のFKだけが静かに落ちていた)
- 区切りを幅ゼロの後読みに(default gen_random_uuid(), の閉じ括弧に
  次の列が飲まれるのを防ぐ)
@yohey-w
yohey-w force-pushed the fix/sql-column-level-foreign-keys branch from 6faba83 to 85da324 Compare September 14, 2026 11:11
yohey-w and others added 7 commits September 14, 2026 20:34
列制約のFK抽出(#42)で入れたコメント除去が、文字列リテラルを跨いでいた。
`default '--'` の "--" を行コメントの開始と誤り、そこから行末までを捨てるため、
同じ行にある `FOREIGN KEY (...) REFERENCES ...` まで消えていた。
これは本PR以前は拾えていた表制約の検出を壊す回帰だった。

  create table t (sep text default '--', foreign key (p) references parent (id));
    旧: FK=1 → 本PRの初版: FK=0 → 本コミット: FK=1

コメント除去を、引用符(' " `)の中に入らない走査に置き換えた。
エスケープは SQL 標準の二重化('' や "")を終端と見ない。

あわせて列定義の切り出しを正規表現から深さ付きの走査に替えた。
`numeric(10,2)` の "," や `default 'a,b'` の "," は列の区切りではないのに、
区切りとみなしてカンマの右隣を列名として拾い、列名が "2" や "b" になっていた。
副次的に、複数行にまたがる列定義の外部キーも拾えるようになった。

回帰テストを先にRED(旧実装で5本失敗)にしてから修正した。
`ALTER TABLE ... ADD COLUMN` の列制約は未対応のまま(本PR以前も0本・回帰ではない)。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xpq9A9SFvMUky6i6NLFz2
公開の抽出経路は tree-sitter のノード文字列を _normalize_ws で1行に潰してから
_regex_foreign_keys へ渡す。潰したあとにコメントを除去すると、行コメント "--"
の終端である改行が既に消えているため、コメント以降=文の残り全部が削られる。
実コメント1行が、その下にある既存の表制約FKまで道連れにしていた。

  create table facility (
    id uuid primary key,
    -- ER図の "||" 側を…
    tenant_id uuid not null references tenant (id),
    foreign key (report_definition_id) references report_definition (id)
  );
    修正前: FK=0(コメント行以降が全部消える) → 本コミット: FK=3

コメント除去を _normalize_ws より前に移した(tree経路・正規表現フォールバック経路の両方)。

あわせて、文字列リテラルの中の REFERENCES を外部キーと読み違えないようにした。
`default 'references parent (id)'` が偽のFKとして数えられていた。
参照句を探すときだけシングルクォートの中身を空白で潰す(長さは変えないので
取り出す位置はずれない)。引用符つき識別子は列名・テーブル名なので残す。

2件とも先にRED(e3ffeb9 で失敗)にしてから修正。全 7753 passed / 2 xfailed。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xpq9A9SFvMUky6i6NLFz2
コメント除去は「引用の中に入らない」ことで成り立っている。引用の種類を
取りこぼすと、値の中の "--" を行コメントの開始と誤り、そこから行末までを
捨てて、本PR以前は拾えていた表制約の外部キーまで消してしまう。

新たに字句として扱うもの(いずれも修正前は FK=0・修正後 FK=1):
  PostgreSQL のドル引用   default $$--$$
  MySQL のバックスラッシュ default 'it\'s -- fine'
  T-SQL の角括弧識別子     [odd--name]

あわせて、表制約の検索を文字列リテラルを空白で潰した版に対して行うようにした。
`default 'FOREIGN KEY (fake) REFERENCES fake (id)'` が偽の外部キーとして
数えられていた(これは本PR以前からの誤検出で、回帰ではない)。

4件とも先にRED(b58dffc で失敗)にしてから修正。全 7757 passed / 2 xfailed。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xpq9A9SFvMUky6i6NLFz2
方言でバックスラッシュの意味が違う。
  MySQL:      'it\'s'  → \' はエスケープで、文字列はまだ閉じない
  PostgreSQL: 'C:\'    → \ はただの文字で、次の ' で閉じる

前コミットは常にエスケープと読んだため、PostgreSQL の標準文字列
`default '\'` で文字列が閉じなくなり、以降を全部リテラルとみなして
後続の表制約FKを落としていた(main=1 / 前コミット=0)。

見分けはつかないので、まずエスケープありで読み、それで閉じないときだけ
エスケープなしで読み直す。閉じない=残り全部を失う、なので閉じる読み方を採る。
両方言とも後続の外部キーを保てる。

MySQL の `default 1--2`("--" の直後に空白が無ければ減算)は取りこぼす。
空白を必須にすると PostgreSQL / SQLite の `--コメント`(空白なし・実務で
最も多い書き方)を取りこぼすため、意図的な選択としてコードにも注記した。

RED(a49bc9c で失敗)→ GREEN。全 7758 passed / 2 xfailed。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xpq9A9SFvMUky6i6NLFz2
MySQL は "--" の直後に空白を要求するので `default 1--2` は「1 から -2 を引く」。
PostgreSQL / SQLite は空白なしの `--コメント` もコメントなので、
「空白があればコメント」という規則では後者(実務で最も多い書き方)を落とす。

そこで両側で切る。直前が値の終わり(英数字・"_"・")")で、直後が数値か
開き括弧のときだけ演算子と読む。`--コメント` は直後が文字なのでコメントのまま。

  create table t (n int default 1--2, foreign key (p) references parent (id));
    main=1 → 修正前=0(コメント扱いで後続が消える) → 本コミット=1

公開経路(_extract_sql_schema)でも main と同じ1本に戻ることを確認した。
前コミットで「取りこぼす」と注記した既知の分かれ道を、注記ごと解消した。

RED(916a659 で失敗)→ GREEN。全 7759 passed / 2 xfailed。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xpq9A9SFvMUky6i6NLFz2
コメント除去は列制約(本PRで新しく拾えるようにした側)のためだけに要る仕組み。
それを表制約の経路にも通していたため、方言の字句をひとつ読み違えるたびに
本PR以前から拾えていた表制約FKまで道連れになっていた。実際、レビューのたびに
同じ形の指摘が出た('--' / ドル引用 / バックスラッシュ / 角括弧 / 1--2 / a--b)。

  create table t (a int, b int, c int as (a--b), foreign key (p) references parent (id));
    main=1 → 修正前=0 → 本コミット=1
  a-/*gap*/-b(公開経路で二重にコメント除去していた)
    main=1 → 修正前=0 → 本コミット=1

経路を分けた。表制約は元の文(文字列リテラルだけ空白化)から探し、
列制約はコメント除去済みの文から探す。これで字句判定を誤っても、
失うのは新しく増えた分だけで、従来の検出結果は構造的に減らない。
公開経路が事前に除去した版は引数で渡し、二重除去もやめた。

RED(31abfac で失敗)→ GREEN。全 7761 passed / 2 xfailed。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xpq9A9SFvMUky6i6NLFz2
前コミットは表制約を「文字列リテラルを空白で潰した版」から探していた。
偽FK(`default 'FOREIGN KEY (fk) REFERENCES fake (id)'`)を消せる代わりに、
SQLite が受け付ける単一引用符の識別子まで巻き添えで消していた。

  CREATE TABLE child(parent_id INTEGER, FOREIGN KEY(parent_id) REFERENCES 'parent'(id));
    main=1 → 前コミット=0 → 本コミット=1

偽FKは本PR以前からある誤検出で、本PRの主題(列制約の外部キーを拾う)ではない。
表制約の入力に手を入れないと決めれば、その経路の出力は main と同一になり、
退行しないことが手続きとして保証される。偽FKは既知の未対応としてテストに残した。

45ケースのDDLコーパスで公開経路を main と差分比較: 落とした FK は0件、
新たに拾えた FK は13件。全 7762 passed / 2 xfailed。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xpq9A9SFvMUky6i6NLFz2
@yohey-w
yohey-w merged commit ad283c1 into main Sep 14, 2026
5 checks passed
@yohey-w
yohey-w deleted the fix/sql-column-level-foreign-keys branch September 14, 2026 13:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant