que-schema 0.1.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 +7 -0
- data/.github/workflows/release.yml +15 -0
- data/.github/workflows/test.yml +39 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +238 -0
- data/README.md +69 -0
- data/Rakefile +15 -0
- data/checksums/que-schema-0.1.0.gem.sha512 +1 -0
- data/lib/que-schema.rb +7 -0
- data/lib/que_schema/migration_helpers.rb +23 -0
- data/lib/que_schema/railtie.rb +14 -0
- data/lib/que_schema/schema_dumper.rb +47 -0
- data/lib/que_schema/schema_statements.rb +28 -0
- data/lib/que_schema/version.rb +5 -0
- data/spec/integration_spec.rb +77 -0
- data/spec/migration_helpers_spec.rb +54 -0
- data/spec/que_schema_spec.rb +38 -0
- data/spec/railtie_spec.rb +23 -0
- data/spec/schema_dumper_spec.rb +212 -0
- data/spec/schema_load_spec.rb +46 -0
- data/spec/schema_statements_spec.rb +85 -0
- data/spec/spec_helper.rb +40 -0
- data/spec/support/database.rb +54 -0
- metadata +108 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7527f5ce44a1b320c5d7614f8967b608ded6a21116ef4821855e49ca22fb1730
|
|
4
|
+
data.tar.gz: ac0e1cd4f0d2384cf5e7344724ad5075d261ce1383a6d63140c8d60071067308
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a7c65e8a37a8d36feadac9be7fd2df507898b2711edc379ecbe2a317175569604d18b47e49fdf4e23f82e5f362161ed49f5e12e835321cd70ed38818743ec6a4
|
|
7
|
+
data.tar.gz: af2ffccca2ea04ae3d5ed731085100cedac5c9c709032c7c0a34e6fad606ef0585fad059877add945aa7315da6c04c757dd1fcedf41f2f94a5b60b29aadf9a65
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
dry_run:
|
|
7
|
+
type: boolean
|
|
8
|
+
default: false
|
|
9
|
+
description: "Test without publishing to RubyGems"
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release:
|
|
13
|
+
uses: SOFware/reissue/.github/workflows/shared-ruby-gem-release.yml@main
|
|
14
|
+
with:
|
|
15
|
+
dry_run: ${{ inputs.dry_run }}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, master]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
services:
|
|
14
|
+
postgres:
|
|
15
|
+
image: postgres:16
|
|
16
|
+
env:
|
|
17
|
+
POSTGRES_USER: postgres
|
|
18
|
+
POSTGRES_PASSWORD: postgres
|
|
19
|
+
ports: ["5432:5432"]
|
|
20
|
+
options: >-
|
|
21
|
+
--health-cmd pg_isready
|
|
22
|
+
--health-interval 10s
|
|
23
|
+
--health-timeout 5s
|
|
24
|
+
--health-retries 5
|
|
25
|
+
|
|
26
|
+
env:
|
|
27
|
+
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/que_schema_test
|
|
28
|
+
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v4
|
|
31
|
+
|
|
32
|
+
- name: Set up Ruby
|
|
33
|
+
uses: ruby/setup-ruby@v1
|
|
34
|
+
with:
|
|
35
|
+
ruby-version: "4.0"
|
|
36
|
+
bundler-cache: true
|
|
37
|
+
|
|
38
|
+
- name: Run tests
|
|
39
|
+
run: bundle exec rake
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
que-schema (0.1.0)
|
|
5
|
+
activerecord (>= 6.0)
|
|
6
|
+
que
|
|
7
|
+
railties (>= 6.0)
|
|
8
|
+
|
|
9
|
+
GEM
|
|
10
|
+
remote: https://rubygems.org/
|
|
11
|
+
specs:
|
|
12
|
+
actionpack (8.1.2)
|
|
13
|
+
actionview (= 8.1.2)
|
|
14
|
+
activesupport (= 8.1.2)
|
|
15
|
+
nokogiri (>= 1.8.5)
|
|
16
|
+
rack (>= 2.2.4)
|
|
17
|
+
rack-session (>= 1.0.1)
|
|
18
|
+
rack-test (>= 0.6.3)
|
|
19
|
+
rails-dom-testing (~> 2.2)
|
|
20
|
+
rails-html-sanitizer (~> 1.6)
|
|
21
|
+
useragent (~> 0.16)
|
|
22
|
+
actionview (8.1.2)
|
|
23
|
+
activesupport (= 8.1.2)
|
|
24
|
+
builder (~> 3.1)
|
|
25
|
+
erubi (~> 1.11)
|
|
26
|
+
rails-dom-testing (~> 2.2)
|
|
27
|
+
rails-html-sanitizer (~> 1.6)
|
|
28
|
+
activemodel (8.1.2)
|
|
29
|
+
activesupport (= 8.1.2)
|
|
30
|
+
activerecord (8.1.2)
|
|
31
|
+
activemodel (= 8.1.2)
|
|
32
|
+
activesupport (= 8.1.2)
|
|
33
|
+
timeout (>= 0.4.0)
|
|
34
|
+
activesupport (8.1.2)
|
|
35
|
+
base64
|
|
36
|
+
bigdecimal
|
|
37
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
38
|
+
connection_pool (>= 2.2.5)
|
|
39
|
+
drb
|
|
40
|
+
i18n (>= 1.6, < 2)
|
|
41
|
+
json
|
|
42
|
+
logger (>= 1.4.2)
|
|
43
|
+
minitest (>= 5.1)
|
|
44
|
+
securerandom (>= 0.3)
|
|
45
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
|
46
|
+
uri (>= 0.13.1)
|
|
47
|
+
ast (2.4.3)
|
|
48
|
+
base64 (0.3.0)
|
|
49
|
+
bigdecimal (4.0.1)
|
|
50
|
+
builder (3.3.0)
|
|
51
|
+
concurrent-ruby (1.3.6)
|
|
52
|
+
connection_pool (3.0.2)
|
|
53
|
+
crass (1.0.6)
|
|
54
|
+
date (3.5.1)
|
|
55
|
+
diff-lcs (1.6.2)
|
|
56
|
+
docile (1.4.1)
|
|
57
|
+
drb (2.2.3)
|
|
58
|
+
erb (6.0.2)
|
|
59
|
+
erubi (1.13.1)
|
|
60
|
+
i18n (1.14.8)
|
|
61
|
+
concurrent-ruby (~> 1.0)
|
|
62
|
+
io-console (0.8.2)
|
|
63
|
+
irb (1.17.0)
|
|
64
|
+
pp (>= 0.6.0)
|
|
65
|
+
prism (>= 1.3.0)
|
|
66
|
+
rdoc (>= 4.0.0)
|
|
67
|
+
reline (>= 0.4.2)
|
|
68
|
+
json (2.18.1)
|
|
69
|
+
language_server-protocol (3.17.0.5)
|
|
70
|
+
lint_roller (1.1.0)
|
|
71
|
+
logger (1.7.0)
|
|
72
|
+
loofah (2.25.0)
|
|
73
|
+
crass (~> 1.0.2)
|
|
74
|
+
nokogiri (>= 1.12.0)
|
|
75
|
+
minitest (6.0.2)
|
|
76
|
+
drb (~> 2.0)
|
|
77
|
+
prism (~> 1.5)
|
|
78
|
+
nokogiri (1.19.1-aarch64-linux-gnu)
|
|
79
|
+
racc (~> 1.4)
|
|
80
|
+
nokogiri (1.19.1-aarch64-linux-musl)
|
|
81
|
+
racc (~> 1.4)
|
|
82
|
+
nokogiri (1.19.1-arm-linux-gnu)
|
|
83
|
+
racc (~> 1.4)
|
|
84
|
+
nokogiri (1.19.1-arm-linux-musl)
|
|
85
|
+
racc (~> 1.4)
|
|
86
|
+
nokogiri (1.19.1-arm64-darwin)
|
|
87
|
+
racc (~> 1.4)
|
|
88
|
+
nokogiri (1.19.1-x86_64-darwin)
|
|
89
|
+
racc (~> 1.4)
|
|
90
|
+
nokogiri (1.19.1-x86_64-linux-gnu)
|
|
91
|
+
racc (~> 1.4)
|
|
92
|
+
nokogiri (1.19.1-x86_64-linux-musl)
|
|
93
|
+
racc (~> 1.4)
|
|
94
|
+
parallel (1.27.0)
|
|
95
|
+
parser (3.3.10.2)
|
|
96
|
+
ast (~> 2.4.1)
|
|
97
|
+
racc
|
|
98
|
+
pg (1.6.3)
|
|
99
|
+
pg (1.6.3-aarch64-linux)
|
|
100
|
+
pg (1.6.3-aarch64-linux-musl)
|
|
101
|
+
pg (1.6.3-arm64-darwin)
|
|
102
|
+
pg (1.6.3-x86_64-darwin)
|
|
103
|
+
pg (1.6.3-x86_64-linux)
|
|
104
|
+
pg (1.6.3-x86_64-linux-musl)
|
|
105
|
+
pp (0.6.3)
|
|
106
|
+
prettyprint
|
|
107
|
+
prettyprint (0.2.0)
|
|
108
|
+
prism (1.9.0)
|
|
109
|
+
psych (5.3.1)
|
|
110
|
+
date
|
|
111
|
+
stringio
|
|
112
|
+
que (2.4.1)
|
|
113
|
+
racc (1.8.1)
|
|
114
|
+
rack (3.2.5)
|
|
115
|
+
rack-session (2.1.1)
|
|
116
|
+
base64 (>= 0.1.0)
|
|
117
|
+
rack (>= 3.0.0)
|
|
118
|
+
rack-test (2.2.0)
|
|
119
|
+
rack (>= 1.3)
|
|
120
|
+
rackup (2.3.1)
|
|
121
|
+
rack (>= 3)
|
|
122
|
+
rails-dom-testing (2.3.0)
|
|
123
|
+
activesupport (>= 5.0.0)
|
|
124
|
+
minitest
|
|
125
|
+
nokogiri (>= 1.6)
|
|
126
|
+
rails-html-sanitizer (1.7.0)
|
|
127
|
+
loofah (~> 2.25)
|
|
128
|
+
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
|
129
|
+
railties (8.1.2)
|
|
130
|
+
actionpack (= 8.1.2)
|
|
131
|
+
activesupport (= 8.1.2)
|
|
132
|
+
irb (~> 1.13)
|
|
133
|
+
rackup (>= 1.0.0)
|
|
134
|
+
rake (>= 12.2)
|
|
135
|
+
thor (~> 1.0, >= 1.2.2)
|
|
136
|
+
tsort (>= 0.2)
|
|
137
|
+
zeitwerk (~> 2.6)
|
|
138
|
+
rainbow (3.1.1)
|
|
139
|
+
rake (13.3.1)
|
|
140
|
+
rdoc (7.2.0)
|
|
141
|
+
erb
|
|
142
|
+
psych (>= 4.0.0)
|
|
143
|
+
tsort
|
|
144
|
+
regexp_parser (2.11.3)
|
|
145
|
+
reissue (0.4.18)
|
|
146
|
+
rake
|
|
147
|
+
reline (0.6.3)
|
|
148
|
+
io-console (~> 0.5)
|
|
149
|
+
rspec (3.13.2)
|
|
150
|
+
rspec-core (~> 3.13.0)
|
|
151
|
+
rspec-expectations (~> 3.13.0)
|
|
152
|
+
rspec-mocks (~> 3.13.0)
|
|
153
|
+
rspec-core (3.13.6)
|
|
154
|
+
rspec-support (~> 3.13.0)
|
|
155
|
+
rspec-expectations (3.13.5)
|
|
156
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
157
|
+
rspec-support (~> 3.13.0)
|
|
158
|
+
rspec-mocks (3.13.8)
|
|
159
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
160
|
+
rspec-support (~> 3.13.0)
|
|
161
|
+
rspec-support (3.13.7)
|
|
162
|
+
rubocop (1.84.2)
|
|
163
|
+
json (~> 2.3)
|
|
164
|
+
language_server-protocol (~> 3.17.0.2)
|
|
165
|
+
lint_roller (~> 1.1.0)
|
|
166
|
+
parallel (~> 1.10)
|
|
167
|
+
parser (>= 3.3.0.2)
|
|
168
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
169
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
170
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
171
|
+
ruby-progressbar (~> 1.7)
|
|
172
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
173
|
+
rubocop-ast (1.49.0)
|
|
174
|
+
parser (>= 3.3.7.2)
|
|
175
|
+
prism (~> 1.7)
|
|
176
|
+
rubocop-performance (1.26.1)
|
|
177
|
+
lint_roller (~> 1.1)
|
|
178
|
+
rubocop (>= 1.75.0, < 2.0)
|
|
179
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
180
|
+
ruby-progressbar (1.13.0)
|
|
181
|
+
securerandom (0.4.1)
|
|
182
|
+
simplecov (0.22.0)
|
|
183
|
+
docile (~> 1.1)
|
|
184
|
+
simplecov-html (~> 0.11)
|
|
185
|
+
simplecov_json_formatter (~> 0.1)
|
|
186
|
+
simplecov-html (0.13.2)
|
|
187
|
+
simplecov_json_formatter (0.1.4)
|
|
188
|
+
standard (1.54.0)
|
|
189
|
+
language_server-protocol (~> 3.17.0.2)
|
|
190
|
+
lint_roller (~> 1.0)
|
|
191
|
+
rubocop (~> 1.84.0)
|
|
192
|
+
standard-custom (~> 1.0.0)
|
|
193
|
+
standard-performance (~> 1.8)
|
|
194
|
+
standard-custom (1.0.2)
|
|
195
|
+
lint_roller (~> 1.0)
|
|
196
|
+
rubocop (~> 1.50)
|
|
197
|
+
standard-performance (1.9.0)
|
|
198
|
+
lint_roller (~> 1.1)
|
|
199
|
+
rubocop-performance (~> 1.26.0)
|
|
200
|
+
standardrb (1.0.1)
|
|
201
|
+
standard
|
|
202
|
+
stringio (3.2.0)
|
|
203
|
+
thor (1.5.0)
|
|
204
|
+
timeout (0.6.0)
|
|
205
|
+
tsort (0.2.0)
|
|
206
|
+
tzinfo (2.0.6)
|
|
207
|
+
concurrent-ruby (~> 1.0)
|
|
208
|
+
unicode-display_width (3.2.0)
|
|
209
|
+
unicode-emoji (~> 4.1)
|
|
210
|
+
unicode-emoji (4.2.0)
|
|
211
|
+
uri (1.1.1)
|
|
212
|
+
useragent (0.16.11)
|
|
213
|
+
zeitwerk (2.7.5)
|
|
214
|
+
|
|
215
|
+
PLATFORMS
|
|
216
|
+
aarch64-linux
|
|
217
|
+
aarch64-linux-gnu
|
|
218
|
+
aarch64-linux-musl
|
|
219
|
+
arm-linux-gnu
|
|
220
|
+
arm-linux-musl
|
|
221
|
+
arm64-darwin
|
|
222
|
+
x86_64-darwin
|
|
223
|
+
x86_64-linux
|
|
224
|
+
x86_64-linux-gnu
|
|
225
|
+
x86_64-linux-musl
|
|
226
|
+
|
|
227
|
+
DEPENDENCIES
|
|
228
|
+
pg
|
|
229
|
+
que-schema!
|
|
230
|
+
rake (~> 13.0)
|
|
231
|
+
reissue
|
|
232
|
+
rspec (~> 3.0)
|
|
233
|
+
simplecov
|
|
234
|
+
simplecov_json_formatter
|
|
235
|
+
standardrb
|
|
236
|
+
|
|
237
|
+
BUNDLED WITH
|
|
238
|
+
2.7.2
|
data/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# que-schema
|
|
2
|
+
|
|
3
|
+
Enables **schema.rb** compatibility for the [que](https://github.com/que-rb/que) job queue gem. With this gem, you can use Rails' default `:ruby` schema format instead of being forced to `structure.sql`.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Que's migrations create PostgreSQL-specific objects (PL/pgSQL functions, triggers, UNLOGGED tables, storage options) that ActiveRecord's schema dumper doesn't represent in `schema.rb` by default. Apps using Que have typically had to set `config.active_record.schema_format = :sql` and maintain `structure.sql`. This gem patches the schema dump/load pipeline so all of Que's constructs round-trip through `schema.rb` with **zero changes** to your existing Que migrations.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Add to your Gemfile:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem "que-schema"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
No configuration required. Keep using `schema.rb` (the default). If you were on `structure.sql` only for Que, you can switch back to `:ruby` and run `db:schema:dump`; the dump will include Que's schema via custom DSL calls.
|
|
18
|
+
|
|
19
|
+
## How it works
|
|
20
|
+
|
|
21
|
+
- **Dump:** The gem prepends into `ActiveRecord::SchemaDumper`. When it sees the `que_jobs` table (and reads the Que schema version from the table comment), it emits `que_define_schema(version: N)` after table definitions (tables must exist first because Que's functions reference the `public.que_jobs` type). Any `que_*` table detected as UNLOGGED via `pg_class` is written as `que_create_unlogged_table` instead of `create_table`. GIN indexes on `que_*` tables are detected dynamically and suppressed from the normal table dumps (they are recreated by `que_define_schema`). Fillfactor settings on `que_*` tables are also suppressed.
|
|
22
|
+
- **Load:** `db:schema:load` runs your `schema.rb`. The gem adds `que_define_schema(version:)` and `que_create_unlogged_table` to the schema context. `que_define_schema` runs the stored SQL for that version (functions, triggers, indexes, fillfactor, table comment). `que_create_unlogged_table` is implemented as `create_table` + `ALTER TABLE ... SET UNLOGGED`.
|
|
23
|
+
|
|
24
|
+
No monkey-patching of Que; everything is done by extending ActiveRecord.
|
|
25
|
+
|
|
26
|
+
## Supported Que versions
|
|
27
|
+
|
|
28
|
+
- **Que migration version 7** is supported (current).
|
|
29
|
+
|
|
30
|
+
Support for older migration versions (e.g. 5, 6) can be added later by adding SQL assets under `lib/que_schema/sql/v5`, etc.
|
|
31
|
+
|
|
32
|
+
## Requirements
|
|
33
|
+
|
|
34
|
+
- Rails 6.0+
|
|
35
|
+
- PostgreSQL (Que is PostgreSQL-only)
|
|
36
|
+
- Ruby >= 2.7
|
|
37
|
+
|
|
38
|
+
## Limitations
|
|
39
|
+
|
|
40
|
+
- **PostgreSQL only.** The gem does nothing on SQLite/MySQL; it only runs when the adapter is PostgreSQL.
|
|
41
|
+
- **UNLOGGED table:** `que_lockers` is created as UNLOGGED. If you run `db:schema:load` against a non-PostgreSQL database, that call would fail (expected).
|
|
42
|
+
|
|
43
|
+
## Development
|
|
44
|
+
|
|
45
|
+
### Tests
|
|
46
|
+
|
|
47
|
+
Uses RSpec. Requires a running PostgreSQL. The test database (`que_schema_test`) is created automatically if it doesn't exist.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
bundle install
|
|
51
|
+
bundle exec rake
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
You can set `DATABASE_URL` to point to a different PostgreSQL instance:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
export DATABASE_URL=postgresql://user:pass@host:5432/que_schema_test
|
|
58
|
+
bundle exec rake
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Adding support for a new Que migration version
|
|
62
|
+
|
|
63
|
+
1. Add a directory `lib/que_schema/sql/v<N>/` with `functions.sql`, `triggers.sql`, and optionally `down.sql`.
|
|
64
|
+
2. Copy the exact `CREATE FUNCTION` / `CREATE TRIGGER` SQL from [que-rb/que](https://github.com/que-rb/que) for that version; use `CREATE OR REPLACE` and `DROP TRIGGER IF EXISTS` for idempotency.
|
|
65
|
+
3. Extend the schema dumper version detection if the new version uses a different table comment or detection method.
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
MIT.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rspec/core/rake_task"
|
|
5
|
+
require "reissue/gem"
|
|
6
|
+
|
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
8
|
+
|
|
9
|
+
Reissue::Task.create :reissue do |task|
|
|
10
|
+
task.version_file = "lib/que_schema/version.rb"
|
|
11
|
+
task.fragment = :git
|
|
12
|
+
task.push_finalize = :branch
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
task default: :spec
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aae14df55017ee2128437cd923ce6c9cc34f56b8b53cb77a362750c7271ad172492a1df977b92397d669907a105f8650d2ed55aceb4cb0a05343dd13ec73327c
|
data/lib/que-schema.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "que"
|
|
4
|
+
|
|
5
|
+
module QueSchema
|
|
6
|
+
# Convenience methods for migrations when setting up Que via ActiveRecord.
|
|
7
|
+
module MigrationHelpers
|
|
8
|
+
# Creates the Que schema for the given version.
|
|
9
|
+
# Delegates to Que.migrate! which handles everything.
|
|
10
|
+
def create_que_schema(version:)
|
|
11
|
+
que_define_schema(version: version)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Drops the entire Que schema by migrating down to version 0.
|
|
15
|
+
# Removes all Que tables, functions, and triggers.
|
|
16
|
+
def drop_que_schema
|
|
17
|
+
return unless connection.adapter_name.match?(/postgresql/i)
|
|
18
|
+
|
|
19
|
+
Que.connection_proc = proc { |&block| block.call(connection.raw_connection) }
|
|
20
|
+
Que.migrate!(version: 0)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module QueSchema
|
|
4
|
+
class Railtie < ::Rails::Railtie
|
|
5
|
+
config.before_initialize do
|
|
6
|
+
ActiveSupport.on_load(:active_record) do
|
|
7
|
+
ActiveRecord::Migration.include(QueSchema::SchemaStatements)
|
|
8
|
+
ActiveRecord::Migration.include(QueSchema::MigrationHelpers)
|
|
9
|
+
ActiveRecord::Schema.include(QueSchema::SchemaStatements)
|
|
10
|
+
ActiveRecord::SchemaDumper.prepend(QueSchema::SchemaDumper)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module QueSchema
|
|
4
|
+
# Prepended into ActiveRecord::SchemaDumper to emit que_define_schema
|
|
5
|
+
# and suppress Que-managed tables (Que.migrate! recreates them on load).
|
|
6
|
+
module SchemaDumper
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def tables(stream)
|
|
10
|
+
super
|
|
11
|
+
|
|
12
|
+
if postgresql? && (version = que_schema_version) && version > 0
|
|
13
|
+
stream.puts " # Que internal schema — emitted by que-schema gem"
|
|
14
|
+
stream.puts " que_define_schema(version: #{version})"
|
|
15
|
+
stream.puts
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Suppress all que_* tables — Que.migrate! creates them during schema load.
|
|
20
|
+
def table(table_name, stream)
|
|
21
|
+
return if postgresql? && que_table?(table_name)
|
|
22
|
+
|
|
23
|
+
super
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def que_schema_version
|
|
27
|
+
return nil unless @connection.table_exists?("que_jobs")
|
|
28
|
+
|
|
29
|
+
result = @connection.execute(<<~SQL)
|
|
30
|
+
SELECT obj_description(c.oid, 'pg_class') AS comment
|
|
31
|
+
FROM pg_class c
|
|
32
|
+
WHERE c.relname = 'que_jobs'
|
|
33
|
+
AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')
|
|
34
|
+
SQL
|
|
35
|
+
raw = result.first&.[]("comment") || result.first&.[](:comment)
|
|
36
|
+
raw.to_s.strip.to_i
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def postgresql?
|
|
40
|
+
@connection.respond_to?(:adapter_name) && @connection.adapter_name.match?(/postgresql/i)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def que_table?(table_name)
|
|
44
|
+
table_name.start_with?("que_")
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "que"
|
|
4
|
+
|
|
5
|
+
module QueSchema
|
|
6
|
+
# DSL methods for schema.rb: available in ActiveRecord::Schema and ActiveRecord::Migration
|
|
7
|
+
# so that db:schema:load can execute que_define_schema.
|
|
8
|
+
module SchemaStatements
|
|
9
|
+
# Recreates the full Que schema for the given version by delegating to
|
|
10
|
+
# Que.migrate!. This creates tables, functions, triggers, indexes, and
|
|
11
|
+
# all other database objects that Que needs.
|
|
12
|
+
def que_define_schema(version:)
|
|
13
|
+
return unless postgresql?
|
|
14
|
+
|
|
15
|
+
Que.connection_proc = proc { |&block| block.call(connection.raw_connection) }
|
|
16
|
+
Que.migrate!(version: version.to_i)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def postgresql?
|
|
22
|
+
return false unless respond_to?(:connection)
|
|
23
|
+
|
|
24
|
+
conn = connection
|
|
25
|
+
conn.respond_to?(:adapter_name) && conn.adapter_name.match?(/postgresql/i)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "stringio"
|
|
5
|
+
|
|
6
|
+
RSpec.describe "QueSchema integration (round-trip)" do
|
|
7
|
+
let(:conn) { QueSchema::Spec.connection }
|
|
8
|
+
let(:pool) { ActiveRecord::Base.connection_pool }
|
|
9
|
+
|
|
10
|
+
after do
|
|
11
|
+
QueSchema::Spec.clean!
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "full round-trip: create schema -> dump -> drop -> load -> verify" do
|
|
15
|
+
# Create schema via que_define_schema (which delegates to Que.migrate!)
|
|
16
|
+
c = conn
|
|
17
|
+
schema_context = Object.new.extend(QueSchema::SchemaStatements)
|
|
18
|
+
schema_context.define_singleton_method(:connection) { c }
|
|
19
|
+
schema_context.que_define_schema(version: 7)
|
|
20
|
+
|
|
21
|
+
# Dump schema
|
|
22
|
+
stream = StringIO.new
|
|
23
|
+
ActiveRecord::SchemaDumper.dump(pool, stream)
|
|
24
|
+
schema_rb = stream.string
|
|
25
|
+
|
|
26
|
+
expect(schema_rb).to include("que_define_schema(version: 7)")
|
|
27
|
+
expect(schema_rb).not_to match(/create_table "que_jobs"/)
|
|
28
|
+
expect(schema_rb).not_to match(/create_table "que_lockers"/)
|
|
29
|
+
expect(schema_rb).not_to match(/create_table "que_values"/)
|
|
30
|
+
|
|
31
|
+
# Drop everything
|
|
32
|
+
QueSchema::Spec.clean!
|
|
33
|
+
expect(conn.table_exists?(:que_jobs)).to be false
|
|
34
|
+
|
|
35
|
+
# Load schema (eval the dumped schema)
|
|
36
|
+
eval(schema_rb) # rubocop:disable Security/Eval
|
|
37
|
+
|
|
38
|
+
# Verify tables were recreated by Que.migrate!
|
|
39
|
+
expect(conn.table_exists?(:que_jobs)).to be true
|
|
40
|
+
expect(conn.table_exists?(:que_values)).to be true
|
|
41
|
+
expect(conn.table_exists?(:que_lockers)).to be true
|
|
42
|
+
|
|
43
|
+
# Verify functions were recreated
|
|
44
|
+
r = conn.execute(<<~SQL)
|
|
45
|
+
SELECT proname FROM pg_proc
|
|
46
|
+
WHERE proname IN ('que_validate_tags', 'que_job_notify', 'que_determine_job_state', 'que_state_notify')
|
|
47
|
+
SQL
|
|
48
|
+
names = r.map { |row| row["proname"] || row[:proname] }
|
|
49
|
+
expect(names).to contain_exactly("que_validate_tags", "que_job_notify", "que_determine_job_state", "que_state_notify")
|
|
50
|
+
|
|
51
|
+
# Verify triggers were recreated
|
|
52
|
+
r = conn.execute(<<~SQL)
|
|
53
|
+
SELECT tgname FROM pg_trigger
|
|
54
|
+
WHERE tgrelid = 'que_jobs'::regclass
|
|
55
|
+
AND tgname IN ('que_job_notify', 'que_state_notify')
|
|
56
|
+
SQL
|
|
57
|
+
trigger_names = r.map { |row| row["tgname"] || row[:tgname] }
|
|
58
|
+
expect(trigger_names).to contain_exactly("que_job_notify", "que_state_notify")
|
|
59
|
+
|
|
60
|
+
# Verify que_lockers is UNLOGGED
|
|
61
|
+
r = conn.execute("SELECT relpersistence FROM pg_class WHERE relname = 'que_lockers'")
|
|
62
|
+
expect(r.first["relpersistence"] || r.first[:relpersistence]).to eq("u")
|
|
63
|
+
|
|
64
|
+
# Verify table comment (schema version)
|
|
65
|
+
r = conn.execute(<<~SQL)
|
|
66
|
+
SELECT obj_description(c.oid, 'pg_class') AS comment
|
|
67
|
+
FROM pg_class c
|
|
68
|
+
WHERE c.relname = 'que_jobs'
|
|
69
|
+
SQL
|
|
70
|
+
expect(r.first["comment"] || r.first[:comment]).to eq("7")
|
|
71
|
+
|
|
72
|
+
# Verify a job can be inserted
|
|
73
|
+
conn.execute("INSERT INTO que_jobs (queue, priority, job_class, args, data, job_schema_version) VALUES ('default', 100, 'TestJob', '[]', '{}', 7)")
|
|
74
|
+
count = conn.execute("SELECT count(*) AS cnt FROM que_jobs")
|
|
75
|
+
expect((count.first["cnt"] || count.first[:cnt]).to_i).to eq(1)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe QueSchema::MigrationHelpers do
|
|
6
|
+
let(:test_class) do
|
|
7
|
+
Class.new do
|
|
8
|
+
include QueSchema::SchemaStatements
|
|
9
|
+
include QueSchema::MigrationHelpers
|
|
10
|
+
|
|
11
|
+
attr_accessor :connection
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
let(:connection) { QueSchema::Spec.connection }
|
|
16
|
+
let(:instance) { test_class.new.tap { |i| i.connection = connection } }
|
|
17
|
+
|
|
18
|
+
after do
|
|
19
|
+
QueSchema::Spec.clean!
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "#create_que_schema" do
|
|
23
|
+
it "delegates to que_define_schema" do
|
|
24
|
+
expect(instance).to receive(:que_define_schema).with(version: 7)
|
|
25
|
+
instance.create_que_schema(version: 7)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "creates the full Que schema" do
|
|
29
|
+
instance.create_que_schema(version: 7)
|
|
30
|
+
expect(connection.table_exists?(:que_jobs)).to be true
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe "#drop_que_schema" do
|
|
35
|
+
it "removes all Que tables, functions, and triggers" do
|
|
36
|
+
instance.create_que_schema(version: 7)
|
|
37
|
+
expect(connection.table_exists?(:que_jobs)).to be true
|
|
38
|
+
|
|
39
|
+
instance.drop_que_schema
|
|
40
|
+
expect(connection.table_exists?(:que_jobs)).to be false
|
|
41
|
+
expect(connection.table_exists?(:que_values)).to be false
|
|
42
|
+
expect(connection.table_exists?(:que_lockers)).to be false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context "when not PostgreSQL" do
|
|
46
|
+
let(:connection) { double("connection", adapter_name: "SQLite") }
|
|
47
|
+
|
|
48
|
+
it "does nothing" do
|
|
49
|
+
expect(connection).not_to receive(:execute)
|
|
50
|
+
instance.drop_que_schema
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe QueSchema do
|
|
6
|
+
it "has a version number" do
|
|
7
|
+
expect(QueSchema::VERSION).to be_a(String)
|
|
8
|
+
expect(QueSchema::VERSION).not_to be_empty
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "defines SchemaStatements module" do
|
|
12
|
+
expect(QueSchema::SchemaStatements).to be_a(Module)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "defines SchemaDumper module" do
|
|
16
|
+
expect(QueSchema::SchemaDumper).to be_a(Module)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "defines MigrationHelpers module" do
|
|
20
|
+
expect(QueSchema::MigrationHelpers).to be_a(Module)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "includes SchemaStatements in ActiveRecord::Migration" do
|
|
24
|
+
expect(ActiveRecord::Migration.ancestors).to include(QueSchema::SchemaStatements)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "includes MigrationHelpers in ActiveRecord::Migration" do
|
|
28
|
+
expect(ActiveRecord::Migration.ancestors).to include(QueSchema::MigrationHelpers)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "includes SchemaStatements in ActiveRecord::Schema" do
|
|
32
|
+
expect(ActiveRecord::Schema.ancestors).to include(QueSchema::SchemaStatements)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "prepends SchemaDumper into ActiveRecord::SchemaDumper" do
|
|
36
|
+
expect(ActiveRecord::SchemaDumper.ancestors).to include(QueSchema::SchemaDumper)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
if defined?(Rails)
|
|
6
|
+
RSpec.describe QueSchema::Railtie do
|
|
7
|
+
it "is defined as a Rails::Railtie subclass" do
|
|
8
|
+
# We can't fully test Railtie without Rails, but we can verify the class
|
|
9
|
+
# is loadable and structured correctly when Rails is defined.
|
|
10
|
+
expect(QueSchema::Railtie).to be < Rails::Railtie
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# When Rails is not defined, just verify the file doesn't blow up
|
|
16
|
+
RSpec.describe "QueSchema without Rails" do
|
|
17
|
+
it "does not load Railtie when Rails is not defined" do
|
|
18
|
+
# que-schema.rb guards: require "que_schema/railtie" if defined?(Rails)
|
|
19
|
+
# Since we're not in a Rails app, Railtie may or may not be loaded
|
|
20
|
+
# depending on test ordering, but the gem should work without Rails
|
|
21
|
+
expect(defined?(QueSchema)).to eq("constant")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "stringio"
|
|
5
|
+
|
|
6
|
+
RSpec.describe QueSchema::SchemaDumper do
|
|
7
|
+
let(:connection) { double("connection") }
|
|
8
|
+
|
|
9
|
+
let(:dumper_class) do
|
|
10
|
+
mod = described_class
|
|
11
|
+
Class.new do
|
|
12
|
+
prepend mod
|
|
13
|
+
|
|
14
|
+
attr_accessor :connection
|
|
15
|
+
|
|
16
|
+
def initialize(connection)
|
|
17
|
+
@connection = connection
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def tables(stream)
|
|
21
|
+
stream.puts " # original tables output"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def table(table_name, stream)
|
|
25
|
+
stream.puts " create_table \"#{table_name}\", force: :cascade do |t|"
|
|
26
|
+
stream.puts " end"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private :tables, :table
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
let(:dumper) { dumper_class.new(connection) }
|
|
34
|
+
|
|
35
|
+
def stub_postgresql!
|
|
36
|
+
allow(connection).to receive(:adapter_name).and_return("PostgreSQL")
|
|
37
|
+
allow(connection).to receive(:respond_to?).with(:adapter_name).and_return(true)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe "#tables" do
|
|
41
|
+
context "when PostgreSQL with que_jobs" do
|
|
42
|
+
before do
|
|
43
|
+
stub_postgresql!
|
|
44
|
+
allow(connection).to receive(:table_exists?).with("que_jobs").and_return(true)
|
|
45
|
+
allow(connection).to receive(:execute).and_return([{"comment" => "7"}])
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "emits que_define_schema after tables" do
|
|
49
|
+
stream = StringIO.new
|
|
50
|
+
dumper.send(:tables, stream)
|
|
51
|
+
output = stream.string
|
|
52
|
+
|
|
53
|
+
expect(output).to include("que_define_schema(version: 7)")
|
|
54
|
+
expect(output).to include("# original tables output")
|
|
55
|
+
expect(output.index("que_define_schema")).to be > output.index("original tables")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context "when PostgreSQL without que_jobs" do
|
|
60
|
+
before do
|
|
61
|
+
stub_postgresql!
|
|
62
|
+
allow(connection).to receive(:table_exists?).with("que_jobs").and_return(false)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "does not emit que_define_schema" do
|
|
66
|
+
stream = StringIO.new
|
|
67
|
+
dumper.send(:tables, stream)
|
|
68
|
+
expect(stream.string).not_to include("que_define_schema")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
context "when not PostgreSQL" do
|
|
73
|
+
before do
|
|
74
|
+
allow(connection).to receive(:adapter_name).and_return("SQLite")
|
|
75
|
+
allow(connection).to receive(:respond_to?).with(:adapter_name).and_return(true)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "does not emit que_define_schema" do
|
|
79
|
+
stream = StringIO.new
|
|
80
|
+
dumper.send(:tables, stream)
|
|
81
|
+
expect(stream.string).not_to include("que_define_schema")
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
context "when comment is nil" do
|
|
86
|
+
before do
|
|
87
|
+
stub_postgresql!
|
|
88
|
+
allow(connection).to receive(:table_exists?).with("que_jobs").and_return(true)
|
|
89
|
+
allow(connection).to receive(:execute).and_return([{"comment" => nil}])
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it "does not emit que_define_schema when version is 0" do
|
|
93
|
+
stream = StringIO.new
|
|
94
|
+
dumper.send(:tables, stream)
|
|
95
|
+
expect(stream.string).not_to include("que_define_schema")
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe "#table" do
|
|
101
|
+
context "when PostgreSQL" do
|
|
102
|
+
before { stub_postgresql! }
|
|
103
|
+
|
|
104
|
+
it "suppresses que_* tables" do
|
|
105
|
+
stream = StringIO.new
|
|
106
|
+
dumper.send(:table, "que_jobs", stream)
|
|
107
|
+
expect(stream.string).to be_empty
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "suppresses que_lockers" do
|
|
111
|
+
stream = StringIO.new
|
|
112
|
+
dumper.send(:table, "que_lockers", stream)
|
|
113
|
+
expect(stream.string).to be_empty
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "suppresses que_values" do
|
|
117
|
+
stream = StringIO.new
|
|
118
|
+
dumper.send(:table, "que_values", stream)
|
|
119
|
+
expect(stream.string).to be_empty
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it "suppresses any future que_* table" do
|
|
123
|
+
stream = StringIO.new
|
|
124
|
+
dumper.send(:table, "que_something_new", stream)
|
|
125
|
+
expect(stream.string).to be_empty
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "does not suppress non-que tables" do
|
|
129
|
+
stream = StringIO.new
|
|
130
|
+
dumper.send(:table, "users", stream)
|
|
131
|
+
expect(stream.string).to include('create_table "users"')
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
context "when not PostgreSQL" do
|
|
136
|
+
before do
|
|
137
|
+
allow(connection).to receive(:adapter_name).and_return("SQLite")
|
|
138
|
+
allow(connection).to receive(:respond_to?).with(:adapter_name).and_return(true)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it "does not suppress que_* tables" do
|
|
142
|
+
stream = StringIO.new
|
|
143
|
+
dumper.send(:table, "que_jobs", stream)
|
|
144
|
+
expect(stream.string).to include('create_table "que_jobs"')
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
describe "#que_table? (private)" do
|
|
150
|
+
it "returns true for que_ prefixed tables" do
|
|
151
|
+
expect(dumper.send(:que_table?, "que_jobs")).to be true
|
|
152
|
+
expect(dumper.send(:que_table?, "que_lockers")).to be true
|
|
153
|
+
expect(dumper.send(:que_table?, "que_values")).to be true
|
|
154
|
+
expect(dumper.send(:que_table?, "que_new_future_table")).to be true
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it "returns false for non-que tables" do
|
|
158
|
+
expect(dumper.send(:que_table?, "users")).to be false
|
|
159
|
+
expect(dumper.send(:que_table?, "queue_items")).to be false
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
describe "#que_schema_version (private)" do
|
|
164
|
+
it "returns the version from the table comment" do
|
|
165
|
+
allow(connection).to receive(:table_exists?).with("que_jobs").and_return(true)
|
|
166
|
+
allow(connection).to receive(:execute).and_return([{"comment" => "7"}])
|
|
167
|
+
expect(dumper.send(:que_schema_version)).to eq(7)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it "returns nil when que_jobs does not exist" do
|
|
171
|
+
allow(connection).to receive(:table_exists?).with("que_jobs").and_return(false)
|
|
172
|
+
expect(dumper.send(:que_schema_version)).to be_nil
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it "returns 0 when comment is nil" do
|
|
176
|
+
allow(connection).to receive(:table_exists?).with("que_jobs").and_return(true)
|
|
177
|
+
allow(connection).to receive(:execute).and_return([{"comment" => nil}])
|
|
178
|
+
expect(dumper.send(:que_schema_version)).to eq(0)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
it "returns 0 when no rows returned" do
|
|
182
|
+
allow(connection).to receive(:table_exists?).with("que_jobs").and_return(true)
|
|
183
|
+
allow(connection).to receive(:execute).and_return([])
|
|
184
|
+
expect(dumper.send(:que_schema_version)).to eq(0)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
it "handles symbol key for comment" do
|
|
188
|
+
allow(connection).to receive(:table_exists?).with("que_jobs").and_return(true)
|
|
189
|
+
allow(connection).to receive(:execute).and_return([{comment: "7"}])
|
|
190
|
+
expect(dumper.send(:que_schema_version)).to eq(7)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
describe "#postgresql? (private)" do
|
|
195
|
+
it "returns true for PostgreSQL" do
|
|
196
|
+
allow(connection).to receive(:respond_to?).with(:adapter_name).and_return(true)
|
|
197
|
+
allow(connection).to receive(:adapter_name).and_return("PostgreSQL")
|
|
198
|
+
expect(dumper.send(:postgresql?)).to be true
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
it "returns false for non-PostgreSQL" do
|
|
202
|
+
allow(connection).to receive(:respond_to?).with(:adapter_name).and_return(true)
|
|
203
|
+
allow(connection).to receive(:adapter_name).and_return("SQLite")
|
|
204
|
+
expect(dumper.send(:postgresql?)).to be false
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
it "returns false when adapter_name is not available" do
|
|
208
|
+
allow(connection).to receive(:respond_to?).with(:adapter_name).and_return(false)
|
|
209
|
+
expect(dumper.send(:postgresql?)).to be false
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe "QueSchema schema load" do
|
|
6
|
+
let(:conn) { QueSchema::Spec.connection }
|
|
7
|
+
|
|
8
|
+
after do
|
|
9
|
+
QueSchema::Spec.clean!
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "que_define_schema(version: 7) creates the full Que schema" do
|
|
13
|
+
c = conn
|
|
14
|
+
schema_context = Object.new
|
|
15
|
+
schema_context.extend(QueSchema::SchemaStatements)
|
|
16
|
+
schema_context.define_singleton_method(:connection) { c }
|
|
17
|
+
schema_context.que_define_schema(version: 7)
|
|
18
|
+
|
|
19
|
+
# Tables
|
|
20
|
+
expect(conn.table_exists?(:que_jobs)).to be true
|
|
21
|
+
expect(conn.table_exists?(:que_values)).to be true
|
|
22
|
+
expect(conn.table_exists?(:que_lockers)).to be true
|
|
23
|
+
|
|
24
|
+
# Functions
|
|
25
|
+
r = conn.execute(<<~SQL)
|
|
26
|
+
SELECT proname FROM pg_proc WHERE proname IN ('que_validate_tags', 'que_job_notify', 'que_determine_job_state', 'que_state_notify')
|
|
27
|
+
SQL
|
|
28
|
+
names = r.map { |row| row["proname"] || row[:proname] }
|
|
29
|
+
expect(names).to contain_exactly("que_validate_tags", "que_job_notify", "que_determine_job_state", "que_state_notify")
|
|
30
|
+
|
|
31
|
+
# Triggers
|
|
32
|
+
r = conn.execute("SELECT tgname FROM pg_trigger WHERE tgrelid = 'que_jobs'::regclass AND tgname IN ('que_job_notify', 'que_state_notify')")
|
|
33
|
+
names = r.map { |row| row["tgname"] || row[:tgname] }
|
|
34
|
+
expect(names).to contain_exactly("que_job_notify", "que_state_notify")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "que_define_schema creates que_lockers as UNLOGGED" do
|
|
38
|
+
c = conn
|
|
39
|
+
schema_context = Object.new.extend(QueSchema::SchemaStatements)
|
|
40
|
+
schema_context.define_singleton_method(:connection) { c }
|
|
41
|
+
schema_context.que_define_schema(version: 7)
|
|
42
|
+
|
|
43
|
+
r = conn.execute("SELECT relpersistence FROM pg_class WHERE relname = 'que_lockers'")
|
|
44
|
+
expect(r.first["relpersistence"] || r.first[:relpersistence]).to eq("u")
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe QueSchema::SchemaStatements do
|
|
6
|
+
let(:test_class) do
|
|
7
|
+
Class.new do
|
|
8
|
+
include QueSchema::SchemaStatements
|
|
9
|
+
|
|
10
|
+
attr_accessor :connection
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
let(:connection) { QueSchema::Spec.connection }
|
|
15
|
+
let(:instance) { test_class.new.tap { |i| i.connection = connection } }
|
|
16
|
+
|
|
17
|
+
after do
|
|
18
|
+
QueSchema::Spec.clean!
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe "#que_define_schema" do
|
|
22
|
+
context "when not PostgreSQL" do
|
|
23
|
+
let(:connection) { double("connection", adapter_name: "SQLite") }
|
|
24
|
+
|
|
25
|
+
it "returns nil without executing SQL" do
|
|
26
|
+
expect(connection).not_to receive(:execute)
|
|
27
|
+
result = instance.que_define_schema(version: 7)
|
|
28
|
+
expect(result).to be_nil
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context "when PostgreSQL" do
|
|
33
|
+
it "creates all Que tables, functions, and triggers for version 7" do
|
|
34
|
+
instance.que_define_schema(version: 7)
|
|
35
|
+
|
|
36
|
+
expect(connection.table_exists?(:que_jobs)).to be true
|
|
37
|
+
expect(connection.table_exists?(:que_values)).to be true
|
|
38
|
+
expect(connection.table_exists?(:que_lockers)).to be true
|
|
39
|
+
|
|
40
|
+
r = connection.execute("SELECT proname FROM pg_proc WHERE proname IN ('que_validate_tags', 'que_job_notify', 'que_determine_job_state', 'que_state_notify')")
|
|
41
|
+
names = r.map { |row| row["proname"] || row[:proname] }
|
|
42
|
+
expect(names).to contain_exactly("que_validate_tags", "que_job_notify", "que_determine_job_state", "que_state_notify")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "creates que_lockers as UNLOGGED" do
|
|
46
|
+
instance.que_define_schema(version: 7)
|
|
47
|
+
|
|
48
|
+
r = connection.execute("SELECT relpersistence FROM pg_class WHERE relname = 'que_lockers'")
|
|
49
|
+
expect(r.first["relpersistence"] || r.first[:relpersistence]).to eq("u")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "creates triggers on que_jobs" do
|
|
53
|
+
instance.que_define_schema(version: 7)
|
|
54
|
+
|
|
55
|
+
r = connection.execute("SELECT tgname FROM pg_trigger WHERE tgrelid = 'que_jobs'::regclass AND tgname IN ('que_job_notify', 'que_state_notify')")
|
|
56
|
+
names = r.map { |row| row["tgname"] || row[:tgname] }
|
|
57
|
+
expect(names).to contain_exactly("que_job_notify", "que_state_notify")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "is idempotent" do
|
|
61
|
+
instance.que_define_schema(version: 7)
|
|
62
|
+
expect { instance.que_define_schema(version: 7) }.not_to raise_error
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe "#postgresql? (private)" do
|
|
68
|
+
it "returns false when no connection method exists" do
|
|
69
|
+
obj = Object.new
|
|
70
|
+
obj.extend(QueSchema::SchemaStatements)
|
|
71
|
+
expect(obj.send(:postgresql?)).to be false
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "returns false for non-PostgreSQL adapters" do
|
|
75
|
+
fake_conn = double("connection", adapter_name: "Mysql2")
|
|
76
|
+
allow(fake_conn).to receive(:respond_to?).with(:adapter_name).and_return(true)
|
|
77
|
+
inst = test_class.new.tap { |i| i.connection = fake_conn }
|
|
78
|
+
expect(inst.send(:postgresql?)).to be false
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "returns true for PostgreSQL adapter" do
|
|
82
|
+
expect(instance.send(:postgresql?)).to be true
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "simplecov"
|
|
4
|
+
require "simplecov_json_formatter"
|
|
5
|
+
SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new([
|
|
6
|
+
SimpleCov::Formatter::HTMLFormatter,
|
|
7
|
+
SimpleCov::Formatter::JSONFormatter
|
|
8
|
+
])
|
|
9
|
+
SimpleCov.start do
|
|
10
|
+
add_filter %r{spec/}
|
|
11
|
+
add_filter %r{vendor/}
|
|
12
|
+
minimum_coverage 90
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
require "active_record"
|
|
16
|
+
require "que-schema"
|
|
17
|
+
require "support/database"
|
|
18
|
+
|
|
19
|
+
# Apply patches when running without Rails (Railtie only runs when Rails boots)
|
|
20
|
+
ActiveRecord::Migration.include(QueSchema::SchemaStatements)
|
|
21
|
+
ActiveRecord::Migration.include(QueSchema::MigrationHelpers)
|
|
22
|
+
ActiveRecord::Schema.include(QueSchema::SchemaStatements)
|
|
23
|
+
ActiveRecord::SchemaDumper.prepend(QueSchema::SchemaDumper)
|
|
24
|
+
|
|
25
|
+
# Establish database connection for all specs
|
|
26
|
+
QueSchema::Spec.establish_connection
|
|
27
|
+
|
|
28
|
+
# Suppress migration output during tests
|
|
29
|
+
ActiveRecord::Migration.verbose = false
|
|
30
|
+
|
|
31
|
+
RSpec.configure do |config|
|
|
32
|
+
config.expect_with :rspec do |expectations|
|
|
33
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
|
34
|
+
end
|
|
35
|
+
config.mock_with :rspec do |mocks|
|
|
36
|
+
mocks.verify_partial_doubles = true
|
|
37
|
+
end
|
|
38
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
|
39
|
+
config.filter_run_when_matching :focus
|
|
40
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record"
|
|
4
|
+
require "que"
|
|
5
|
+
|
|
6
|
+
module QueSchema
|
|
7
|
+
module Spec
|
|
8
|
+
DATABASE_NAME = "que_schema_test"
|
|
9
|
+
DATABASE_URL = ENV["DATABASE_URL"] || "postgresql://localhost/#{DATABASE_NAME}"
|
|
10
|
+
|
|
11
|
+
def self.establish_connection
|
|
12
|
+
return if @connected
|
|
13
|
+
|
|
14
|
+
begin
|
|
15
|
+
ActiveRecord::Base.establish_connection(DATABASE_URL)
|
|
16
|
+
ActiveRecord::Base.connection.execute("SELECT 1")
|
|
17
|
+
rescue ActiveRecord::NoDatabaseError
|
|
18
|
+
create_database
|
|
19
|
+
ActiveRecord::Base.establish_connection(DATABASE_URL)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
@connected = true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.create_database
|
|
26
|
+
base_url = DATABASE_URL.sub(%r{/[^/]+\z}, "/postgres")
|
|
27
|
+
ActiveRecord::Base.establish_connection(base_url)
|
|
28
|
+
ActiveRecord::Base.connection.create_database(DATABASE_NAME)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.connection
|
|
32
|
+
establish_connection
|
|
33
|
+
ActiveRecord::Base.connection
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.clean!
|
|
37
|
+
conn = connection
|
|
38
|
+
|
|
39
|
+
# Delegate to Que.migrate! which drops everything in the correct order,
|
|
40
|
+
# respecting dependencies between constraints, functions, and tables.
|
|
41
|
+
Que.connection_proc = proc { |&block| block.call(conn.raw_connection) }
|
|
42
|
+
Que.migrate!(version: 0)
|
|
43
|
+
rescue
|
|
44
|
+
# If Que.migrate! fails (e.g. tables don't exist), dynamically discover
|
|
45
|
+
# and drop all que_* objects so we don't hardcode Que's internal names.
|
|
46
|
+
conn.execute(<<~SQL).each { |row| conn.execute("DROP TABLE IF EXISTS #{conn.quote_table_name(row["tablename"])} CASCADE") }
|
|
47
|
+
SELECT tablename FROM pg_tables WHERE schemaname = 'public' AND tablename LIKE 'que_%'
|
|
48
|
+
SQL
|
|
49
|
+
conn.execute(<<~SQL).each { |row| conn.execute("DROP FUNCTION IF EXISTS #{row["oid"]}::regprocedure CASCADE") }
|
|
50
|
+
SELECT oid FROM pg_proc WHERE proname LIKE 'que_%' AND pronamespace = 'public'::regnamespace
|
|
51
|
+
SQL
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: que-schema
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jim Gay
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activerecord
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '6.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '6.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: que
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: railties
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '6.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '6.0'
|
|
54
|
+
description: Patches Rails' ActiveRecord schema dumper so applications using que-rb/que
|
|
55
|
+
can use schema.rb instead of structure.sql. Round-trips Que's PostgreSQL functions,
|
|
56
|
+
triggers, and options through the Ruby schema format.
|
|
57
|
+
email:
|
|
58
|
+
- jim@saturnflyer.com
|
|
59
|
+
executables: []
|
|
60
|
+
extensions: []
|
|
61
|
+
extra_rdoc_files: []
|
|
62
|
+
files:
|
|
63
|
+
- ".github/workflows/release.yml"
|
|
64
|
+
- ".github/workflows/test.yml"
|
|
65
|
+
- CHANGELOG.md
|
|
66
|
+
- Gemfile
|
|
67
|
+
- Gemfile.lock
|
|
68
|
+
- README.md
|
|
69
|
+
- Rakefile
|
|
70
|
+
- checksums/que-schema-0.1.0.gem.sha512
|
|
71
|
+
- lib/que-schema.rb
|
|
72
|
+
- lib/que_schema/migration_helpers.rb
|
|
73
|
+
- lib/que_schema/railtie.rb
|
|
74
|
+
- lib/que_schema/schema_dumper.rb
|
|
75
|
+
- lib/que_schema/schema_statements.rb
|
|
76
|
+
- lib/que_schema/version.rb
|
|
77
|
+
- spec/integration_spec.rb
|
|
78
|
+
- spec/migration_helpers_spec.rb
|
|
79
|
+
- spec/que_schema_spec.rb
|
|
80
|
+
- spec/railtie_spec.rb
|
|
81
|
+
- spec/schema_dumper_spec.rb
|
|
82
|
+
- spec/schema_load_spec.rb
|
|
83
|
+
- spec/schema_statements_spec.rb
|
|
84
|
+
- spec/spec_helper.rb
|
|
85
|
+
- spec/support/database.rb
|
|
86
|
+
homepage: https://github.com/SOFware/que-schema
|
|
87
|
+
licenses: []
|
|
88
|
+
metadata:
|
|
89
|
+
homepage_uri: https://github.com/SOFware/que-schema
|
|
90
|
+
source_code_uri: https://github.com/SOFware/que-schema
|
|
91
|
+
rdoc_options: []
|
|
92
|
+
require_paths:
|
|
93
|
+
- lib
|
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
95
|
+
requirements:
|
|
96
|
+
- - ">="
|
|
97
|
+
- !ruby/object:Gem::Version
|
|
98
|
+
version: '2.7'
|
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
requirements: []
|
|
105
|
+
rubygems_version: 4.0.5
|
|
106
|
+
specification_version: 4
|
|
107
|
+
summary: Enables schema.rb compatibility for the que job queue gem
|
|
108
|
+
test_files: []
|