検索/複数のSQL方言に対応するコンパイラを、クエリの意味を変えずに広げる

複数のSQL方言に対応するコンパイラを、クエリの意味を変えずに広げる

SQLをASTに変え、別のデータベース向けのSQLに書き直すコンパイラを直す時に使う。先に受け持つ層を見つけ、方言が変わっても意味を保ち、SQLを文字列でなくASTの部品で組み立て、実際の実行と速さをテストする。

このAIルールを使うには
## 直す前に、その動きを受け持つコンパイラの層を見つける システムを、各層が異なる役割を担うコンパイラとして扱う。 - 字句解析器はSQLテキストを型付きトークンへ変換し、各方言が字句マッピングを上書きできるようにする。 - 構文解析器は再帰下降法で抽象構文木(AST)を構築し、照合、カーソル、リスト、ラッパー用の専用ヘルパーを使う。 - 式クラスは意味論上のSQL概念を表し、走査と変換に対応する。 - 生成器は、単純な変換マッピングまたは専用の生成メソッドを使い、式を再帰的にSQLへ戻す。 - 方言モジュールは、機能フラグ、トークン集合、構文解析マッピング、生成マッピングを使って共通基盤を拡張する。 - 最適化規則は、意図された順序でASTを正規化して書き換える。 - スキーマ情報と型情報は、最適化、トランスパイル、リネージを支える。 - リネージは、出力列を上流の式とテーブルへ結び付ける。 動作を変更する前に、どの層がその動作を担うかを特定する。ある層の修正のために別の層へロジックを漏らさず、字句解析、構文解析、意味表現、最適化、生成を分離したまま保つ。 構文解析器と生成器の対称性を保つ。構文解析はトークンを式へ対応付け、生成は式をSQLへ対応付ける。構文解析器が認識できないテキストを意図的に汎用コマンドノードへ退避させる場合、その保存機構を黙って不完全な構文解析へ変えてはならない。 ## リポジトリが決めたテストと検査のコマンドを、純粋実装とネイティブ拡張の両方で使う 編集前に、リポジトリのコントリビューション手引きとアーキテクチャ手引きを読む。基本インストール、開発用インストール、任意のネイティブ拡張のインストール、コミット前設定、全テスト、単体テスト、スタイル検査、型検査、ベンチマークには、文書化されたターゲットを使う。 純粋実装と、任意で使えるコンパイル済み拡張を区別する。まず直接関係する実行方式を確認し、その後に変更内容が必要とする広範な検査を行う。ネイティブ高速化に影響する場合は、同じ動作だと仮定せず、純粋実装とコンパイル済み経路の両方をテストする。 反復作業中は、特定のテストファイル、クラス、メソッドを実行して失敗箇所を正確に狙う。提出前には、整形、静的検査、純粋実行環境のテスト、該当する場合はネイティブ拡張のテストを含む、リポジトリ所定の完全な検査ターゲットを実行する。 性能に関係する作業では、構文解析と最適化のベンチマークを使う。同等の構成同士を比較し、都合のよいマイクロベンチマークのために意味論上の正しさを犠牲にしてはならない。 ## SQLを別の方言に変えても、同じ意味を保つ ASTを意味論上の正本として扱う。元のSQLを意味を表す式へ解析し、その式から対象方言を生成する。元の表記ではなく意味を保つ。コメントは可能な範囲で保持してよいが、意味の同等性を優先する。 複数の方言で共有する機能は、その意味が本当に一致する場合に共通基盤へ追加する。方言固有の構文や動作は、該当する方言だけへ追加する。名前の個別判定を散在させず、明示的な機能フラグ、予約トークン集合、関数または文のマッピング、式からSQLへのマッピングを使う。 最適化規則は意図された順序のまま保つ。修飾、型注釈、述語や射影のプッシュダウン、単純化は、先行する処理やスキーマ情報に依存することがある。最適化器を物理クエリプランナーではなく、論理的な書き換え器として扱う。 機能がデータベース構造に依存する場合は、スキーマ情報を明示的に与える。必要なテーブルと列のメタデータがない状態で、修飾、型推論、最適化、列リネージの信頼性を主張してはならない。 ## SQLは文字列をつながず、ASTの部品とテンプレートで組み立てる 汎用的な関数式を作る前に、式カタログに既存の専用クラスがないか探す。専用の意味クラスが存在しない場合に限り、汎用的な匿名式を使う。 対応済みの生成機構から最も単純なものを優先する。 1. 単一の複雑な式の型には、自動検出される生成器メソッド `<lowercase_expression_name>_sql` を実装する。 2. 変換マッピングは、単純な一行の名前変更、ラムダ、または意図的に複数の入口を持つ関数に限定する。 3. 生成器メソッド内では、直列化するためだけに関数ノードを構築せず、生成器の関数ヘルパーを使う。 4. 配列、論理式、条件分岐、キャスト、列、NULL、算術、添字アクセス、比較には、式ビルダーと演算子を使う。 5. 複雑な結果には、SQLテンプレートを解析し、複製したAST内のプレースホルダーを置換する。 文字列補間でSQL断片を組み立ててはならない。ASTビルダーとテンプレートは、手作業の文字列では壊れやすい引用符処理、エスケープ、入れ子、方言ごとの動作を保つ。 ## 型は型注釈の後に調べ、NULLは実行時に判定する 構文上の型検査と意味論上の型検査を区別する。`is_string` のようなリテラル判定は、ノードが文字列リテラルそのものかを調べて値を取り出す場合だけ使う。列、関数の結果、その他の式について推論された意味論上の型を調べる場合は `is_type()` を使う。 意味論上の動作が型に依存する場合は、`is_type()` を呼ぶ前に型注釈を実行する。型に依存するトランスパイルのテストではASTへ型注釈を付けなければならない。そうしないと、型が一度も設定されていないため、正しい意味検査が失敗したように見える。 ノードが数値リテラルであることを確認してから、`to_py()` など式の変換メソッドで数値を取り出す。内部の生テキストから引用符を手作業で外して型変換してはならない。 リテラルのNULLノードだけを確認して、トランスパイル時にSQLの `NULL` 動作を解決してはならない。列、パラメーター、式、NULLリテラルがすべて同じ意味論に従うよう、実行時の `IS NULL` ロジックを含むSQLを生成する。 祖先ノードを探す時は、親クエリへ探索が抜けないよう、クエリスコープの境界を含める。対象方言が特定の引数を表現できない場合は、フレームワーク所定の非対応引数宣言機構を使う。 ## 方言ごとにテストし、実際のデータベースで動かす 影響を受ける方言またはコンパイラ層を担当するテスト群へテストを追加する。同一のまま往復できるべき構文にはラウンドトリップ検証を使い、一つの意味式から対象ごとに異なるSQLを生成すべき場合は方言間検証を使う。 同じ動作をすでに証明している検証を重複させない。該当する場合は、NULL許容性、型、引用符、スコープ、非対応引数、方言差など、新しい意味上の区別に焦点を当てたケースを追加する。 データベース固有のテスト群へ置いたテストが、対応するデータベースまたは互換エンジンで実際に動くことを確認する。規則が実行時の動作に依存する場合、生成された文字列だけでは証拠にならない。 型に依存する結果では、リテラルと型付きの非リテラル式の両方をテストする。ネイティブ高速化では純粋実装との同等性を確認する。最適化器の変更ではスキーマを考慮したケースを含め、規則の順序が意味を変えないことを検証する。 ## 1つの変更は1つの目的に絞り、レビューしやすくする 各変更は、明確に定義した一つの目的に限定する。単純でない動作にはテストを追加し、APIが変わる場合は一般向けdocstringを更新し、リポジトリのプルリクエストとコミットの規約に従い、提出前に所定の完全な検査を実行する。 未使用のimport、変数、到達しないコードを取り除く。意図的に取り扱いを終えたコードのための互換用小細工は追加しない。不要な一時変数より読みやすいインライン式を優先するが、意味上の処理段階が不明確になるまでロジックを圧縮してはならない。 コメントは自明でないロジックにだけ使い、docstringは複雑な動作または一般向けAPIにだけ追加する。タスクがライセンスを明示的に対象とする場合を除き、ライセンスファイルやライセンス設定を変更してはならない。 方言間の意図しない結合、式の型の重複、手作業で組み立てたSQL、型注釈の欠落、コンパイル時だけのNULL処理、スコープの漏れ、未検証のネイティブ動作またはデータベース固有動作が変更にないかレビューする。 ## 速くする時も、ネイティブ高速化と構文解析の作りを守る 主な実装が高水準言語で書かれていても、性能を製品機能として扱う。任意のネイティブ高速化経路を保ち、字句解析、構文解析、生成、最適化に影響する変更はベンチマークで測る。 意図的に採用された再帰下降構文解析器を、最適化になるとの推測だけでPratt構文解析へ置き換えてはならない。構文解析方式はアーキテクチャ上の選択であり、交換可能な速度設定ではない。 コードベースが推奨する代替手段を文書化している場合は、コンパイル済み経路を妨げると分かっている抽象化を避ける。たとえば、非対応のプロトコル抽象化ではなく、単純なUnion型と構造的ダックタイピングを選ぶ。 構文解析、AST構築、生成、方言ごとの動作、テスト全体で正しさを保ってから最適化する。意味を変え、引用符処理を失い、`NULL` を誤って扱い、または純粋方式とコンパイル済み方式で動作が分かれる高速化はリグレッションである。