plume 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +280 -10
- data/lib/plume/analysis/resolver.rb +574 -0
- data/lib/plume/analysis.rb +135 -0
- data/lib/plume/ast/alter_table_statement.rb +29 -0
- data/lib/plume/ast/analyze_statement.rb +15 -0
- data/lib/plume/ast/assignment.rb +18 -0
- data/lib/plume/ast/attach_statement.rb +17 -0
- data/lib/plume/ast/begin_statement.rb +17 -0
- data/lib/plume/ast/binary_expression.rb +71 -0
- data/lib/plume/ast/case_condition.rb +29 -0
- data/lib/plume/ast/case_expression.rb +22 -0
- data/lib/plume/ast/cast_expression.rb +16 -0
- data/lib/plume/ast/check_column_constraint.rb +20 -0
- data/lib/plume/ast/check_table_constraint.rb +19 -0
- data/lib/plume/ast/collate_column_constraint.rb +19 -0
- data/lib/plume/ast/collation_expression.rb +26 -0
- data/lib/plume/ast/column_constraint.rb +7 -0
- data/lib/plume/ast/column_definition.rb +16 -0
- data/lib/plume/ast/column_name.rb +22 -0
- data/lib/plume/ast/column_type.rb +25 -0
- data/lib/plume/ast/commit_statement.rb +13 -0
- data/lib/plume/ast/common_table_expression.rb +31 -0
- data/lib/plume/ast/compound_select.rb +30 -0
- data/lib/plume/ast/conflict_clause.rb +22 -0
- data/lib/plume/ast/conflict_resolution.rb +20 -0
- data/lib/plume/ast/create_index_statement.rb +29 -0
- data/lib/plume/ast/create_table_statement.rb +47 -0
- data/lib/plume/ast/create_trigger_statement.rb +43 -0
- data/lib/plume/ast/create_view_statement.rb +30 -0
- data/lib/plume/ast/create_virtual_table_statement.rb +28 -0
- data/lib/plume/ast/default_column_constraint.rb +29 -0
- data/lib/plume/ast/delete_statement.rb +28 -0
- data/lib/plume/ast/detach_statement.rb +13 -0
- data/lib/plume/ast/drop_statement.rb +21 -0
- data/lib/plume/ast/empty_function_argument.rb +6 -0
- data/lib/plume/ast/exists_expression.rb +15 -0
- data/lib/plume/ast/explain_statement.rb +15 -0
- data/lib/plume/ast/expression.rb +30 -0
- data/lib/plume/ast/foreign_key_action.rb +26 -0
- data/lib/plume/ast/foreign_key_clause.rb +50 -0
- data/lib/plume/ast/foreign_key_column_constraint.rb +16 -0
- data/lib/plume/ast/foreign_key_table_constraint.rb +20 -0
- data/lib/plume/ast/frame_boundary.rb +35 -0
- data/lib/plume/ast/frame_spec.rb +34 -0
- data/lib/plume/ast/function_arguments.rb +17 -0
- data/lib/plume/ast/function_reference.rb +29 -0
- data/lib/plume/ast/generated_as_column_constraint.rb +24 -0
- data/lib/plume/ast/indexed_column.rb +21 -0
- data/lib/plume/ast/insert_statement.rb +35 -0
- data/lib/plume/ast/join.rb +12 -0
- data/lib/plume/ast/join_clause.rb +15 -0
- data/lib/plume/ast/join_on_constraint.rb +12 -0
- data/lib/plume/ast/join_operator.rb +31 -0
- data/lib/plume/ast/join_using_constraint.rb +16 -0
- data/lib/plume/ast/like_expression.rb +19 -0
- data/lib/plume/ast/literal_expression.rb +29 -0
- data/lib/plume/ast/literal_false.rb +7 -0
- data/lib/plume/ast/literal_nil.rb +7 -0
- data/lib/plume/ast/literal_value.rb +18 -0
- data/lib/plume/ast/match_clause.rb +15 -0
- data/lib/plume/ast/node.rb +207 -0
- data/lib/plume/ast/noop_column_constraint.rb +15 -0
- data/lib/plume/ast/noop_table_constraint.rb +15 -0
- data/lib/plume/ast/not_like_expression.rb +7 -0
- data/lib/plume/ast/not_null_column_constraint.rb +17 -0
- data/lib/plume/ast/null_column_constraint.rb +17 -0
- data/lib/plume/ast/ordering_term.rb +32 -0
- data/lib/plume/ast/over_clause.rb +25 -0
- data/lib/plume/ast/parenthetical_expression.rb +37 -0
- data/lib/plume/ast/parenthetical_source.rb +19 -0
- data/lib/plume/ast/pragma_statement.rb +26 -0
- data/lib/plume/ast/primary_key_column_constraint.rb +23 -0
- data/lib/plume/ast/primary_key_table_constraint.rb +23 -0
- data/lib/plume/ast/raise_expression.rb +23 -0
- data/lib/plume/ast/reindex_statement.rb +15 -0
- data/lib/plume/ast/release_statement.rb +16 -0
- data/lib/plume/ast/result_column.rb +18 -0
- data/lib/plume/ast/returning_clause.rb +12 -0
- data/lib/plume/ast/rollback_statement.rb +19 -0
- data/lib/plume/ast/savepoint_statement.rb +16 -0
- data/lib/plume/ast/select_core.rb +27 -0
- data/lib/plume/ast/select_statement.rb +34 -0
- data/lib/plume/ast/star_function_argument.rb +6 -0
- data/lib/plume/ast/star_result_column.rb +16 -0
- data/lib/plume/ast/strict_table_option.rb +6 -0
- data/lib/plume/ast/subquery_expression.rb +14 -0
- data/lib/plume/ast/subquery_source.rb +19 -0
- data/lib/plume/ast/table_constraint.rb +7 -0
- data/lib/plume/ast/table_function.rb +28 -0
- data/lib/plume/ast/table_name.rb +28 -0
- data/lib/plume/ast/table_option.rb +7 -0
- data/lib/plume/ast/ternary_expression.rb +26 -0
- data/lib/plume/ast/token.rb +180 -0
- data/lib/plume/ast/unary_expression.rb +36 -0
- data/lib/plume/ast/unique_column_constraint.rb +17 -0
- data/lib/plume/ast/unique_table_constraint.rb +20 -0
- data/lib/plume/ast/update_statement.rb +35 -0
- data/lib/plume/ast/upsert_clause.rb +26 -0
- data/lib/plume/ast/vacuum_statement.rb +18 -0
- data/lib/plume/ast/values_clause.rb +12 -0
- data/lib/plume/ast/values_row.rb +13 -0
- data/lib/plume/ast/variable.rb +15 -0
- data/lib/plume/ast/window_definition.rb +26 -0
- data/lib/plume/ast/with_clause.rb +15 -0
- data/lib/plume/ast/without_rowid_table_option.rb +6 -0
- data/lib/plume/catalog.rb +180 -0
- data/lib/plume/lexer.rb +639 -0
- data/lib/plume/parser.rb +4015 -0
- data/lib/plume/printer.rb +471 -0
- data/lib/plume/sqlite_grammar.rb +86 -0
- data/lib/plume/union.rb +28 -0
- data/lib/plume/version.rb +1 -1
- data/lib/plume.rb +143 -3
- metadata +167 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5233a7cda234897d9b0cca09246777a1cd343d3da52d9fef922a11eddb230195
|
|
4
|
+
data.tar.gz: 539669c43ded2e665a44f44f33d43354c050243c02afc7b08de2b4ecb41f0709
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eb601a1c93cfc62614a3188053f20401ccd4213e9b5274f8f71af8ec567b66ecffeb18e796b9bfad4499d55a80be5ddf0a2a646086b633aadc4ba2093bbde46d
|
|
7
|
+
data.tar.gz: 79172b07840a82d4a35a201e51fbbe589763cc0160fe7ac2360d9254bf0ca9789e1c9b0ea3cba3e33769d9cb6945cc700e07fe4620f7a0f6514246cb54ac3a04
|
data/README.md
CHANGED
|
@@ -1,38 +1,308 @@
|
|
|
1
1
|
# Plume
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://rubygems.org/gems/plume)
|
|
4
|
+
[](https://rubygems.org/gems/plume)
|
|
5
|
+

|
|
6
|
+
[](https://github.com/sponsors/fractaledmind)
|
|
7
|
+
[](https://twitter.com/fractaledmind)
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
<img src="/assets/logo.svg" width="100" alt="Plume logo: a blue feather quill pen" align="right" />
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
Plume is a Ruby library for working with SQLite elegantly. Presently, this means that it provides a pure Ruby implementation of SQLite's lexer/tokenizer as well as a 100% compatible parser. This is useful for introspecting the structure of SQL queries, and for building tools that work with SQL queries - like working out which tables and columns a query is going to read.
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
## Installation
|
|
10
14
|
|
|
11
15
|
Install the gem and add to the application's Gemfile by executing:
|
|
12
16
|
|
|
13
17
|
```bash
|
|
14
|
-
bundle add
|
|
18
|
+
bundle add plume
|
|
15
19
|
```
|
|
16
20
|
|
|
17
21
|
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
18
22
|
|
|
19
23
|
```bash
|
|
20
|
-
gem install
|
|
24
|
+
gem install plume
|
|
21
25
|
```
|
|
22
26
|
|
|
23
27
|
## Usage
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
Parse a SQL query with the `Plume::Parser` class:
|
|
30
|
+
|
|
31
|
+
Every statement in SQLite's grammar parses, and the parser is checked against the SQL in SQLite's own test suite: of the 15,836 statements there, the ones it does not parse are those SQLite rejects as well.
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
sql = <<~SQL
|
|
35
|
+
CREATE TEMP TABLE IF NOT EXISTS `schema0`."tb0" (
|
|
36
|
+
c0 INTEGER PRIMARY KEY DESC ON CONFLICT ABORT AUTOINCREMENT,
|
|
37
|
+
c1 INT NOT NULL ON CONFLICT ROLLBACK,
|
|
38
|
+
c2 TEXT UNIQUE ON CONFLICT IGNORE,
|
|
39
|
+
c3 BLOB CHECK ( c3 > 0),
|
|
40
|
+
c4 REAL DEFAULT (1.1 * 2.2),
|
|
41
|
+
c5 ANY COLLATE RTRIM,
|
|
42
|
+
c6 DECIMAL(4, 6) GENERATED ALWAYS AS (1.1 + 2.2) STORED,
|
|
43
|
+
c7 REFERENCES tb1(c1) ON DELETE SET NULL ON UPDATE CASCADE MATCH FULL DEFERRABLE INITIALLY DEFERRED,
|
|
44
|
+
PRIMARY KEY (c0, c1 AUTOINCREMENT) ON CONFLICT ABORT,
|
|
45
|
+
UNIQUE (c0, c1) ON CONFLICT ROLLBACK
|
|
46
|
+
CHECK (c0 > 0)
|
|
47
|
+
FOREIGN KEY (c0) REFERENCES tb1(c1) ON DELETE CASCADE ON UPDATE RESTRICT MATCH FULL DEFERRABLE INITIALLY DEFERRED
|
|
48
|
+
) WITHOUT ROWID;
|
|
49
|
+
SQL
|
|
50
|
+
ast = Plume.parse(sql).first # one node per statement
|
|
51
|
+
# => Plume::CreateTableStatement(...)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
<details>
|
|
55
|
+
<summary>(click to see full AST)</summary>
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
Plume::CreateTableStatement(
|
|
59
|
+
temporary = true,
|
|
60
|
+
if_not_exists = true,
|
|
61
|
+
table = Plume::TableName(
|
|
62
|
+
schema = "schema0",
|
|
63
|
+
table = "tb0",
|
|
64
|
+
),
|
|
65
|
+
columns = [
|
|
66
|
+
Plume::ColumnDefinition(
|
|
67
|
+
name = "c0",
|
|
68
|
+
type = Plume::ColumnType(
|
|
69
|
+
text = "INTEGER",
|
|
70
|
+
affinity = :INTEGER,
|
|
71
|
+
),
|
|
72
|
+
constraints = [
|
|
73
|
+
Plume::PrimaryKeyColumnConstraint(...),
|
|
74
|
+
],
|
|
75
|
+
),
|
|
76
|
+
Plume::ColumnDefinition(
|
|
77
|
+
name = "c1",
|
|
78
|
+
type = Plume::ColumnType(
|
|
79
|
+
text = "INT",
|
|
80
|
+
affinity = :INTEGER,
|
|
81
|
+
),
|
|
82
|
+
constraints = [
|
|
83
|
+
Plume::NotNullColumnConstraint(...),
|
|
84
|
+
],
|
|
85
|
+
),
|
|
86
|
+
Plume::ColumnDefinition(
|
|
87
|
+
name = "c2",
|
|
88
|
+
type = Plume::ColumnType(
|
|
89
|
+
text = "TEXT",
|
|
90
|
+
affinity = :TEXT,
|
|
91
|
+
),
|
|
92
|
+
constraints = [
|
|
93
|
+
Plume::UniqueColumnConstraint(...),
|
|
94
|
+
],
|
|
95
|
+
),
|
|
96
|
+
Plume::ColumnDefinition(
|
|
97
|
+
name = "c3",
|
|
98
|
+
type = Plume::ColumnType(
|
|
99
|
+
text = "BLOB",
|
|
100
|
+
affinity = :BLOB,
|
|
101
|
+
),
|
|
102
|
+
constraints = [
|
|
103
|
+
Plume::CheckColumnConstraint(...),
|
|
104
|
+
],
|
|
105
|
+
),
|
|
106
|
+
Plume::ColumnDefinition(
|
|
107
|
+
name = "c4",
|
|
108
|
+
type = Plume::ColumnType(
|
|
109
|
+
text = "REAL",
|
|
110
|
+
affinity = :REAL,
|
|
111
|
+
),
|
|
112
|
+
constraints = [
|
|
113
|
+
Plume::DefaultColumnConstraint(...),
|
|
114
|
+
],
|
|
115
|
+
),
|
|
116
|
+
Plume::ColumnDefinition(
|
|
117
|
+
name = "c5",
|
|
118
|
+
type = Plume::ColumnType(
|
|
119
|
+
text = "ANY",
|
|
120
|
+
affinity = :ANY,
|
|
121
|
+
),
|
|
122
|
+
constraints = [
|
|
123
|
+
Plume::CollateColumnConstraint(...),
|
|
124
|
+
],
|
|
125
|
+
),
|
|
126
|
+
Plume::ColumnDefinition(
|
|
127
|
+
name = "c6",
|
|
128
|
+
type = Plume::ColumnType(
|
|
129
|
+
text = "DECIMAL(4, 6)",
|
|
130
|
+
affinity = :ANY,
|
|
131
|
+
),
|
|
132
|
+
constraints = [
|
|
133
|
+
Plume::GeneratedAsColumnConstraint(...),
|
|
134
|
+
],
|
|
135
|
+
),
|
|
136
|
+
Plume::ColumnDefinition(
|
|
137
|
+
name = "c7",
|
|
138
|
+
constraints = [
|
|
139
|
+
Plume::ForeignKeyColumnConstraint(...),
|
|
140
|
+
],
|
|
141
|
+
),
|
|
142
|
+
],
|
|
143
|
+
constraints = [
|
|
144
|
+
Plume::PrimaryKeyTableConstraint(
|
|
145
|
+
autoincrement = true,
|
|
146
|
+
columns = [
|
|
147
|
+
Plume::IndexedColumn(...),
|
|
148
|
+
Plume::IndexedColumn(...),
|
|
149
|
+
],
|
|
150
|
+
conflict_clause = Plume::ConflictClause(resolution = :ABORT),
|
|
151
|
+
),
|
|
152
|
+
Plume::UniqueTableConstraint(
|
|
153
|
+
columns = [
|
|
154
|
+
Plume::IndexedColumn(...),
|
|
155
|
+
Plume::IndexedColumn(...),
|
|
156
|
+
],
|
|
157
|
+
conflict_clause = Plume::ConflictClause(resolution = :ROLLBACK),
|
|
158
|
+
),
|
|
159
|
+
Plume::CheckTableConstraint(
|
|
160
|
+
expression = Plume::BinaryExpression(
|
|
161
|
+
operator = :ABOVE,
|
|
162
|
+
left = Plume::ColumnName(...),
|
|
163
|
+
right = Plume::LiteralExpression(...),
|
|
164
|
+
),
|
|
165
|
+
),
|
|
166
|
+
Plume::ForeignKeyTableConstraint(
|
|
167
|
+
columns = [Plume::IndexedColumn(...)],
|
|
168
|
+
foreign_key_clause = Plume::ForeignKeyClause(
|
|
169
|
+
table = "tb1",
|
|
170
|
+
deferred = true,
|
|
171
|
+
columns = [...],
|
|
172
|
+
actions = [...],
|
|
173
|
+
match_clauses = [...],
|
|
174
|
+
),
|
|
175
|
+
),
|
|
176
|
+
],
|
|
177
|
+
options = [
|
|
178
|
+
Plume::WithoutRowidTableOption(),
|
|
179
|
+
],
|
|
180
|
+
select_statement = Object(),
|
|
181
|
+
strict = false,
|
|
182
|
+
without_row_id = true,
|
|
183
|
+
)
|
|
184
|
+
```
|
|
185
|
+
</details>
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
Turn a tree back into SQL with `to_sql`. It prints from the nodes' values rather than copying the source, so trees built by hand or changed after parsing print too:
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
stmt = Plume.parse("select a from t where b = 1").first
|
|
192
|
+
stmt.body.condition = Plume::BinaryExpression.new(
|
|
193
|
+
operator: :ALL,
|
|
194
|
+
left: stmt.body.condition,
|
|
195
|
+
right: Plume::BinaryExpression.new(operator: :EQUALS, left: Plume::ColumnName.new(column: "tenant_id"), right: Plume::Variable.new(name: "?")),
|
|
196
|
+
)
|
|
197
|
+
stmt.to_sql
|
|
198
|
+
# => "SELECT a FROM t WHERE (b = 1) AND (tenant_id = ?)"
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Tokenize a SQL query with the `Plume::Lexer` class:
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
tokens = Plume.tokenize("SELECT * FROM users WHERE id = 1")
|
|
205
|
+
# => [:SELECT, :STAR, :FROM, :ID, :WHERE, :ID, :EQ, :INTEGER]
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
SQLite works with a grammar of _167_ token types, _136_ of which are keywords. The remaining _31_ are punctuation, operators, and literals.
|
|
209
|
+
|
|
210
|
+
### What does this query read?
|
|
211
|
+
|
|
212
|
+
`Plume.reads` works out which tables and columns a statement would read, looking through aliases, joins, subqueries, CTEs and views:
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
catalog = Plume::Catalog.from_sql(<<~SQL)
|
|
216
|
+
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT);
|
|
217
|
+
CREATE TABLE posts (id INTEGER PRIMARY KEY, user_id INT, title TEXT);
|
|
218
|
+
SQL
|
|
219
|
+
|
|
220
|
+
reads = Plume.reads(<<~SQL, catalog: catalog)
|
|
221
|
+
WITH recent AS (SELECT * FROM posts ORDER BY id DESC LIMIT 10)
|
|
222
|
+
SELECT r.title, u.name FROM recent r JOIN users u ON u.id = r.user_id
|
|
223
|
+
WHERE u.email LIKE '%@example.com'
|
|
224
|
+
SQL
|
|
225
|
+
|
|
226
|
+
reads.tables # => #<Set: {"posts", "users"}>
|
|
227
|
+
reads.columns # => {"posts" => #<Set: {"id", "user_id", "title"}>, "users" => #<Set: {"id", "name", "email"}>}
|
|
228
|
+
reads.projected # columns whose values can end up in the result rows
|
|
229
|
+
reads.referenced # columns only used to filter, join, sort or group
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
`Plume.access` does the same for `INSERT`, `UPDATE` and `DELETE`, and also tells you what they write:
|
|
233
|
+
|
|
234
|
+
```ruby
|
|
235
|
+
access = Plume.access(<<~SQL, catalog: catalog)
|
|
236
|
+
UPDATE posts SET title = upper(u.name) FROM users u WHERE u.id = posts.user_id RETURNING posts.id
|
|
237
|
+
SQL
|
|
238
|
+
|
|
239
|
+
access.writes.updated # => {"posts" => #<Set: {"title"}>}
|
|
240
|
+
access.reads.projected # => {"users" => #<Set: {"name"}>, "posts" => #<Set: {"id"}>}
|
|
241
|
+
access.reads.referenced # => {"users" => #<Set: {"id"}>, "posts" => #<Set: {"user_id"}>}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
A value copied into a column being written counts as projected, just like one returned in the result rows.
|
|
245
|
+
|
|
246
|
+
Both also take what you already parsed - a statement node, or the array `Plume.parse` returns - so the SQL is not parsed twice, and a tree you changed after parsing is analyzed as changed.
|
|
247
|
+
|
|
248
|
+
The catalog can also come from a live database with `Plume::Catalog.from_sqlite(db)`, or be left out - then `SELECT *` is reported as a read of the column `"*"` and unqualified columns only resolve when there is a single table in scope.
|
|
249
|
+
|
|
250
|
+
Anything that cannot be attributed (an unknown table or column, an ambiguous name) raises `Plume::Analysis::Error` with a pointer into the SQL. Pass `strict: false` to get the partial result and a list in `reads.unresolved` instead. Each entry of `reads.references` carries the AST node it came from, so you can point at it too - see [examples/read_guard.rb](examples/read_guard.rb) for a small column-level access check built on top.
|
|
251
|
+
|
|
252
|
+
The results are tested against SQLite's own [authorizer callback](https://www.sqlite.org/c3ref/set_authorizer.html), which is also the place to enforce access rules at runtime when you have a connection. `Plume.reads` is for when you want to vet SQL ahead of time, without a database, or with good error messages. It reports one thing the authorizer does not: the right-hand join columns of `USING` and `NATURAL` joins.
|
|
253
|
+
|
|
254
|
+
## Roadmap
|
|
255
|
+
|
|
256
|
+
- [x] `expression`
|
|
257
|
+
- [x] `alter_table_stmt`
|
|
258
|
+
- [x] `analyze_stmt`
|
|
259
|
+
- [x] `attach_stmt`
|
|
260
|
+
- [x] `begin_stmt`
|
|
261
|
+
- [x] `commit_stmt`
|
|
262
|
+
- [x] `create_table_stmt`
|
|
263
|
+
- [x] `create_index_stmt`
|
|
264
|
+
- [x] `create_trigger_stmt`
|
|
265
|
+
- [x] `create_view_stmt`
|
|
266
|
+
- [x] `create_virtual_table_stmt`
|
|
267
|
+
- [x] `delete_stmt`
|
|
268
|
+
- [x] `detach_stmt`
|
|
269
|
+
- [x] `drop_index_stmt`
|
|
270
|
+
- [x] `drop_table_stmt`
|
|
271
|
+
- [x] `drop_trigger_stmt`
|
|
272
|
+
- [x] `drop_view_stmt`
|
|
273
|
+
- [x] `insert_stmt`
|
|
274
|
+
- [x] `pragma_stmt`
|
|
275
|
+
- [x] `reindex_stmt`
|
|
276
|
+
- [x] `release_stmt`
|
|
277
|
+
- [x] `rollback_stmt`
|
|
278
|
+
- [x] `savepoint_stmt`
|
|
279
|
+
- [x] `select_stmt`
|
|
280
|
+
- [x] `update_stmt`
|
|
281
|
+
- [x] `vacuum_stmt`
|
|
282
|
+
- [x] `with_stmt`
|
|
283
|
+
|
|
284
|
+
## Performance
|
|
285
|
+
|
|
286
|
+
`bin/benchmark` runs the whole fixture corpus (15,817 statements, 4.2 MB of SQL). On an Apple M-series laptop with Ruby 3.4:
|
|
287
|
+
|
|
288
|
+
| Step | Statements/s | MB/s |
|
|
289
|
+
| --- | ---: | ---: |
|
|
290
|
+
| `Plume.tokenize` | ~13,500 | 3.7 |
|
|
291
|
+
| `Plume.parse` | ~1,400 | 0.38 |
|
|
292
|
+
| `Node#to_sql` | ~6,000 | 1.7 |
|
|
293
|
+
| `Plume.reads` (no catalog) | ~1,000 | 0.21 |
|
|
294
|
+
|
|
295
|
+
The corpus leans on very large generated expressions; a typical application query parses in well under a millisecond. About half of the parse time goes to Literal's runtime type checks on every node property.
|
|
26
296
|
|
|
27
297
|
## Development
|
|
28
298
|
|
|
29
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
299
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. `bin/fixture-report` shows how much of SQLite's own test SQL parses (pass `--write` to record it as the new baseline the tests hold us to), and `bin/benchmark` times the whole pipeline. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
30
300
|
|
|
31
301
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
32
302
|
|
|
33
303
|
## Contributing
|
|
34
304
|
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
305
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/yippee-fun/plume. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/yippee-fun/plume/blob/main/CODE_OF_CONDUCT.md).
|
|
36
306
|
|
|
37
307
|
## License
|
|
38
308
|
|
|
@@ -40,4 +310,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
|
40
310
|
|
|
41
311
|
## Code of Conduct
|
|
42
312
|
|
|
43
|
-
Everyone interacting in the Plume project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/
|
|
313
|
+
Everyone interacting in the Plume project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/yippee-fun/plume/blob/main/CODE_OF_CONDUCT.md).
|