arfi 0.5.0 → 1.0.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.env.sample +2 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +8 -1
  5. data/.rubocop_todo.yml +12 -0
  6. data/CHANGELOG.md +94 -0
  7. data/CODE_OF_CONDUCT.md +5 -1
  8. data/CONTRIBUTING.md +26 -0
  9. data/README.md +327 -118
  10. data/SECURITY.md +17 -0
  11. data/Steepfile +1 -29
  12. data/compose.yml +33 -0
  13. data/docscribe.yml +92 -0
  14. data/gemfiles/rails_6_0.gemfile +11 -0
  15. data/gemfiles/rails_6_1.gemfile +11 -0
  16. data/gemfiles/rails_7_0.gemfile +9 -0
  17. data/gemfiles/rails_7_1.gemfile +10 -0
  18. data/gemfiles/rails_7_2.gemfile +10 -0
  19. data/gemfiles/rails_8_0.gemfile +10 -0
  20. data/gemfiles/rails_8_1.gemfile +10 -0
  21. data/lib/arfi/cli.rb +24 -9
  22. data/lib/arfi/commands/f_idx.rb +5 -230
  23. data/lib/arfi/commands/functions.rb +128 -0
  24. data/lib/arfi/commands/functions_candidates.rb +133 -0
  25. data/lib/arfi/commands/functions_creation.rb +157 -0
  26. data/lib/arfi/commands/functions_helpers.rb +181 -0
  27. data/lib/arfi/commands/functions_paths.rb +131 -0
  28. data/lib/arfi/commands/functions_rendering.rb +137 -0
  29. data/lib/arfi/commands/init.rb +88 -0
  30. data/lib/arfi/commands/project.rb +5 -51
  31. data/lib/arfi/errors.rb +15 -3
  32. data/lib/arfi/extensions/active_record/base.rb +33 -23
  33. data/lib/arfi/extensions/active_record/connection_adapters/postgresql/database_statements.rb +159 -24
  34. data/lib/arfi/sql_function_loader.rb +291 -88
  35. data/lib/arfi/tasks/db.rake +60 -18
  36. data/lib/arfi/version.rb +1 -1
  37. data/lib/arfi.rb +2 -0
  38. data/rbs_collection.lock.yaml +93 -61
  39. data/sig/compat/active_record_base_compat.rbs +17 -0
  40. data/sig/compat/thor_dsl.rbs +8 -0
  41. data/sig/lib/arfi/commands/f_idx.rbs +5 -103
  42. data/sig/lib/arfi/commands/functions.rbs +99 -0
  43. data/sig/lib/arfi/commands/functions_candidates.rbs +25 -0
  44. data/sig/lib/arfi/commands/functions_creation.rbs +29 -0
  45. data/sig/lib/arfi/commands/functions_helpers.rbs +41 -0
  46. data/sig/lib/arfi/commands/functions_paths.rbs +25 -0
  47. data/sig/lib/arfi/commands/functions_rendering.rbs +25 -0
  48. data/sig/lib/arfi/commands/init.rbs +22 -0
  49. data/sig/lib/arfi/commands/project.rbs +5 -24
  50. data/sig/lib/arfi/extensions/active_record/base.rbs +5 -10
  51. data/sig/lib/arfi/extensions/active_record/connection_adapters/postgresql/database_statements.rbs +28 -0
  52. data/sig/lib/arfi/sql_function_loader.rbs +45 -87
  53. data/sig/lib/arfi/tasks/db.rbs +3 -0
  54. data/sig/lib/arfi/version.rbs +1 -1
  55. metadata +125 -14
data/README.md CHANGED
@@ -7,45 +7,52 @@
7
7
 
8
8
  ---
9
9
 
10
- > [!WARNING]
11
- > This project only supports PostgreSQL and MySQL databases. SQLite3 will be supported in the future as well as other
12
- > databases supported by Rails.
10
+ ARFI – *ActiveRecord Functions Integration*
13
11
 
14
- > [!NOTE]
15
- > This project requires Ruby 3.1.0+, in future updated 2.6+ Ruby versions will be supported.
12
+ ARFI helps Rails apps create and maintain **custom SQL functions** (often used for functional indexes and query helpers)
13
+ without switching from `schema.rb` to `structure.sql`.
16
14
 
17
- ---
15
+ ARFI follows a **"current state"** model: your repository contains the current function definitions in
16
+ `db/functions/**`, and ARFI loads them into the database automatically during common Rails DB tasks
17
+ (e.g., `db:prepare`, `db:test:prepare`, `db:migrate`, etc.).
18
+
19
+ PostgreSQL bonus: ARFI can recover at runtime from `PG::UndefinedFunction` by loading managed function files and
20
+ retrying the failed query once (thread-guarded).
21
+
22
+ ARFI supports both single-DB and multi-DB Rails setups.
18
23
 
19
- ARFI *ActiveRecord Functional Indexes*
24
+ Demo project: https://github.com/unurgunite/poc_arfi_72
20
25
 
21
- The ARFI gem provides the ability to create and maintain custom SQL functions for ActiveRecord models without switching
22
- to `structure.sql` (an SQL-based schema). You can use your own SQL functions in any part of the project, from migrations
23
- and models to everything else. There is a working example in
24
- the [demo project](https://github.com/unurgunite/poc_arfi_72). All instructions are described
25
- in [README](https://github.com/unurgunite/poc_arfi_72/blob/master/README.md). ARFI supports all types of database
26
- architectures implemented in Rails, suitable for both working with single databases and for simultaneous work with
27
- multiple databases in the same environment.
26
+ ---
28
27
 
29
28
  * [ARFI](#arfi)
30
29
  * [Installation](#installation)
31
30
  * [Usage](#usage)
32
31
  * [Internal documentation](#internal-documentation)
33
32
  * [CLI](#cli)
34
- * [Project creation](#project-creation)
35
- * [Index creation](#index-creation)
36
- * [Index destroy](#index-destroy)
33
+ * [Project initialization](#project-initialization)
34
+ * [Directory layout (1.0.0+)](#directory-layout-100)
35
+ * [Function creation](#function-creation)
36
+ * [Function destroy](#function-destroy)
37
37
  * [Additional help](#additional-help)
38
+ * [Architecture](#architecture)
39
+ * [Entry points](#entry-points)
40
+ * [Decision flow](#decision-flow)
41
+ * [Key design points](#key-design-points)
38
42
  * [Demo](#demo)
39
43
  * [Library features](#library-features)
40
- * [Roadmap](#roadmap)
44
+ * [TODO](#todo)
41
45
  * [Commands](#commands)
42
- * [Function creation](#function-creation)
43
- * [Function destroy](#function-destroy)
46
+ * [Function creation](#function-creation-1)
47
+ * [Function destroy](#function-destroy-1)
44
48
  * [Options](#options)
45
49
  * [`--template` option](#--template-option)
46
50
  * [`--adapter` option](#--adapter-option)
51
+ * [`--schema` option](#--schema-option)
52
+ * [`--force` option](#--force-option)
47
53
  * [Limitations](#limitations)
48
54
  * [Development](#development)
55
+ * [Run tests locally](#run-tests-locally)
49
56
  * [Build from source](#build-from-source)
50
57
  * [Requirements](#requirements)
51
58
  * [Contributing](#contributing)
@@ -71,28 +78,161 @@ Internal documentation available at https://github.com/unurgunite/arfi_docs.
71
78
 
72
79
  ARFI uses Thor as a command line interface (CLI) instead of Rake, so it has a specific DSL.
73
80
 
74
- ### Project creation
81
+ ### Project initialization
82
+
83
+ Run:
84
+
85
+ ```bash
86
+ bundle exec arfi init
87
+ ```
88
+
89
+ This ensures the directory structure exists:
90
+
91
+ ```
92
+ db/functions/public
93
+ db/functions/postgresql/public
94
+ db/functions/mysql/public
95
+ ```
96
+
97
+ Backwards-compatible alias:
98
+
99
+ ```bash
100
+ bundle exec arfi project create
101
+ ```
102
+
103
+ ### Directory layout (1.0.0+)
104
+
105
+ Canonical layout (explicit `public`):
106
+
107
+ - Generic public: `db/functions/public/<function>.sql`
108
+ - PostgreSQL public: `db/functions/postgresql/public/<function>.sql`
109
+ - PostgreSQL schema: `db/functions/postgresql/<schema>/<function>.sql`
110
+ - MySQL / MariaDB / Trilogy public: `db/functions/mysql/public/<function>.sql`
75
111
 
76
- Firstly, run `bundle exec arfi project create` to create a new project. This command will create `db/functions`
77
- directory. ARFI uses `db/functions` directory to store your SQL functions.
112
+ Adapter-specific files override generic files by basename (same `<function>.sql`).
113
+ Files starting with `_` are ignored.
78
114
 
79
115
  ### Function creation
80
116
 
81
- Run `bundle exec arfi f_idx create function_name` to create a new function. New SQL function will be created in
82
- `db/functions` directory under `function_name_v01.sql` name. Edit your function and run `bundle exec rails db:migrate`.
83
- You can also use custom template for functions using `--template` flag, this behaviour is described below.
84
- Type `bundle exec arfi f_idx help create` for additional info.
117
+ Run:
118
+
119
+ ```bash
120
+ bundle exec arfi functions create function_name
121
+ ```
122
+
123
+ The file location depends on adapter:
124
+
125
+ - PostgreSQL: `db/functions/postgresql/public/function_name.sql`
126
+ - MySQL/Trilogy: `db/functions/mysql/public/function_name.sql`
127
+ - Generic: `db/functions/public/function_name.sql`
128
+
129
+ Edit the function SQL and run your usual DB task (`db:migrate`, `db:prepare`, etc.).
130
+
131
+ You can also use a custom template for functions using the `--template` flag; this behaviour is described below.
132
+ Type `bundle exec arfi functions help create` for additional info.
133
+
134
+ Backwards-compatible alias:
135
+
136
+ ```bash
137
+ bundle exec arfi f_idx create function_name
138
+ ```
85
139
 
86
140
  ### Function destroy
87
141
 
88
- If you want to destroy your function, run `bundle exec arfi f_idx destroy function_name [revision (default 1)]`. Please
89
- note that after deleting the function, it will still be available, but if you run "bundle exec rails db:migrate" again,
90
- an error will occur when using the function. Enter `bundle exec arfi f_idx help destroy` for more information.
142
+ Destroy deletes the function file from disk:
143
+
144
+ ```bash
145
+ bundle exec arfi functions destroy function_name
146
+ ```
147
+
148
+ Backwards-compatible alias:
149
+
150
+ ```bash
151
+ bundle exec arfi f_idx destroy function_name
152
+ ```
91
153
 
92
154
  ### Additional help
93
155
 
94
156
  Run `bundle exec arfi` for additional help.
95
157
 
158
+ ## Architecture
159
+
160
+ ARFI loads SQL function files into your database(s) during common Rails DB tasks. The flow depends on the entry point, database configuration, and adapter.
161
+
162
+ ### Entry points
163
+
164
+ | Entry point | Description |
165
+ |-----------------------------|----------------------------------------------------|
166
+ | `db:migrate` / `db:prepare` | Non-suffixed Rake tasks -> `_db:arfi_enhance` |
167
+ | `db:migrate:animals` | Suffixed Rake task -> `run_with_connection_switch` |
168
+ | Runtime retry | `PG::UndefinedFunction` -> auto-reload |
169
+ | Direct call | `SqlFunctionLoader.load!` from anywhere |
170
+
171
+ ### Decision flow
172
+
173
+ ```mermaid
174
+ flowchart TD
175
+ A(["SqlFunctionLoader.load!"]) --> B{"connection<br>passed?"}
176
+ B -->|"Yes"| C["Use passed connection"]
177
+ B -->|"No"| D{"multi_db?<br>AND<br>task_name nil?"}
178
+
179
+ D -->|"No"| E["populate_db<br>(default_connection)"]
180
+ D -->|"Yes"| F["populate_multiple_db"]
181
+
182
+ F --> F1["Save original connection config"]
183
+ F1 --> F2["For each DB config:"]
184
+ F2 --> F3["establish_connection(config)"]
185
+ F3 --> F4["populate_db(conn)"]
186
+ F4 --> F2
187
+ F2 --> F5["Restore original connection config"]
188
+
189
+ C --> G["Check adapter support"]
190
+ E --> G
191
+ F5 --> G
192
+
193
+ G --> H["Collect SQL files"]
194
+ H --> I["db/functions/public/*.sql<br>(generic, priority 1-2)"]
195
+ H --> J["db/functions/{pg,mysql}/**/*.sql<br>(adapter-specific, priority 8-10)"]
196
+
197
+ I --> K["Override resolution:<br>higher priority wins"]
198
+ J --> K
199
+ K --> L["Ignore _prefixed files"]
200
+ L --> M["Execute each SQL file"]
201
+
202
+ M --> N{"clear_active_connections?"}
203
+ N -->|"Yes"| O["connection_handler<br>.clear_active_connections!"]
204
+ N -->|"No"| P["Skip cleanup"]
205
+
206
+ subgraph Entry["Entry Points"]
207
+ Q1["Rake: db:migrate"]
208
+ Q2["Rake: db:migrate:animals"]
209
+ Q3["Runtime: PG::UndefinedFunction"]
210
+ Q4["Direct call"]
211
+ end
212
+
213
+ Q1 --> R1["_db:arfi_enhance<br>load!(task_name: nil)"]
214
+ R1 --> A
215
+
216
+ Q2 --> R2["_db:arfi_enhance:db:migrate:animals"]
217
+ R2 --> R3["run_with_connection_switch"]
218
+ R3 --> R4["Save original config"]
219
+ R4 --> R5["establish_connection(animals)"]
220
+ R5 --> A
221
+ R5 --> R6["Restore original config"]
222
+
223
+ Q3 --> R7["arfi_try_reload_and_retry?"]
224
+ R7 --> R8["load!(task_name: 'arfi:runtime',<br>connection: self,<br>clear_active_connections: false)"]
225
+ R8 --> A
226
+ ```
227
+
228
+ ### Key design points
229
+
230
+ - **Single-DB (default)**: loads functions into one connection, one `populate_db` call.
231
+ - **Multi-DB**: `populate_multiple_db` iterates over all configs for the current environment, loading functions into each. The original connection is saved before the loop and restored afterwards, matching the pattern used in `run_with_connection_switch`.
232
+ - **Suffixed tasks** (`db:migrate:animals`): switch to the named database, load functions, then restore the original connection.
233
+ - **Runtime retry**: triggered only for `PG::UndefinedFunction`, loads functions on the same connection without clearing other active connections.
234
+ - **File override**: adapter-specific files take priority over generic files. The highest priority file wins per schema+filename key.
235
+
96
236
  ## Demo
97
237
 
98
238
  Demo available as separate project built with Rails 7.2 and PostgreSQL 14: https://github.com/unurgunite/poc_arfi_72.
@@ -100,43 +240,42 @@ README is also available.
100
240
 
101
241
  ## Library features
102
242
 
103
- 1. ARFI supports about all types of database initialization. It respects your database schema format and database
104
- configuration.
105
-
106
- | Task | Completed |
107
- |------------------|--------------------------------------------------------------|
108
- | db:migrate | :white_check_mark: |
109
- | db:setup | :white_check_mark: |
110
- | db:prepare | :white_check_mark: |
111
- | db:schema:load | :white_check_mark: |
112
- | db:reset | :white_check_mark: |
113
- | db:setup:db_name | In progress (see [limitations][1]) :arrows_counterclockwise: |
114
-
115
- 2. Database support. ARFI supports PostgreSQL and MySQL databases and projects with multiple databases at the same time.
116
-
117
- | DB adapter | Tested |
118
- |------------|---------------------------------------|
119
- | PostgreSQL | :white_check_mark: |
120
- | MySQL | :white_check_mark: |
121
- | SQLite3 | In progress :arrows_counterclockwise: |
243
+ 1. ARFI supports most Rails database initialization flows and respects your schema format and database configuration.
244
+
245
+ | Task | Supported |
246
+ |--------------------------|-----------|
247
+ | db:migrate | ✅ |
248
+ | db:setup | |
249
+ | db:prepare | |
250
+ | db:test:prepare | |
251
+ | db:schema:load | |
252
+ | db:migrate:db_name | |
253
+ | db:prepare:db_name | |
254
+ | db:schema:load:db_name | ✅ |
255
+
256
+ 2. Database support. ARFI supports PostgreSQL and MySQL-compatible databases, including multi-db setups.
257
+
258
+ | DB adapter / client | Tested |
259
+ |-----------------------|---------------------------------|
260
+ | PostgreSQL | |
261
+ | MySQL (mysql2) | |
262
+ | MariaDB (mysql2) | ✅ |
263
+ | Trilogy (Rails 7.1+) | ✅ |
264
+ | SQLite3 | Not supported (see Limitations) |
122
265
 
123
266
  3. Rails support
124
267
 
125
- | Rails version | Tested |
126
- |---------------|---------------------------------------|
127
- | 8 | :white_check_mark: |
128
- | 7 | :white_check_mark: |
129
- | 6 | In progress :arrows_counterclockwise: |
268
+ | Rails version | Tested |
269
+ |---------------|--------|
270
+ | 8 | |
271
+ | 7 | |
272
+ | 6 | |
130
273
 
131
- ## Roadmap
274
+ ## TODO
132
275
 
133
- 1. ~~Custom template for SQL functions using `--template` flag;~~
134
- 2. ~~Multidb support (Rails 6+ feature);~~
135
- 3. Add support for 4+ ActiveRecord;
136
- 4. Add RSpec tests;
137
- 5. ~~Add separate YARD doc page;~~
138
- 6. ~~Update CI/CD;~~
139
- 7. Add support for Ruby 2.6+.
276
+ 1. Add `functions validate` / `functions doctor` command group (planned for 1.1.0);
277
+ 2. Add functions autoloader (v1.2.0);
278
+ 3. Add more adapters (Oracle, MSSQL).
140
279
 
141
280
  ## Commands
142
281
 
@@ -145,79 +284,150 @@ ARFI uses Thor as a command line interface.
145
284
 
146
285
  ### Function creation
147
286
 
148
- ARFI supports creation of SQL functions. To create a new function, run `bundle exec arfi f_idx create function_name`.
287
+ To create a new function file, run:
288
+
289
+ ```bash
290
+ bundle exec arfi functions create function_name
291
+ ```
292
+
149
293
  Also, there are some options:
150
294
 
151
- | Option name | Description | Possible values | Default value |
152
- |--------------|--------------------------------------------------------------------------------------|----------------------------|---------------------------------------------------------------|
153
- | `--template` | use custom template | path within you filesystem | nil (will be used default template for each type of adapters) |
154
- | `--adapter` | adapter specific function creation due to syntax differences between different RDBMS | postgresql, mysql | nil (function will be stored in generic `db/functions`) |
295
+ | Option name | Description | Possible values | Default value |
296
+ |--------------|-----------------------------------------------------------------------|-------------------------------|-------------------------------------|
297
+ | `--template` | Use custom Ruby template that returns SQL | path within your filesystem | nil |
298
+ | `--adapter` | Store function in adapter directory and use adapter skeleton/template | postgresql, mysql, trilogy | nil (generic `db/functions/public`) |
299
+ | `--schema` | PostgreSQL schema (alternative to `schema.function` form) | schema name (postgresql only) | public |
300
+ | `--force` | Overwrite existing function file if it already exists | true/false | false |
301
+
302
+ Backwards-compatible alias:
303
+
304
+ ```bash
305
+ bundle exec arfi f_idx create function_name
306
+ ```
155
307
 
156
308
  ### Function destroy
157
309
 
158
- ARFI supports destroy of SQL functions. To destroy a function, run
159
- `bundle exec arfi f_idx destroy function_name [revision (1 by default)]`.
310
+ To destroy a function file, run:
160
311
 
161
- | Option name | Description | Possible values | Default value |
162
- |--------------|------------------------------|-------------------|------------------------------------------------------------|
163
- | `--revision` | Function revision to destroy | Integer | 1 |
164
- | `--adapter` | adapter specific function | postgresql, mysql | nil (function will be destroyed in generic `db/functions`) |
312
+ ```bash
313
+ bundle exec arfi functions destroy function_name
314
+ ```
315
+
316
+ | Option name | Description | Possible values | Default value |
317
+ |-------------|------------------------------------------------------|-------------------------------|---------------|
318
+ | `--adapter` | Adapter-specific function directory | postgresql, mysql, trilogy | nil (generic) |
319
+ | `--schema` | PostgreSQL schema (alternative to `schema.function`) | schema name (postgresql only) | public |
320
+
321
+ Backwards-compatible alias:
322
+
323
+ ```bash
324
+ bundle exec arfi f_idx destroy function_name
325
+ ```
165
326
 
166
327
  #### Options
167
328
 
168
329
  ##### `--template` option
169
330
 
170
331
  This option is used for creating an SQL function. In this case, the function will not be created with the default
171
- template, but with user defined. There are some rules for templates:
332
+ template, but with a user-defined one. There are some rules for templates:
333
+
334
+ 1. The template must be written in Ruby-compatible syntax: the function must be placed in a HEREDOC statement and must
335
+ use interpolation for variables. You can use helper methods in the template file. The main rule is that the template
336
+ must evaluate to a String with SQL.
337
+ 2. ARFI supports dynamic variables in templates:
338
+ - `index_name` (backward compatible) / `function_name`
339
+ - `schema_name`
340
+ - `qualified_name`
341
+ - `original_ref`
172
342
 
173
- 1. The template must be written in a Ruby-compatible syntax: the function must be placed in a HEREDOC statement and must
174
- use interpolation for variables. If you need to take a more comprehensive approach to the issue of function
175
- generation, you can try using your own methods in the template file. No matter what you write there, the main rule is
176
- that your main method should return a string with a function template, as described below.
177
- 2. ARFI supports dynamic variables in templates, but only one at the moment. You need to specify `index_name`
178
- variable as below. In feature updated ARFI will support more variables. Here are default templates in ARFI for
179
- PostgreSQL and MySQL:
343
+ Default templates:
180
344
 
181
345
  PostgreSQL:
182
- ```ruby
183
- <<~SQL
184
- CREATE OR REPLACE FUNCTION #{index_name}() RETURNS TEXT[]
185
- LANGUAGE SQL
186
- IMMUTABLE AS
187
- $$
188
- -- Function body here
189
- $$
190
- SQL
191
- ```
346
+ ```ruby
347
+ <<~SQL
348
+ CREATE OR REPLACE FUNCTION #{qualified_name}() RETURNS TEXT[]
349
+ LANGUAGE SQL
350
+ IMMUTABLE AS
351
+ $$
352
+ -- Function body here
353
+ $$
354
+ SQL
355
+ ```
356
+
192
357
  MySQL:
193
- ```ruby
194
- <<~SQL
195
- CREATE FUNCTION #{index_name} ()
196
- RETURNS return_type
197
- BEGIN
198
- -- Function body here
199
- END;
200
- SQL
201
- ```
202
- 3. By default ARFI uses PostgreSQL template.
358
+ ```ruby
359
+ <<~SQL
360
+ CREATE FUNCTION #{function_name} ()
361
+ RETURNS return_type
362
+ BEGIN
363
+ -- Function body here
364
+ END;
365
+ SQL
366
+ ```
367
+
368
+ 3. By default, ARFI uses PostgreSQL template.
203
369
 
204
370
  ##### `--adapter` option
205
371
 
206
372
  This option is used both when destroying and when creating an SQL function. In this case, the function will not be
207
- created in the default directory `db/functions`, but in the child `db/functions/#{adapter}`. Supported adapters:
208
- `postgresql`and `mysql`, but there will be more in the future.
373
+ created in the default directory `db/functions/public`, but in an adapter directory.
374
+
375
+ Supported adapters: `postgresql`, `mysql`, `trilogy`.
376
+
377
+ ##### `--schema` option
378
+
379
+ PostgreSQL-only option that controls schema directory:
380
+
381
+ - `--adapter=postgresql --schema=audit` => `db/functions/postgresql/audit/<fn>.sql`
382
+
383
+ You can also pass schema-qualified names: `audit.my_fn`.
384
+
385
+ ##### `--force` option
386
+
387
+ Overwrite existing function file if it already exists.
209
388
 
210
389
  ## Limitations
211
390
 
212
- Currently, ARFI has a limitation for `db:setup:db_name` task due to the fact how Rails manage this rake task. More info
213
- here: [limitations][1]. This command will work, but it is not recommended to use it. Note that this limitation applies
214
- only to multi-db setup, default `db:setup` will work as expected.
391
+ - SQLite3 is not supported for "stored functions from SQL files" because SQLite user-defined functions are typically
392
+ registered per connection via the client library, not created via `CREATE FUNCTION` SQL.
393
+ - MySQL does not support `CREATE OR REPLACE FUNCTION` in the same way as PostgreSQL; plan function replacement strategy
394
+ accordingly.
215
395
 
216
396
  ## Development
217
397
 
398
+ ### Run tests locally
399
+
400
+ Start the required databases via Docker Compose:
401
+
402
+ ```shell
403
+ docker compose up -d
404
+ ```
405
+
406
+ Wait for both services to become healthy (check with `docker ps`). Then run the test suite:
407
+
408
+ ```shell
409
+ bundle exec rspec
410
+ ```
411
+
412
+ The test suite automatically detects available databases via environment variables:
413
+
414
+ | Variable | Default value |
415
+ |---------------------|-----------------------------------------------------------|
416
+ | `ARFI_POSTGRES_URL` | `postgresql://postgres:postgres@localhost:5432/arfi_test` |
417
+ | `ARFI_MYSQL_URL` | `mysql2://root:password@127.0.0.1:3306/arfi_test` |
418
+
419
+ Specs tagged with `:pgsql` or `:mysql` only run when the corresponding database is reachable;
420
+ untagged specs run with no external dependencies.
421
+
422
+ To stop the containers when done:
423
+
424
+ ```shell
425
+ docker compose down
426
+ ```
427
+
218
428
  ### Build from source
219
429
 
220
- The manual installation includes installation via command line interface. it is practically no different from what
430
+ The manual installation includes installation via command line interface. It is practically no different from what
221
431
  happens during the automatic build of the project:
222
432
 
223
433
  ```shell
@@ -225,7 +435,7 @@ git clone https://github.com/unurgunite/arfi.git
225
435
  cd arfi
226
436
  bundle install
227
437
  gem build arfi.gemspec
228
- gem install arfi-0.5.0.gem
438
+ gem install arfi-1.0.0.gem
229
439
  ```
230
440
 
231
441
  Also, you can run `bin/setup` to automatically install everything needed.
@@ -236,14 +446,14 @@ ARFI is built on top of the following gems:
236
446
 
237
447
  | Dependencies | Description |
238
448
  |--------------|--------------------------------------------------------------------------------------------|
239
- | ActiveRecord | Used to patch `ActiveRecord::Base` module with new methods. |
449
+ | ActiveRecord | Used to patch `ActiveRecord::Base` module with new methods |
240
450
  | Rails | Used for fetching project settings (database connection settings, Rails environment, etc.) |
241
- | Thor | For CLI development. |
242
- | Rubocop | For static code analysis. |
243
- | Rake | For patching built-in Rails Rake tasks. |
244
- | Steep | For static type checking. |
245
- | RBS | For static type checking. |
246
- | YARD | For generating documentation. |
451
+ | Thor | For CLI development |
452
+ | Rubocop | For static code analysis |
453
+ | Rake | For patching built-in Rails Rake tasks |
454
+ | Steep | For static type checking |
455
+ | RBS | For static type checking |
456
+ | YARD | For generating documentation |
247
457
 
248
458
  ## Contributing
249
459
 
@@ -253,7 +463,8 @@ the [code of conduct](https://github.com/unurgunite/arfi/blob/master/CODE_OF_CON
253
463
 
254
464
  ## Miscellaneous
255
465
 
256
- ARFI is highly inspired by https://github.com/teoljungberg/fx project.
466
+ ARFI differs by focusing on keeping functions in sync from the current repository state (and optional runtime recovery
467
+ on PostgreSQL).
257
468
 
258
469
  ## License
259
470
 
@@ -263,5 +474,3 @@ The gem is available as open source under the terms of the [MIT License](https:/
263
474
 
264
475
  Everyone interacting in the ARFI project's codebases, issue trackers, chat rooms and mailing lists is expected to follow
265
476
  the [code of conduct](https://github.com/unurgunite/arfi/blob/master/CODE_OF_CONDUCT.md).
266
-
267
- [1]: https://blog.saeloun.com/2021/10/27/rails-7-adds-database-specific-setup/#limitation
data/SECURITY.md ADDED
@@ -0,0 +1,17 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ If you discover a security vulnerability, please do **not** open a public issue.
6
+
7
+ Instead, send a private report to the repository maintainers via GitHub's
8
+ [private vulnerability reporting](https://github.com/unurgunite/arfi/security/advisories/new).
9
+
10
+ You should receive a response within 48 hours. If you don't, please follow up
11
+ to ensure the message was received.
12
+
13
+ ## Supported Versions
14
+
15
+ | Version | Supported |
16
+ |---------|-----------|
17
+ | 1.x | ✅ |
data/Steepfile CHANGED
@@ -1,35 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # D = Steep::Diagnostic
4
- #
5
3
  target :lib do
6
4
  signature 'sig'
7
- # ignore_signature "sig/test"
8
- # use "rbs_collection"
9
- check 'lib' # Directory name
10
- # check "path/to/source.rb" # File name
11
- # check "app/models/**/*.rb" # Glob
12
- # ignore "lib/templates/*.rb"
5
+ check 'lib'
13
6
  ignore 'lib/arfi/extensions/active_record/connection_adapters/postgresql/database_statements.rb'
14
-
15
- # library "pathname" # Standard libraries
16
- # library "strong_json" # Gems
17
-
18
- # configure_code_diagnostics(D::Ruby.default) # `default` diagnostics setting (applies by default)
19
- # configure_code_diagnostics(D::Ruby.strict) # `strict` diagnostics setting
20
- # configure_code_diagnostics(D::Ruby.lenient) # `lenient` diagnostics setting
21
- # configure_code_diagnostics(D::Ruby.silent) # `silent` diagnostics setting
22
- # configure_code_diagnostics do |hash| # You can setup everything yourself
23
- # hash[D::Ruby::NoMethod] = :information
24
- # end
25
7
  end
26
-
27
- # target :test do
28
- # unreferenced! # Skip type checking the `lib` code when types in `test` target is changed
29
- # signature "sig/test" # Put RBS files for tests under `sig/test`
30
- # check "test" # Type check Ruby scripts under `test`
31
- #
32
- # configure_code_diagnostics(D::Ruby.lenient) # Weak type checking for test code
33
- #
34
- # # library "pathname" # Standard libraries
35
- # end
data/compose.yml ADDED
@@ -0,0 +1,33 @@
1
+ services:
2
+ postgres:
3
+ image: postgres:16
4
+ container_name: arfi-postgres
5
+ environment:
6
+ POSTGRES_USER: postgres
7
+ POSTGRES_PASSWORD: postgres
8
+ POSTGRES_DB: arfi_test
9
+ ports:
10
+ - "5432:5432"
11
+ healthcheck:
12
+ test: [ "CMD-SHELL", "pg_isready -U postgres -d arfi_test" ]
13
+ interval: 2s
14
+ timeout: 3s
15
+ retries: 30
16
+ volumes:
17
+ - arfi_pg_data:/var/lib/postgresql/data
18
+ mysql:
19
+ image: mysql:8.0
20
+ ports:
21
+ - "3306:3306"
22
+ environment:
23
+ MYSQL_ROOT_PASSWORD: password
24
+ MYSQL_DATABASE: arfi_test
25
+ healthcheck:
26
+ test: [ "CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -ppassword --silent" ]
27
+ interval: 5s
28
+ timeout: 3s
29
+ retries: 30
30
+ start_period: 10s
31
+
32
+ volumes:
33
+ arfi_pg_data: