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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +280 -10
  3. data/lib/plume/analysis/resolver.rb +574 -0
  4. data/lib/plume/analysis.rb +135 -0
  5. data/lib/plume/ast/alter_table_statement.rb +29 -0
  6. data/lib/plume/ast/analyze_statement.rb +15 -0
  7. data/lib/plume/ast/assignment.rb +18 -0
  8. data/lib/plume/ast/attach_statement.rb +17 -0
  9. data/lib/plume/ast/begin_statement.rb +17 -0
  10. data/lib/plume/ast/binary_expression.rb +71 -0
  11. data/lib/plume/ast/case_condition.rb +29 -0
  12. data/lib/plume/ast/case_expression.rb +22 -0
  13. data/lib/plume/ast/cast_expression.rb +16 -0
  14. data/lib/plume/ast/check_column_constraint.rb +20 -0
  15. data/lib/plume/ast/check_table_constraint.rb +19 -0
  16. data/lib/plume/ast/collate_column_constraint.rb +19 -0
  17. data/lib/plume/ast/collation_expression.rb +26 -0
  18. data/lib/plume/ast/column_constraint.rb +7 -0
  19. data/lib/plume/ast/column_definition.rb +16 -0
  20. data/lib/plume/ast/column_name.rb +22 -0
  21. data/lib/plume/ast/column_type.rb +25 -0
  22. data/lib/plume/ast/commit_statement.rb +13 -0
  23. data/lib/plume/ast/common_table_expression.rb +31 -0
  24. data/lib/plume/ast/compound_select.rb +30 -0
  25. data/lib/plume/ast/conflict_clause.rb +22 -0
  26. data/lib/plume/ast/conflict_resolution.rb +20 -0
  27. data/lib/plume/ast/create_index_statement.rb +29 -0
  28. data/lib/plume/ast/create_table_statement.rb +47 -0
  29. data/lib/plume/ast/create_trigger_statement.rb +43 -0
  30. data/lib/plume/ast/create_view_statement.rb +30 -0
  31. data/lib/plume/ast/create_virtual_table_statement.rb +28 -0
  32. data/lib/plume/ast/default_column_constraint.rb +29 -0
  33. data/lib/plume/ast/delete_statement.rb +28 -0
  34. data/lib/plume/ast/detach_statement.rb +13 -0
  35. data/lib/plume/ast/drop_statement.rb +21 -0
  36. data/lib/plume/ast/empty_function_argument.rb +6 -0
  37. data/lib/plume/ast/exists_expression.rb +15 -0
  38. data/lib/plume/ast/explain_statement.rb +15 -0
  39. data/lib/plume/ast/expression.rb +30 -0
  40. data/lib/plume/ast/foreign_key_action.rb +26 -0
  41. data/lib/plume/ast/foreign_key_clause.rb +50 -0
  42. data/lib/plume/ast/foreign_key_column_constraint.rb +16 -0
  43. data/lib/plume/ast/foreign_key_table_constraint.rb +20 -0
  44. data/lib/plume/ast/frame_boundary.rb +35 -0
  45. data/lib/plume/ast/frame_spec.rb +34 -0
  46. data/lib/plume/ast/function_arguments.rb +17 -0
  47. data/lib/plume/ast/function_reference.rb +29 -0
  48. data/lib/plume/ast/generated_as_column_constraint.rb +24 -0
  49. data/lib/plume/ast/indexed_column.rb +21 -0
  50. data/lib/plume/ast/insert_statement.rb +35 -0
  51. data/lib/plume/ast/join.rb +12 -0
  52. data/lib/plume/ast/join_clause.rb +15 -0
  53. data/lib/plume/ast/join_on_constraint.rb +12 -0
  54. data/lib/plume/ast/join_operator.rb +31 -0
  55. data/lib/plume/ast/join_using_constraint.rb +16 -0
  56. data/lib/plume/ast/like_expression.rb +19 -0
  57. data/lib/plume/ast/literal_expression.rb +29 -0
  58. data/lib/plume/ast/literal_false.rb +7 -0
  59. data/lib/plume/ast/literal_nil.rb +7 -0
  60. data/lib/plume/ast/literal_value.rb +18 -0
  61. data/lib/plume/ast/match_clause.rb +15 -0
  62. data/lib/plume/ast/node.rb +207 -0
  63. data/lib/plume/ast/noop_column_constraint.rb +15 -0
  64. data/lib/plume/ast/noop_table_constraint.rb +15 -0
  65. data/lib/plume/ast/not_like_expression.rb +7 -0
  66. data/lib/plume/ast/not_null_column_constraint.rb +17 -0
  67. data/lib/plume/ast/null_column_constraint.rb +17 -0
  68. data/lib/plume/ast/ordering_term.rb +32 -0
  69. data/lib/plume/ast/over_clause.rb +25 -0
  70. data/lib/plume/ast/parenthetical_expression.rb +37 -0
  71. data/lib/plume/ast/parenthetical_source.rb +19 -0
  72. data/lib/plume/ast/pragma_statement.rb +26 -0
  73. data/lib/plume/ast/primary_key_column_constraint.rb +23 -0
  74. data/lib/plume/ast/primary_key_table_constraint.rb +23 -0
  75. data/lib/plume/ast/raise_expression.rb +23 -0
  76. data/lib/plume/ast/reindex_statement.rb +15 -0
  77. data/lib/plume/ast/release_statement.rb +16 -0
  78. data/lib/plume/ast/result_column.rb +18 -0
  79. data/lib/plume/ast/returning_clause.rb +12 -0
  80. data/lib/plume/ast/rollback_statement.rb +19 -0
  81. data/lib/plume/ast/savepoint_statement.rb +16 -0
  82. data/lib/plume/ast/select_core.rb +27 -0
  83. data/lib/plume/ast/select_statement.rb +34 -0
  84. data/lib/plume/ast/star_function_argument.rb +6 -0
  85. data/lib/plume/ast/star_result_column.rb +16 -0
  86. data/lib/plume/ast/strict_table_option.rb +6 -0
  87. data/lib/plume/ast/subquery_expression.rb +14 -0
  88. data/lib/plume/ast/subquery_source.rb +19 -0
  89. data/lib/plume/ast/table_constraint.rb +7 -0
  90. data/lib/plume/ast/table_function.rb +28 -0
  91. data/lib/plume/ast/table_name.rb +28 -0
  92. data/lib/plume/ast/table_option.rb +7 -0
  93. data/lib/plume/ast/ternary_expression.rb +26 -0
  94. data/lib/plume/ast/token.rb +180 -0
  95. data/lib/plume/ast/unary_expression.rb +36 -0
  96. data/lib/plume/ast/unique_column_constraint.rb +17 -0
  97. data/lib/plume/ast/unique_table_constraint.rb +20 -0
  98. data/lib/plume/ast/update_statement.rb +35 -0
  99. data/lib/plume/ast/upsert_clause.rb +26 -0
  100. data/lib/plume/ast/vacuum_statement.rb +18 -0
  101. data/lib/plume/ast/values_clause.rb +12 -0
  102. data/lib/plume/ast/values_row.rb +13 -0
  103. data/lib/plume/ast/variable.rb +15 -0
  104. data/lib/plume/ast/window_definition.rb +26 -0
  105. data/lib/plume/ast/with_clause.rb +15 -0
  106. data/lib/plume/ast/without_rowid_table_option.rb +6 -0
  107. data/lib/plume/catalog.rb +180 -0
  108. data/lib/plume/lexer.rb +639 -0
  109. data/lib/plume/parser.rb +4015 -0
  110. data/lib/plume/printer.rb +471 -0
  111. data/lib/plume/sqlite_grammar.rb +86 -0
  112. data/lib/plume/union.rb +28 -0
  113. data/lib/plume/version.rb +1 -1
  114. data/lib/plume.rb +143 -3
  115. metadata +167 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9e39a7313fe99e4e087c4d486cf07bdda3fc549f6f218b88379bf548ea4f3d0
4
- data.tar.gz: c1e7f06a6894e747befa1a6c4243bd56a6452a93415ad955ddead14ec01e77d1
3
+ metadata.gz: 5233a7cda234897d9b0cca09246777a1cd343d3da52d9fef922a11eddb230195
4
+ data.tar.gz: 539669c43ded2e665a44f44f33d43354c050243c02afc7b08de2b4ecb41f0709
5
5
  SHA512:
6
- metadata.gz: 482bc86238132bf117c58a6aec4bc40839f9c203dc525ccfe57f10aa3ded097efd4aa5b593a43adbd8ca33def03a013b37f9487833d24733ff9e0443557a58dd
7
- data.tar.gz: 6373ed11b0debe32f204204aeb69bc2fa6708f6861e7a0ef02f7a9ab75e0870204bfcd825fb4c0e1436740435a2669fcdd06731997be7c8d76e7cd1d5a5f5200
6
+ metadata.gz: eb601a1c93cfc62614a3188053f20401ccd4213e9b5274f8f71af8ec567b66ecffeb18e796b9bfad4499d55a80be5ddf0a2a646086b633aadc4ba2093bbde46d
7
+ data.tar.gz: 79172b07840a82d4a35a201e51fbbe589763cc0160fe7ac2360d9254bf0ca9789e1c9b0ea3cba3e33769d9cb6945cc700e07fe4620f7a0f6514246cb54ac3a04
data/README.md CHANGED
@@ -1,38 +1,308 @@
1
1
  # Plume
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
3
+ [![Gem Version](https://badge.fury.io/rb/plume.svg)](https://rubygems.org/gems/plume)
4
+ [![Gem Downloads](https://img.shields.io/gem/dt/plume)](https://rubygems.org/gems/plume)
5
+ ![Tests](https://github.com/yippee-fun/plume/actions/workflows/main.yml/badge.svg)
6
+ [![Sponsors](https://img.shields.io/github/sponsors/fractaledmind?color=eb4aaa&logo=GitHub%20Sponsors)](https://github.com/sponsors/fractaledmind)
7
+ [![Twitter Follow](https://img.shields.io/twitter/url?label=%40fractaledmind&style=social&url=https%3A%2F%2Ftwitter.com%2Ffractaledmind)](https://twitter.com/fractaledmind)
4
8
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/plume`. To experiment with that code, run `bin/console` for an interactive prompt.
9
+ <img src="/assets/logo.svg" width="100" alt="Plume logo: a blue feather quill pen" align="right" />
6
10
 
7
- ## Installation
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
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
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 UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
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 UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
24
+ gem install plume
21
25
  ```
22
26
 
23
27
  ## Usage
24
28
 
25
- TODO: Write usage instructions here
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/[USERNAME]/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/[USERNAME]/plume/blob/main/CODE_OF_CONDUCT.md).
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/[USERNAME]/plume/blob/main/CODE_OF_CONDUCT.md).
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).