🏷️

Rustで自作データベースを作る その29: verifyのmismatch codeを追加して、差分の種類を機械可読にする

に公開

今日からゴールデンウィーク!!

前回は、verify の schema detail mode を導入して、

  • verify-table --details
  • verify-db --details
  • missing / unexpected columns
  • type / nullable mismatch
  • primary key mismatch
  • truncation 付き detail report

を追加し、schema mismatch の中身を限定的に確認できるようにしました。

ここまでで verify はかなり育っています。

  • summary で一致 / 不一致が分かる
  • PK details で key 差分が見える
  • schema details で列差分が見える

ただ、ここまで来ると今度は別の課題が見えてきます。

情報は見える。でもプログラムから扱うには、まだ少し人間向けすぎる

たとえば今ほしいのは、こんなことです。

  • この mismatch は row count 系なのか
  • PK 集合系なのか
  • schema 型差分なのか
  • nullable 差分なのか
  • CLI や CI から安定した code で扱えないか

そこで今回は、verify の mismatch code を追加します。

今回のテーマはこれです。

差分の「説明文」だけでなく、「種類」を機械可読に返す


なぜ details の次に code なのか

ここまでの verify は、人間にとってかなり見やすくなっています。

たとえば text 出力で、

mismatch: schema differs
column_mismatches:
  - name
    expected_type: String
    actual_type: Bytes

のように出れば、読む人には十分分かります。

でも、これを自動化しようとすると少しつらいです。

  • summary 文字列を parse したくなる
  • details の存在有無を推測に使いたくなる
  • mismatch の系統ごとに分岐したいのに安定キーがない

そこで必要なのが、structured code です。


status / code / details は別物

ここでまず整理したいのが、この 3 つの違いです。

status

  • success
  • mismatch
  • failed

mismatch code

  • mismatch の種類
  • 例: row_count_mismatch

details

  • 具体的な中身
  • 例: missing PK や missing column

この 3 つは似て見えてかなり役割が違います。

図にするとこうです。

status
  -> 大分類

mismatch code
  -> mismatch の種別

details
  -> mismatch の中身

この分離がかなり大事です。


今回追加する mismatch code

最初は増やしすぎない方がいいです。
今すでに verify が扱っている範囲だけを code にします。

最小なら、たとえばこうです。

schema_missing_columns
schema_unexpected_columns
schema_type_mismatch
schema_nullable_mismatch
schema_primary_key_mismatch
row_count_mismatch
primary_key_set_mismatch
table_missing_in_target
table_unexpected_in_target

これだけあれば、かなり十分です。


なぜ summary 文字列だけでは足りないのか

文字列だけだと、どうしても不安定になります。

たとえば、

  • "schema differs"
  • "row count and primary key set differ"
  • "table missing in target"

こういう文字列は人間には分かりやすいです。
でも機械にとっては扱いづらいです。

  • wording が変わるかもしれない
  • 多言語化で壊れる
  • 部分一致判定が必要になる

一方で enum/code なら、こうです。

row_count_mismatch
primary_key_set_mismatch

かなり安定しています。


今回の全体像

verify report はこう整理できます。

VerifyReport
  ├─ status
  ├─ mismatch_codes
  └─ details

つまり、

  • status は大きな分類
  • mismatch_codes は軽量な機械可読 summary
  • details は必要時の深掘り

です。

この layering はかなりきれいです。


mismatch code は details の代わりではない

ここも重要です。

mismatch code は details を置き換えるものではありません。

役割はこうです。

mismatch code

  • 何系統の問題かを軽く判定する
  • details がなくても使える

details

  • その問題の中身を掘る
  • opt-in の詳細

たとえば、

mismatch_codes:
  - schema_type_mismatch
  - row_count_mismatch
  - primary_key_set_mismatch

だけでも、自動処理にはかなり使えます。
その上で必要なら details を見る、という形です。


今回も default verify に載せてよい

前回の details は opt-in でした。
でも mismatch code は details より軽いです。

だから今回は、default verify でも mismatch code を返してよい です。

これはかなり自然です。

  • summary より一歩詳しい
  • でも details ほど重くない
  • 人にも機械にも useful

つまり verify はこうなります。

default verify
  -> status + mismatch_codes

verify --details
  -> status + mismatch_codes + details

かなりバランスが良いです。


code は compare 結果から派生させたい

ここも設計上かなり大事です。

mismatch code を details から後付けで作るのではなく、
summary 判定と同じ compare 結果から派生 させるのが良いです。

図にするとこうです。

compare result
   ├─ status
   ├─ mismatch_codes
   └─ details

これなら、

  • summary と code がズレにくい
  • code と details がズレにくい
  • compare ロジックが一箇所に寄る

かなりきれいです。


たとえば schema type mismatch ならこう出る

例として name 列が String ではなく Bytes だったとします。

この時、verify report ではこうなります。

status: mismatch
mismatch_codes:
  - schema_type_mismatch

そして --details ならさらにこうなります。

column_mismatches:
  - name
    expected_type: String
    actual_type: Bytes

つまり、

  • code は種類
  • details は具体例

という分担がかなり明確です。


row count mismatch と PK set mismatch も code 化する

row 側も同じです。

たとえば row count が違うだけなら、

mismatch_codes:
  - row_count_mismatch

PK 集合も違えば、

mismatch_codes:
  - row_count_mismatch
  - primary_key_set_mismatch

このように複数 code を並べられます。

これがあると、CI や admin script でもかなり扱いやすいです。


success / failed の時はどうするか

ここも決めておきたいポイントです。

  • success の時は mismatch_codes = []
  • failed の時は mismatch code を無理に出さない

これが自然です。

理由は簡単です。

  • success に mismatch code は不要
  • failed は compare 自体が終わっていないかもしれない

つまり mismatch code は、mismatch 専用の軽量分類 です。


database verify では per-table codes が自然

database verify の時は、やはり per-table に code を持つのが自然です。

たとえばこうです。

users:
  mismatch_codes:
    - schema_type_mismatch

orders:
  mismatch_codes:
    - row_count_mismatch
    - primary_key_set_mismatch

必要なら DB 全体として union を持ってもよいですが、
まずは per-table が一番実用的です。


text 出力でも十分 useful

CLI text では、こんな形で十分です。

status: mismatch
table: users
mismatch_codes:
  - schema_type_mismatch
  - row_count_mismatch
  - primary_key_set_mismatch

もし --details を付ければ、その下に具体的な内容が続きます。

この layering はかなり分かりやすいです。


JSON 出力は特に強い

JSON では mismatch code の価値がかなり高いです。

たとえばこうです。

{
  "status": "mismatch",
  "table_name": "users",
  "mismatch_codes": [
    "schema_type_mismatch",
    "row_count_mismatch",
    "primary_key_set_mismatch"
  ],
  "details": {
    "primary_key_details": { ... },
    "schema_details": { ... }
  }
}

この形なら、外部ツールからかなり扱いやすいです。

  • code だけ見て分岐
  • 必要なら details まで読む

この二段構えができます。


文字列 summary は残してよい

ここで気をつけたいのは、code を入れたからといって summary 文字列を消す必要はないことです。

mismatch_summary: "row count and primary key set differ"
mismatch_codes:
  - row_count_mismatch
  - primary_key_set_mismatch

これは両立してよいです。

  • summary: 人間向け
  • codes: 機械向け

という役割だからです。


だから文字列を parser にさせない

ただし大事なのは、summary を機械処理の正本にしないことです。

今後使うべきなのは mismatch_codes です。

summary はあくまで人が読む補助。
この立て付けを docs にも明記しておくと良いです。


verify_table_import の内部イメージ

概念的にはこんな感じです。

let schema_compare = compare_schema(...);
let row_count_compare = compare_row_count(...);
let pk_compare = compare_primary_keys(...);

let mismatch_codes = collect_mismatch_codes(
    &schema_compare,
    &row_count_compare,
    &pk_compare,
);

let details = if options.include_details {
    Some(build_details(schema_compare, pk_compare, ...))
} else {
    None
};

TableVerifyReport {
    status: compute_status(...),
    mismatch_codes,
    details,
    ...
}

ポイントは、同じ compare 結果から全部を作ることです。


これで mismatch の種類を外から扱いやすくなる

この設計が入ると、たとえば外からはこういう使い方ができます。

  • row_count_mismatch だけなら件数ズレとして扱う
  • schema_type_mismatch があれば schema 差分として扱う
  • primary_key_set_mismatch があれば key 差分として扱う

つまり mismatch を「種類別」に扱えるようになります。

これはかなり大きいです。


end-to-end のサンプルはかなり映える

今回の記事で見せやすいのは、schema と row の両方がズレたケースです。

db-cli verify-table --db ./target-db --in users.json --format json

出力イメージ:

{
  "status": "mismatch",
  "table_name": "users",
  "mismatch_codes": [
    "schema_type_mismatch",
    "row_count_mismatch",
    "primary_key_set_mismatch"
  ]
}

そして --details を付けると、具体的な差分が付きます。

db-cli verify-table --db ./target-db --in users.json --details
status: mismatch
table: users
mismatch_codes:
  - schema_type_mismatch
  - row_count_mismatch
  - primary_key_set_mismatch

かなり “summary と details の使い分け” が分かりやすいです。


verify の layering がさらに明確になる

ここまで来ると verify はかなり多層ですが、整理するとむしろ分かりやすいです。

status
  -> success / mismatch / failed

mismatch_codes
  -> どの系統の mismatch か

details
  -> mismatch の具体的な中身

この 3 層構造は、かなり自然です。


今回まだやらないこと

ここは明確に書いた方が記事として締まります。

今回 やらない のは、たとえば次です。

  • row value diff
  • auto repair
  • migration proposal
  • full diff engine
  • checksum tree
  • overwrite / merge import
  • interactive mismatch explorer

なぜ今はやらないのか。

それは、今回必要なのが

差分の種類を機械可読にすること

だからです。

つまり今欲しいのは、

  • mismatch の系統が code で分かる
  • details がなくても扱える
  • 必要なら details まで深掘れる
  • でも full diff には行かない

という最小契約です。


今回の責務分離

今回の mismatch code で見えてくる責務分離は、かなりきれいです。

status
  └─ 大分類

mismatch code
  └─ mismatch の機械可読な種別

details
  └─ mismatch の具体内容

CLI
  └─ それを text/json で見せる

compare core
  └─ すべての判定の正本

特に大事なのは、

  • code が details の代わりをしようとしない
  • code と details が同じ比較基盤から出る
  • default verify の軽さを壊さない

という点です。


所感

今回のタスクは、かなり “人向けの verify” から “機械にも優しい verify” へ進む回だと思います。

details があると人間にはかなり便利です。
でも自動化や将来の運用を考えると、やはり欲しいのは code です。

  • 安定している
  • 比較しやすい
  • 分岐しやすい
  • 文字列 wording に依存しない

この違いは大きいです。

しかも今回は code を details と競合させず、
その手前の軽量分類として置いているのがかなり筋が良いと思います。


まとめ

今回は、verify の mismatch code を導入して、

  • VerifyMismatchCode
  • verify report の mismatch_codes
  • CLI / JSON での code 表示
  • summary / code / details の 3 層化

を追加し、差分の種類を機械可読にできるようにするタスクでした。

要するに、T-028 で作った details の次に、
差分の“系統”を安定した code で扱えるようにする回です。

これで自作DBは、単に export/import/verify できるだけでなく、
ズレた時にそのズレを人にも機械にも扱いやすい形で表現できる小さな DB システム にさらに近づきます。

次は、この基盤の上に row value diff ではなく、まずは verify の code から簡単な remediation hint を任意表示する read-only helper を入れるのが自然なテーマになります。
mismatch の種類が code で分かるようになったので、次は「その code なら次に何を見ればよいか」を軽く案内できるようにすると、さらに運用しやすくなりそうです。

次回もぜひご覧ください!

Discussion