Skip to content

Latest commit

 

History

History
227 lines (179 loc) · 9.59 KB

File metadata and controls

227 lines (179 loc) · 9.59 KB

SQL Parser Plan

目的

SQL 構文を理解することを主目的として、学習用 RDBMS の最初の実装対象として SQL パーサーを作る。

方針は次の通り。

  • 最初は文字列を直接読み進める手書き parser を実装する
  • parser は構文解析だけを担当し、テーブル存在確認や型解決はしない
  • 将来的に sqlparser-rs を導入しやすいよう、自前 AST を中心に設計する
  • トランザクション構文は最初の parser スコープから外し、 単一文の CRUD を優先したうえで後続計画へ切り出す

現在の実装状況

2026-03-22 時点で、sql crate の hand-written parser は 次の最小 CRUD をサポートしている。

CREATE TABLE users (id INT, name TEXT, active BOOLEAN);
CREATE TABLE metrics (score FLOAT, note NULL);
INSERT INTO users VALUES (1, 'Alice');
INSERT INTO users (id, name, active) VALUES (1, 'Alice', true);
UPDATE users SET name = 'Bob' WHERE id = 1;
UPDATE users SET active = false;
DELETE FROM users WHERE id = 1;
DELETE FROM users;
SELECT id FROM users;
SELECT id, name FROM users;
SELECT * FROM users;
SELECT DISTINCT id FROM users;
SELECT id AS user_id FROM users;
SELECT id user_id FROM users;
SELECT COUNT(id) FROM users;
SELECT COUNT(*) FROM users;
SELECT id + 1 FROM users;
SELECT -1 FROM users;
SELECT id FROM users WHERE id = 1;
SELECT id FROM users WHERE id = (SELECT id FROM users);
SELECT id FROM users WHERE NOT id = 1;
SELECT id FROM users WHERE id = 1 AND active = 1;
SELECT id FROM users WHERE id = 1 OR active = 1;
SELECT id FROM users WHERE (id = 1);
SELECT id FROM users AS u;
SELECT id FROM (SELECT id FROM users) AS u;
SELECT users.id FROM users JOIN orders ON users.id = orders.user_id;
SELECT id FROM users JOIN orders USING (user_id);
SELECT id FROM users GROUP BY id;
SELECT id FROM users GROUP BY id HAVING COUNT(id) > 1;
SELECT id FROM users ORDER BY COUNT(id) DESC;

現在の式パーサーは比較式、NOT、unary -ANDOR、括弧付き条件、 簡単な算術演算、COUNT(...) のような関数呼び出し、 scalar subquery、true / false / NULL の最小リテラルを対象とする。 内部では式パーサーを独立させ、 primary、unary、乗除、加減算、比較、NOTANDOR の順に優先順位を積み上げている。 JOIN ... ON ...UPDATE ... SET ...DELETE ... WHERE ...INSERT ... VALUES (...) でも同じ式の仕組みを再利用している。

SELECT 項目は * を除き、 単純列、AS 付き列別名、暗黙列別名、関数呼び出し、一般式を できるだけ同じ考え方で表現する。 FROMJOIN の参照先も、 通常テーブルと最小の subquery を同じ枠組みで扱う。

INSERT は列リスト省略形と列リスト明示形の単一行 VALUES をサポートする。 UPDATE は単一テーブルに対する SET 句と任意の WHERE をサポートする。 DELETE は単一テーブルに対する任意の WHERE をサポートする。 CREATE TABLE は列名と最小データ型だけを持つ定義をサポートする。

スコープ

現段階で到達した SELECT

SELECT については、現在の実装状況に列挙した最小形を 第一段階の到達点とする。

JOIN の派生構文、派生表の広い対応、LIMIT / OFFSET などの 追加拡張はこの計画には含めず、後続の 05-select-extensions.md に切り出す。

今後サポートしたい CRUD 全体

SELECT 以外については、次の最小形を到達点とする。

CREATE TABLE users (id INT, name TEXT, active BOOLEAN);
INSERT INTO users VALUES (1, 'Alice', true);
INSERT INTO users (id, name, active) VALUES (1, 'Alice', true);
UPDATE users SET name = 'Bob' WHERE id = 1;
DELETE FROM users WHERE id = 1;

現時点の制約

  • SELECT の関数呼び出しは最小形の COUNT(...) と scalar subquery から対応しているが、 一般的な関数群や方言差までは未対応
  • ORDER BY / GROUP BY / HAVING / DISTINCT は最小実装であり、 すべての SQL 方言や派生構文を網羅しているわけではない
  • subquery は WHERE id = (SELECT ...)FROM (SELECT ...) AS alias のような 最小形に限って対応しており、結合先 subquery や複雑な派生表は未対応
  • JOINJOIN / LEFT JOIN / RIGHT JOIN / FULL JOINON / USING の最小形に限って対応しており、 INNER JOINLEFT OUTER JOIN、結合先 (SELECT ...) AS alias の広い派生形は未対応
  • 行数制御は未対応であり、LIMIT / OFFSET やそれに類する方言構文はまだ読めない
  • INSERT は単一行 VALUES だけに限って対応しており、 複数行 VALUESINSERT ... SELECT ... は未対応
  • UPDATE は単一テーブルの SET ... [WHERE ...] だけに限って対応しており、 UPDATE ... FROM ... のような派生構文は未対応
  • DELETE は単一テーブルの DELETE FROM ... [WHERE ...] だけに限って対応しており、 USING を伴う派生構文や結合削除は未対応
  • CREATE TABLE は列名と型名だけの最小形に限って対応しており、 制約、既定値、PRIMARY KEYNOT NULLVARCHAR(n) は未対応
  • データ型は INT / TEXT / BOOLEAN / FLOAT / NULL から開始し、 INTEGERBOOL は最小の別名として受け付ける
  • BEGIN / COMMIT / ROLLBACK はまだ未対応であり、 後続の 03-transaction-control.md で扱う

実装方針

1. Statement parser

文単位は再帰下降で実装する。

現時点では Parser が文字列を直接読み進める方式を採る。 空白スキップ、キーワード読取、識別子読取のような小さい補助処理を組み合わせ、 必要に応じて先読みと消費で句境界を判定する。

2. FROM 句の設計

FROM 句は単なる文字列ではなく、 基準テーブル、結合先、結合条件を区別できる構造で表現する。

これにより、最小形の FROM users から、 FROM users AS u JOIN orders USING (user_id) のような形まで段階的に拡張できる。

3. Expression parser

現時点では式パーサーを分離し、 WHEREJOIN ... ON ... で共通利用している。 論理条件、算術演算、関数呼び出し、scalar subquery までは実装済みで、 UPDATE ... SET ... やさらに広い式構文への展開を今後進める。

最終的に目指す優先順位は次の通り。

  1. primary: identifier, literal, parenthesized expression, simple function call
  2. unary: NOT, unary -
  3. multiplicative: *, /
  4. additive: +, -
  5. comparison: =, !=, <, <=, >, >=
  6. logical and: AND
  7. logical or: OR

AST設計

既存の AST は土台として使い、現時点では次の方針を採っている。

  • INSERT は対象テーブル、任意の列リスト、値リストで表現する
  • UPDATEDELETE を表現できるようにする
  • JOIN 条件で ONUSING の両方を表現できるようにする
  • 列別名や関数呼び出しを扱うため、SELECT 項目は式ベースを基本形にする
  • * は通常の式とは別扱いにする
  • 列定義の型は types crate のデータ型を使う
  • リテラル値は共通の値表現を使う
  • parser と binder の責務を分けるため、AST では名前解決をしない

実装順

直近の優先順位

  1. query 側で使いやすい形に AST と executor の境界を調整する
  2. SELECT の将来拡張へ進むか、CRUD を planner / executor へ接続する
  3. 将来の SELECT 拡張

SELECT の将来拡張は、現在の CRUD 実装を一通り通したあとに 05-select-extensions.md の計画として進める。

CRUD 全体の実装順

  1. AST を CRUD 対応の形に整理する
  2. SELECT を今後の句拡張に耐える形へ寄せる
  3. INSERT を実装する
  4. UPDATE / DELETE を実装する
  5. CREATE TABLE を実装する
  6. query 側で使いやすい形に AST と executor の境界を調整する

1 から 5 までは完了し、現在は 6 の段階に入っている。

テスト方針

Parser

  • SQL 文字列から期待した AST が得られる
  • SELECTINSERTUPDATEDELETECREATE TABLE は 正常系と異常系をテーブルドリブンで検証する
  • 別名、JOIN ... ON ...JOIN ... USING (...) を個別ケースで固定する
  • WHERENOTANDOR の優先順位と括弧あり式を追加で検証する
  • 不正な構文で分かりやすいエラーが返る

第一段階の成功条件

第一段階の SELECT については、以下がすべてパースできれば完了とする。

SELECT id FROM users;
SELECT id, name FROM users;
SELECT * FROM users;
SELECT id AS user_id FROM users;
SELECT id user_id FROM users;
SELECT id FROM users WHERE id = 1;
SELECT id FROM users WHERE NOT id = 1;
SELECT id FROM users WHERE id = 1 AND active = 1;
SELECT id FROM users WHERE id = 1 OR active = 1;
SELECT id FROM users WHERE (id = 1);
SELECT id FROM users AS u;
SELECT users.id FROM users JOIN orders ON users.id = orders.user_id;
SELECT id FROM users JOIN orders USING (user_id);

後続方針

  • まずは自前パーサーで SQL -> AST の流れを理解する
  • その後、必要なら sqlparser-rs を使って自前 AST へ変換する adapter を追加する
  • planner, optimizer, transaction はこの段階では広げすぎない