arfi 0.5.1 → 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.
- checksums.yaml +4 -4
- data/.env.sample +2 -0
- data/.rspec +1 -0
- data/.rubocop.yml +8 -1
- data/.rubocop_todo.yml +12 -0
- data/CHANGELOG.md +94 -0
- data/CODE_OF_CONDUCT.md +5 -1
- data/CONTRIBUTING.md +26 -0
- data/README.md +327 -118
- data/SECURITY.md +17 -0
- data/Steepfile +1 -29
- data/compose.yml +33 -0
- data/docscribe.yml +92 -0
- data/gemfiles/rails_6_0.gemfile +11 -0
- data/gemfiles/rails_6_1.gemfile +11 -0
- data/gemfiles/rails_7_0.gemfile +9 -0
- data/gemfiles/rails_7_1.gemfile +10 -0
- data/gemfiles/rails_7_2.gemfile +10 -0
- data/gemfiles/rails_8_0.gemfile +10 -0
- data/gemfiles/rails_8_1.gemfile +10 -0
- data/lib/arfi/cli.rb +24 -9
- data/lib/arfi/commands/f_idx.rb +5 -230
- data/lib/arfi/commands/functions.rb +128 -0
- data/lib/arfi/commands/functions_candidates.rb +133 -0
- data/lib/arfi/commands/functions_creation.rb +157 -0
- data/lib/arfi/commands/functions_helpers.rb +181 -0
- data/lib/arfi/commands/functions_paths.rb +131 -0
- data/lib/arfi/commands/functions_rendering.rb +137 -0
- data/lib/arfi/commands/init.rb +88 -0
- data/lib/arfi/commands/project.rb +5 -51
- data/lib/arfi/errors.rb +15 -3
- data/lib/arfi/extensions/active_record/base.rb +33 -23
- data/lib/arfi/extensions/active_record/connection_adapters/postgresql/database_statements.rb +159 -24
- data/lib/arfi/sql_function_loader.rb +289 -90
- data/lib/arfi/tasks/db.rake +60 -18
- data/lib/arfi/version.rb +1 -1
- data/lib/arfi.rb +2 -0
- data/rbs_collection.lock.yaml +93 -61
- data/sig/compat/active_record_base_compat.rbs +17 -0
- data/sig/compat/thor_dsl.rbs +8 -0
- data/sig/lib/arfi/commands/f_idx.rbs +5 -103
- data/sig/lib/arfi/commands/functions.rbs +99 -0
- data/sig/lib/arfi/commands/functions_candidates.rbs +25 -0
- data/sig/lib/arfi/commands/functions_creation.rbs +29 -0
- data/sig/lib/arfi/commands/functions_helpers.rbs +41 -0
- data/sig/lib/arfi/commands/functions_paths.rbs +25 -0
- data/sig/lib/arfi/commands/functions_rendering.rbs +25 -0
- data/sig/lib/arfi/commands/init.rbs +22 -0
- data/sig/lib/arfi/commands/project.rbs +5 -24
- data/sig/lib/arfi/extensions/active_record/base.rbs +5 -10
- data/sig/lib/arfi/extensions/active_record/connection_adapters/postgresql/database_statements.rbs +28 -0
- data/sig/lib/arfi/sql_function_loader.rbs +45 -87
- data/sig/lib/arfi/tasks/db.rbs +3 -0
- data/sig/lib/arfi/version.rbs +1 -1
- metadata +125 -14
data/README.md
CHANGED
|
@@ -7,45 +7,52 @@
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
24
|
+
Demo project: https://github.com/unurgunite/poc_arfi_72
|
|
20
25
|
|
|
21
|
-
|
|
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
|
|
35
|
-
* [
|
|
36
|
-
* [
|
|
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
|
-
* [
|
|
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
|
|
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
|
-
|
|
77
|
-
|
|
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
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
| db:
|
|
109
|
-
| db:
|
|
110
|
-
| db:prepare
|
|
111
|
-
| db:schema:load
|
|
112
|
-
| db:
|
|
113
|
-
| db:
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
|
121
|
-
|
|
|
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 |
|
|
128
|
-
| 7 |
|
|
129
|
-
| 6 |
|
|
268
|
+
| Rails version | Tested |
|
|
269
|
+
|---------------|--------|
|
|
270
|
+
| 8 | ✅ |
|
|
271
|
+
| 7 | ✅ |
|
|
272
|
+
| 6 | ✅ |
|
|
130
273
|
|
|
131
|
-
##
|
|
274
|
+
## TODO
|
|
132
275
|
|
|
133
|
-
1.
|
|
134
|
-
2.
|
|
135
|
-
3. Add
|
|
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
|
-
|
|
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
|
|
152
|
-
|
|
153
|
-
| `--template` |
|
|
154
|
-
| `--adapter` |
|
|
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
|
-
|
|
159
|
-
`bundle exec arfi f_idx destroy function_name [revision (1 by default)]`.
|
|
310
|
+
To destroy a function file, run:
|
|
160
311
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
|
208
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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:
|