|
1 | 1 | # postgresql-cst-parser |
2 | 2 |
|
| 3 | +[](https://crates.io/crates/postgresql-cst-parser) |
| 4 | + |
| 5 | +**注意:このパーサーはPostgreSQLの公式プロジェクトではなく、独立した非公式ツールです。** |
| 6 | + |
3 | 7 | ## 概要 |
4 | 8 |
|
5 | | -`postgresql-cst-parser`は、Pure Rust で開発された PostgreSQL 専用の Lossless Syntax Tree(CST)パーサーです。この文書では、パーサーの特徴、動機、使用方法、および実装の詳細について説明します。 |
| 9 | +`postgresql-cst-parser`は、Pure Rustで開発されたPostgreSQL専用の具象構文木(CST)パーサーです。このドキュメントでは、パーサーの機能、開発のモチベーション、使用方法、および実装の詳細について説明します。 |
6 | 10 |
|
7 | 11 | ## 主な特徴 |
8 | 12 |
|
9 | | -- **自動生成された CST パーサー**: PostgreSQL の文法から自動的に生成されるため、幅広い文法に対応。 |
10 | | -- **部分的な制限**: スキャナーの一部の実装が不完全であるため、全ての文法に対応しているわけではありません。 |
| 13 | +- **PostgreSQL 17対応**: 最新のPostgreSQL 17の構文をサポートしています。 |
| 14 | +- **構造化されたCST出力**: 生成されるCSTは、PostgreSQLの[gram.y](https://github.com/postgres/postgres/blob/REL_17_0/src/backend/parser/gram.y)ファイルで定義された構造に厳密に従います。 |
| 15 | +- **`cstree`の利用**: 構文木の構築に`cstree`クレートを使用しています。 |
| 16 | +- **wasm-bindgenとの併用**: Pure Rust で書かれているため、wasm-bindgen と併用できます。 |
| 17 | +- **PL/pgSQL**: 現在はサポートされていません。 |
11 | 18 |
|
12 | | -## 開発の動機 |
| 19 | +## 開発のモチベーション |
13 | 20 |
|
14 | | -1. Rust から利用でき、広範な文法をサポートする PostgreSQL の CST パーサーが不足している。 |
15 | | -2. [pg_query.rs](https://github.com/pganalyze/pg_query.rs)はとても素晴らしいライブラリだが、CST は構築できず WebAssembly(wasm)でビルドできない。 |
| 21 | +Rustから使用可能で、すべての構文をサポートし、(Pure Rust で書かれており) wasm-bindgen が利用可能なライブラリが必要だったため開発しました。 |
16 | 22 |
|
17 | 23 | ## 使用方法 |
18 | 24 |
|
19 | | -以下のコード例のようにして使用します。 |
| 25 | +以下のように使用することができます: |
20 | 26 |
|
21 | 27 | ```rust |
22 | | -let resolved_root = postgresql_cst_parser::parse("SELECT 1;"); |
23 | | -dbg!(resolved_root); |
| 28 | +use postgresql_cst_parser::{parse, syntax_kind::SyntaxKind}; |
| 29 | + |
| 30 | +fn main() { |
| 31 | + // Parse SQL query and get the syntax tree |
| 32 | + let sql = "SELECT tbl.a as a, tbl.b from TBL tbl WHERE tbl.a > 0;"; |
| 33 | + let root = parse(sql).unwrap(); |
| 34 | + |
| 35 | + // Example 1: Extract all column references from the query |
| 36 | + let column_refs: Vec<String> = root |
| 37 | + .descendants() |
| 38 | + .filter(|node| node.kind() == SyntaxKind::columnref) |
| 39 | + .map(|node| node.text().to_string()) |
| 40 | + .collect(); |
| 41 | + |
| 42 | + println!("Column references: {:?}", column_refs); // ["tbl.a", "tbl.b", "tbl.a"] |
| 43 | + |
| 44 | + // Example 2: Find the WHERE condition |
| 45 | + if let Some(where_clause) = root |
| 46 | + .descendants() |
| 47 | + .find(|node| node.kind() == SyntaxKind::where_clause) |
| 48 | + { |
| 49 | + println!("WHERE condition: {}", where_clause.text()); |
| 50 | + } |
| 51 | + |
| 52 | + // Example 3: Get the selected table name |
| 53 | + if let Some(relation_expr) = root |
| 54 | + .descendants() |
| 55 | + .find(|node| node.kind() == SyntaxKind::relation_expr) |
| 56 | + { |
| 57 | + if let Some(name_node) = relation_expr |
| 58 | + .descendants() |
| 59 | + .find(|node| node.kind() == SyntaxKind::ColId) |
| 60 | + { |
| 61 | + println!("Table name: {}", name_node.text()); |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | + // Example 4: Parse complex SQL and extract specific nodes |
| 66 | + let complex_sql = "WITH data AS (SELECT id, value FROM source WHERE value > 10) |
| 67 | + SELECT d.id, d.value, COUNT(*) OVER (PARTITION BY d.id) |
| 68 | + FROM data d JOIN other o ON d.id = o.id |
| 69 | + ORDER BY d.value DESC LIMIT 10;"; |
| 70 | + |
| 71 | + let complex_root = parse(complex_sql).unwrap(); |
| 72 | + |
| 73 | + // Extract CTEs (Common Table Expressions) |
| 74 | + let ctes: Vec<_> = complex_root |
| 75 | + .descendants() |
| 76 | + .filter(|node| node.kind() == SyntaxKind::common_table_expr) |
| 77 | + .collect(); |
| 78 | + |
| 79 | + // Extract window functions |
| 80 | + let window_funcs: Vec<_> = complex_root |
| 81 | + .descendants() |
| 82 | + .filter(|node| node.kind() == SyntaxKind::over_clause) |
| 83 | + .collect(); |
| 84 | + |
| 85 | + println!("Number of CTEs: {}", ctes.len()); |
| 86 | + println!("Number of window functions: {}", window_funcs.len()); |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +生成される構文木の例: |
| 91 | + |
| 92 | +```sql |
| 93 | +SELECT tbl.a as a from TBL tbl; |
24 | 94 | ``` |
25 | 95 |
|
26 | | -さらに、このパーサーを実際に体験してみたい場合は、[こちら](https://tanzaku.github.io/postgresql-cst-parser/)でオンラインで直接試すことができます。実際のコードを入力し、パーサーがどのように動作するかを確認してみましょう。 |
| 96 | +``` |
| 97 | + |
| 98 | + |
| 99 | + |
| 100 | + |
| 101 | + |
| 102 | + |
| 103 | + |
| 104 | + |
| 105 | + |
| 106 | + |
| 107 | + |
| 108 | + |
| 109 | + |
| 110 | + |
| 111 | + |
| 112 | + |
| 113 | + |
| 114 | + |
| 115 | + |
| 116 | + |
| 117 | + |
| 118 | + |
| 119 | + |
| 120 | + |
| 121 | + |
| 122 | + |
| 123 | + |
| 124 | + |
| 125 | + |
| 126 | + |
| 127 | + |
| 128 | + |
| 129 | + |
| 130 | + |
| 131 | + |
| 132 | + |
| 133 | + |
| 134 | + |
| 135 | + |
| 136 | + |
| 137 | + |
| 138 | + |
| 139 | + |
| 140 | + |
| 141 | + |
| 142 | + |
| 143 | +``` |
| 144 | + |
| 145 | +## オンラインデモ |
| 146 | + |
| 147 | +[こちら](https://tanzaku.github.io/postgresql-cst-parser/)でパーサーを直接試すことができます。SQLクエリを入力して、生成された構文木をリアルタイムで確認できます。 |
27 | 148 |
|
28 | | -## 実装方法 |
| 149 | +## 実装 |
29 | 150 |
|
30 | | -実装には、PostgreSQL の [scan.l](https://github.com/postgres/postgres/blob/REL_16_STABLE/src/backend/parser/scan.l) と [gram.y](https://github.com/postgres/postgres/blob/REL_16_STABLE/src/backend/parser/gram.y) に対する [libpg_query の patch](https://github.com/pganalyze/libpg_query/tree/16-latest/patches)を適用したものを使用しています。`scan.l` は Rust 用に書き換えられ、`scan.l` と `gram.y` を基にして構文解析表を作成し、パーサーを構築しています。 |
| 151 | +この実装は、PostgreSQLの[scan.l](https://github.com/postgres/postgres/blob/REL_17_0/src/backend/parser/scan.l)と[gram.y](https://github.com/postgres/postgres/blob/REL_17_0/src/backend/parser/gram.y)に対して[libpg_query](https://github.com/pganalyze/libpg_query/tree/17-6.0.0/patches)のパッチを適用したファイルを使用しています。`scan.l`はさらに Rust 用に書き直したうえで、`scan.l`と`gram.y`に基づいて構文解析テーブルを作成し、パーサーを構築しています。 |
31 | 152 |
|
32 | 153 | ## ライセンス |
33 | 154 |
|
34 | | -`kwlist.h`, `scan.l`, `gram.y` は PostgreSQL License です。 |
35 | | -その他のファイルは MIT License の下で公開されています。 |
| 155 | +- `kwlist.h`、`parser.c`、`scan.l`、`gram.y`はPostgreSQLライセンスの下にあります。 |
| 156 | +- `lexer_ported.rs`と`generated.rs`はPostgreSQLから移植されたコードを含むため、移植部分はPostgreSQLライセンスの下にあります。 |
| 157 | +- このプロジェクトでは、`scan.l`、`gram.y`に対して[libpg_query](https://github.com/pganalyze/libpg_query)のパッチを当てていますが、パッチそのものはこのリポジトリには含まれていません。 |
| 158 | +- その他のファイルはMITライセンスの下で公開されています。 |
0 commit comments