fix(parsing): 列制約で書かれた外部キーを抽出する——FK45本を0本と報告していた - #42
Merged
Merged
Conversation
CoDD Audit❌ Audit failed to run. Check workflow logs. |
Coverage Matrix ReportProject: /home/runner/work/codd-dev/codd-dev
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
force-pushed
the
fix/sql-column-level-foreign-keys
branch
from
September 14, 2026 11:11
6faba83 to
85da324
Compare
列制約の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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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