riverqueue 0.11.0 → 0.12.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +132 -0
  3. data/LICENSE +374 -0
  4. data/docs/README.md +765 -0
  5. data/docs/migrations.md +121 -0
  6. data/docs/testing.md +173 -0
  7. data/docs/workers.md +126 -0
  8. data/exe/river +7 -0
  9. data/lib/cli.rb +55 -0
  10. data/lib/client.rb +314 -54
  11. data/lib/client_runtime.rb +612 -0
  12. data/lib/config.rb +154 -0
  13. data/lib/driver/job_row_decoder.rb +85 -0
  14. data/lib/driver/postgres_capabilities.rb +41 -0
  15. data/lib/driver/runtime.rb +661 -0
  16. data/lib/driver.rb +13 -6
  17. data/lib/errors.rb +83 -0
  18. data/lib/event.rb +78 -0
  19. data/lib/insert_opts.rb +40 -11
  20. data/lib/job.rb +28 -13
  21. data/lib/job_persisted_logging/plugin.rb +108 -0
  22. data/lib/migration_cli.rb +90 -0
  23. data/lib/migrator.rb +189 -0
  24. data/lib/params.rb +96 -0
  25. data/lib/periodic_cron.rb +24 -0
  26. data/lib/periodic_job.rb +135 -0
  27. data/lib/resumable.rb +148 -0
  28. data/lib/riverqueue/testing/minitest.rb +18 -0
  29. data/lib/riverqueue/testing/rspec.rb +109 -0
  30. data/lib/riverqueue/testing.rb +134 -0
  31. data/lib/riverqueue.rb +16 -0
  32. data/lib/unique_args.rb +97 -0
  33. data/lib/unique_bitmask.rb +14 -12
  34. data/lib/worker.rb +152 -0
  35. data/lib/worker_runner.rb +106 -0
  36. data/migration/LICENSE +374 -0
  37. data/migration/README.md +11 -0
  38. data/migration/manifest.json +38 -0
  39. data/migration/postgresql/main/001_create_river_migration.down.sql +1 -0
  40. data/migration/postgresql/main/001_create_river_migration.up.sql +8 -0
  41. data/migration/postgresql/main/002_initial_schema.down.sql +5 -0
  42. data/migration/postgresql/main/002_initial_schema.up.sql +96 -0
  43. data/migration/postgresql/main/003_river_job_tags_non_null.down.sql +3 -0
  44. data/migration/postgresql/main/003_river_job_tags_non_null.up.sql +3 -0
  45. data/migration/postgresql/main/004_pending_and_more.down.sql +42 -0
  46. data/migration/postgresql/main/004_pending_and_more.up.sql +45 -0
  47. data/migration/postgresql/main/005_migration_unique_client.down.sql +57 -0
  48. data/migration/postgresql/main/005_migration_unique_client.up.sql +79 -0
  49. data/migration/postgresql/main/006_bulk_unique.down.sql +16 -0
  50. data/migration/postgresql/main/006_bulk_unique.up.sql +40 -0
  51. data/migration/postgresql/main/007_notification_outbox_sqlite_jsonb_and_sql_cleanup.down.sql +56 -0
  52. data/migration/postgresql/main/007_notification_outbox_sqlite_jsonb_and_sql_cleanup.up.sql +44 -0
  53. data/migration/postgresql/main/008_job_id_autoincrement.down.sql +3 -0
  54. data/migration/postgresql/main/008_job_id_autoincrement.up.sql +3 -0
  55. data/migration/sqlite/main/001_create_river_migration.down.sql +1 -0
  56. data/migration/sqlite/main/001_create_river_migration.up.sql +8 -0
  57. data/migration/sqlite/main/002_initial_schema.down.sql +8 -0
  58. data/migration/sqlite/main/002_initial_schema.up.sql +19 -0
  59. data/migration/sqlite/main/003_river_job_tags_non_null.down.sql +6 -0
  60. data/migration/sqlite/main/003_river_job_tags_non_null.up.sql +6 -0
  61. data/migration/sqlite/main/004_pending_and_more.down.sql +26 -0
  62. data/migration/sqlite/main/004_pending_and_more.up.sql +33 -0
  63. data/migration/sqlite/main/005_migration_unique_client.down.sql +37 -0
  64. data/migration/sqlite/main/005_migration_unique_client.up.sql +64 -0
  65. data/migration/sqlite/main/006_bulk_unique.down.sql +7 -0
  66. data/migration/sqlite/main/006_bulk_unique.up.sql +63 -0
  67. data/migration/sqlite/main/007_notification_outbox_sqlite_jsonb_and_sql_cleanup.down.sql +255 -0
  68. data/migration/sqlite/main/007_notification_outbox_sqlite_jsonb_and_sql_cleanup.up.sql +261 -0
  69. data/migration/sqlite/main/008_job_id_autoincrement.down.sql +121 -0
  70. data/migration/sqlite/main/008_job_id_autoincrement.up.sql +123 -0
  71. data/sig/client.rbs +95 -0
  72. data/sig/driver.rbs +170 -0
  73. data/sig/insert_opts.rbs +25 -0
  74. data/sig/job.rbs +72 -0
  75. data/sig/job_persisted_logging/plugin.rbs +29 -0
  76. data/sig/migrator.rbs +44 -0
  77. data/sig/riverqueue.rbs +2 -0
  78. data/sig/runtime.rbs +382 -0
  79. data/sig/testing.rbs +95 -0
  80. data/sig/unique_args.rbs +8 -0
  81. data/sig/unique_bitmask.rbs +8 -0
  82. data/sig/worker_runner.rbs +22 -0
  83. metadata +165 -10
@@ -0,0 +1,255 @@
1
+ --
2
+ -- SQL cleanup rollback.
3
+ --
4
+
5
+ --
6
+ -- Add back unused tables `river_client` and `river_client_queue`.
7
+ --
8
+
9
+ CREATE TABLE /* TEMPLATE: schema */river_client (
10
+ id text PRIMARY KEY NOT NULL,
11
+ created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
12
+ metadata blob NOT NULL DEFAULT (jsonb('{}')),
13
+ paused_at timestamp,
14
+ updated_at timestamp NOT NULL,
15
+ CONSTRAINT name_length CHECK (length(id) > 0 AND length(id) < 128)
16
+ );
17
+
18
+ CREATE TABLE /* TEMPLATE: schema */river_client_queue (
19
+ river_client_id text NOT NULL REFERENCES river_client (id) ON DELETE CASCADE,
20
+ name text NOT NULL,
21
+ created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
22
+ max_workers integer NOT NULL DEFAULT 0,
23
+ metadata blob NOT NULL DEFAULT (jsonb('{}')),
24
+ num_jobs_completed integer NOT NULL DEFAULT 0,
25
+ num_jobs_running integer NOT NULL DEFAULT 0,
26
+ updated_at timestamp NOT NULL,
27
+ PRIMARY KEY (river_client_id, name),
28
+ CONSTRAINT name_length CHECK (length(name) > 0 AND length(name) < 128),
29
+ CONSTRAINT num_jobs_completed_zero_or_positive CHECK (num_jobs_completed >= 0),
30
+ CONSTRAINT num_jobs_running_zero_or_positive CHECK (num_jobs_running >= 0)
31
+ );
32
+
33
+ --
34
+ -- SQLite JSONB conversion rollback.
35
+ --
36
+ -- Convert JSONB binary columns back to JSON text format and restore json()
37
+ -- defaults. The `river_job` rebuild also reverts the addition of `DEFAULT 25`
38
+ -- to `river_job.max_attempts`.
39
+ --
40
+ -- SQLite doesn't allow `ALTER TABLE ADD COLUMN` with non-constant defaults like
41
+ -- `json('{}')`, so rebuild each affected table instead.
42
+ --
43
+
44
+ --
45
+ -- river_job
46
+ --
47
+
48
+ DROP INDEX /* TEMPLATE: schema */river_job_kind;
49
+ DROP INDEX /* TEMPLATE: schema */river_job_state_and_finalized_at_index;
50
+ DROP INDEX /* TEMPLATE: schema */river_job_prioritized_fetching_index;
51
+ DROP INDEX /* TEMPLATE: schema */river_job_unique_idx;
52
+
53
+ ALTER TABLE /* TEMPLATE: schema */river_job RENAME TO river_job_old;
54
+
55
+ CREATE TABLE /* TEMPLATE: schema */river_job (
56
+ id integer PRIMARY KEY, -- SQLite aliases this to ROWID, which may reuse deleted IDs.
57
+ args blob NOT NULL DEFAULT '{}',
58
+ attempt integer NOT NULL DEFAULT 0,
59
+ attempted_at timestamp,
60
+ attempted_by blob, -- json
61
+ created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
62
+ errors blob, -- json
63
+ finalized_at timestamp,
64
+ kind text NOT NULL,
65
+ max_attempts integer NOT NULL,
66
+ metadata blob NOT NULL DEFAULT (json('{}')),
67
+ priority integer NOT NULL DEFAULT 1,
68
+ queue text NOT NULL DEFAULT 'default',
69
+ state text NOT NULL DEFAULT 'available',
70
+ scheduled_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
71
+ tags blob NOT NULL DEFAULT (json('[]')),
72
+ unique_key blob,
73
+ unique_states integer,
74
+ CONSTRAINT finalized_or_finalized_at_null CHECK (
75
+ (finalized_at IS NULL AND state NOT IN ('cancelled', 'completed', 'discarded')) OR
76
+ (finalized_at IS NOT NULL AND state IN ('cancelled', 'completed', 'discarded'))
77
+ ),
78
+ CONSTRAINT priority_in_range CHECK (priority >= 1 AND priority <= 4),
79
+ CONSTRAINT queue_length CHECK (length(queue) > 0 AND length(queue) < 128),
80
+ CONSTRAINT kind_length CHECK (length(kind) > 0 AND length(kind) < 128),
81
+ CONSTRAINT state_valid CHECK (state IN ('available', 'cancelled', 'completed', 'discarded', 'pending', 'retryable', 'running', 'scheduled'))
82
+ );
83
+
84
+ INSERT INTO /* TEMPLATE: schema */river_job (
85
+ id,
86
+ args,
87
+ attempt,
88
+ attempted_at,
89
+ attempted_by,
90
+ created_at,
91
+ errors,
92
+ finalized_at,
93
+ kind,
94
+ max_attempts,
95
+ metadata,
96
+ priority,
97
+ queue,
98
+ state,
99
+ scheduled_at,
100
+ tags,
101
+ unique_key,
102
+ unique_states
103
+ )
104
+ SELECT
105
+ id,
106
+ json(args),
107
+ attempt,
108
+ attempted_at,
109
+ CASE WHEN attempted_by IS NULL THEN NULL ELSE json(attempted_by) END,
110
+ created_at,
111
+ CASE WHEN errors IS NULL THEN NULL ELSE json(errors) END,
112
+ finalized_at,
113
+ kind,
114
+ max_attempts,
115
+ json(metadata),
116
+ priority,
117
+ queue,
118
+ state,
119
+ scheduled_at,
120
+ json(tags),
121
+ unique_key,
122
+ unique_states
123
+ FROM /* TEMPLATE: schema */river_job_old;
124
+
125
+ DROP TABLE /* TEMPLATE: schema */river_job_old;
126
+
127
+ CREATE INDEX /* TEMPLATE: schema */river_job_kind ON river_job (kind);
128
+ CREATE INDEX /* TEMPLATE: schema */river_job_state_and_finalized_at_index ON river_job (state, finalized_at) WHERE finalized_at IS NOT NULL;
129
+ CREATE INDEX /* TEMPLATE: schema */river_job_prioritized_fetching_index ON river_job (state, queue, priority, scheduled_at, id);
130
+ CREATE UNIQUE INDEX /* TEMPLATE: schema */river_job_unique_idx ON river_job (unique_key)
131
+ WHERE unique_key IS NOT NULL
132
+ AND unique_states IS NOT NULL
133
+ AND CASE state
134
+ WHEN 'available' THEN unique_states & (1 << 0)
135
+ WHEN 'cancelled' THEN unique_states & (1 << 1)
136
+ WHEN 'completed' THEN unique_states & (1 << 2)
137
+ WHEN 'discarded' THEN unique_states & (1 << 3)
138
+ WHEN 'pending' THEN unique_states & (1 << 4)
139
+ WHEN 'retryable' THEN unique_states & (1 << 5)
140
+ WHEN 'running' THEN unique_states & (1 << 6)
141
+ WHEN 'scheduled' THEN unique_states & (1 << 7)
142
+ ELSE 0
143
+ END >= 1;
144
+
145
+ --
146
+ -- river_queue
147
+ --
148
+
149
+ ALTER TABLE /* TEMPLATE: schema */river_queue RENAME TO river_queue_old;
150
+
151
+ CREATE TABLE /* TEMPLATE: schema */river_queue (
152
+ name text PRIMARY KEY NOT NULL,
153
+ created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
154
+ metadata blob NOT NULL DEFAULT (json('{}')),
155
+ paused_at timestamp,
156
+ updated_at timestamp NOT NULL
157
+ );
158
+
159
+ INSERT INTO /* TEMPLATE: schema */river_queue (
160
+ name,
161
+ created_at,
162
+ metadata,
163
+ paused_at,
164
+ updated_at
165
+ )
166
+ SELECT
167
+ name,
168
+ created_at,
169
+ json(metadata),
170
+ paused_at,
171
+ updated_at
172
+ FROM /* TEMPLATE: schema */river_queue_old;
173
+
174
+ DROP TABLE /* TEMPLATE: schema */river_queue_old;
175
+
176
+ --
177
+ -- river_client
178
+ --
179
+
180
+ ALTER TABLE /* TEMPLATE: schema */river_client RENAME TO river_client_old;
181
+
182
+ CREATE TABLE /* TEMPLATE: schema */river_client (
183
+ id text PRIMARY KEY NOT NULL,
184
+ created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
185
+ metadata blob NOT NULL DEFAULT (json('{}')),
186
+ paused_at timestamp,
187
+ updated_at timestamp NOT NULL,
188
+ CONSTRAINT name_length CHECK (length(id) > 0 AND length(id) < 128)
189
+ );
190
+
191
+ INSERT INTO /* TEMPLATE: schema */river_client (
192
+ id,
193
+ created_at,
194
+ metadata,
195
+ paused_at,
196
+ updated_at
197
+ )
198
+ SELECT
199
+ id,
200
+ created_at,
201
+ json(metadata),
202
+ paused_at,
203
+ updated_at
204
+ FROM /* TEMPLATE: schema */river_client_old;
205
+
206
+ --
207
+ -- river_client_queue
208
+ --
209
+
210
+ ALTER TABLE /* TEMPLATE: schema */river_client_queue RENAME TO river_client_queue_old;
211
+
212
+ CREATE TABLE /* TEMPLATE: schema */river_client_queue (
213
+ river_client_id text NOT NULL REFERENCES river_client (id) ON DELETE CASCADE,
214
+ name text NOT NULL,
215
+ created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
216
+ max_workers integer NOT NULL DEFAULT 0,
217
+ metadata blob NOT NULL DEFAULT (json('{}')),
218
+ num_jobs_completed integer NOT NULL DEFAULT 0,
219
+ num_jobs_running integer NOT NULL DEFAULT 0,
220
+ updated_at timestamp NOT NULL,
221
+ PRIMARY KEY (river_client_id, name),
222
+ CONSTRAINT name_length CHECK (length(name) > 0 AND length(name) < 128),
223
+ CONSTRAINT num_jobs_completed_zero_or_positive CHECK (num_jobs_completed >= 0),
224
+ CONSTRAINT num_jobs_running_zero_or_positive CHECK (num_jobs_running >= 0)
225
+ );
226
+
227
+ INSERT INTO /* TEMPLATE: schema */river_client_queue (
228
+ river_client_id,
229
+ name,
230
+ created_at,
231
+ max_workers,
232
+ metadata,
233
+ num_jobs_completed,
234
+ num_jobs_running,
235
+ updated_at
236
+ )
237
+ SELECT
238
+ river_client_id,
239
+ name,
240
+ created_at,
241
+ max_workers,
242
+ json(metadata),
243
+ num_jobs_completed,
244
+ num_jobs_running,
245
+ updated_at
246
+ FROM /* TEMPLATE: schema */river_client_queue_old;
247
+
248
+ DROP TABLE /* TEMPLATE: schema */river_client_queue_old;
249
+ DROP TABLE /* TEMPLATE: schema */river_client_old;
250
+
251
+ --
252
+ -- Notification outbox rollback.
253
+ --
254
+
255
+ DROP TABLE /* TEMPLATE: schema */river_notification;
@@ -0,0 +1,261 @@
1
+ --
2
+ -- Notification outbox.
3
+ --
4
+
5
+ CREATE TABLE /* TEMPLATE: schema */river_notification (
6
+ id integer PRIMARY KEY AUTOINCREMENT,
7
+ created_at timestamp NOT NULL DEFAULT (datetime('now', 'subsec')),
8
+ payload text NOT NULL,
9
+ topic text NOT NULL,
10
+ CONSTRAINT topic_length CHECK (length(topic) > 0 AND length(topic) < 128)
11
+ );
12
+
13
+ CREATE INDEX /* TEMPLATE: schema */river_notification_created_at_idx ON river_notification (created_at);
14
+ CREATE INDEX /* TEMPLATE: schema */river_notification_topic_id_idx ON river_notification (topic, id);
15
+
16
+ --
17
+ -- SQLite JSONB conversion.
18
+ --
19
+ -- Convert JSON text columns to JSONB binary format for more efficient storage
20
+ -- and processing, and update column defaults from json() to jsonb().
21
+ --
22
+ -- SQLite doesn't allow `ALTER TABLE ADD COLUMN` with non-constant defaults like
23
+ -- `jsonb('{}')`, so rebuild each affected table instead.
24
+ --
25
+
26
+ --
27
+ -- river_job
28
+ --
29
+
30
+ DROP INDEX /* TEMPLATE: schema */river_job_kind;
31
+ DROP INDEX /* TEMPLATE: schema */river_job_state_and_finalized_at_index;
32
+ DROP INDEX /* TEMPLATE: schema */river_job_prioritized_fetching_index;
33
+ DROP INDEX /* TEMPLATE: schema */river_job_unique_idx;
34
+
35
+ ALTER TABLE /* TEMPLATE: schema */river_job RENAME TO river_job_old;
36
+
37
+ CREATE TABLE /* TEMPLATE: schema */river_job (
38
+ id integer PRIMARY KEY, -- SQLite aliases this to ROWID, which may reuse deleted IDs.
39
+ args blob NOT NULL DEFAULT (jsonb('{}')),
40
+ attempt integer NOT NULL DEFAULT 0,
41
+ attempted_at timestamp,
42
+ attempted_by blob, -- json
43
+ created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
44
+ errors blob, -- json
45
+ finalized_at timestamp,
46
+ kind text NOT NULL,
47
+ max_attempts integer NOT NULL,
48
+ metadata blob NOT NULL DEFAULT (jsonb('{}')),
49
+ priority integer NOT NULL DEFAULT 1,
50
+ queue text NOT NULL DEFAULT 'default',
51
+ state text NOT NULL DEFAULT 'available',
52
+ scheduled_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
53
+ tags blob NOT NULL DEFAULT (jsonb('[]')),
54
+ unique_key blob,
55
+ unique_states integer,
56
+ CONSTRAINT finalized_or_finalized_at_null CHECK (
57
+ (finalized_at IS NULL AND state NOT IN ('cancelled', 'completed', 'discarded')) OR
58
+ (finalized_at IS NOT NULL AND state IN ('cancelled', 'completed', 'discarded'))
59
+ ),
60
+ CONSTRAINT priority_in_range CHECK (priority >= 1 AND priority <= 4),
61
+ CONSTRAINT queue_length CHECK (length(queue) > 0 AND length(queue) < 128),
62
+ CONSTRAINT kind_length CHECK (length(kind) > 0 AND length(kind) < 128),
63
+ CONSTRAINT state_valid CHECK (state IN ('available', 'cancelled', 'completed', 'discarded', 'pending', 'retryable', 'running', 'scheduled'))
64
+ );
65
+
66
+ INSERT INTO /* TEMPLATE: schema */river_job (
67
+ id,
68
+ args,
69
+ attempt,
70
+ attempted_at,
71
+ attempted_by,
72
+ created_at,
73
+ errors,
74
+ finalized_at,
75
+ kind,
76
+ max_attempts,
77
+ metadata,
78
+ priority,
79
+ queue,
80
+ state,
81
+ scheduled_at,
82
+ tags,
83
+ unique_key,
84
+ unique_states
85
+ )
86
+ SELECT
87
+ id,
88
+ jsonb(args),
89
+ attempt,
90
+ attempted_at,
91
+ CASE WHEN attempted_by IS NULL THEN NULL ELSE jsonb(attempted_by) END,
92
+ created_at,
93
+ CASE WHEN errors IS NULL THEN NULL ELSE jsonb(errors) END,
94
+ finalized_at,
95
+ kind,
96
+ max_attempts,
97
+ jsonb(metadata),
98
+ priority,
99
+ queue,
100
+ state,
101
+ scheduled_at,
102
+ jsonb(tags),
103
+ unique_key,
104
+ unique_states
105
+ FROM /* TEMPLATE: schema */river_job_old;
106
+
107
+ DROP TABLE /* TEMPLATE: schema */river_job_old;
108
+
109
+ CREATE INDEX /* TEMPLATE: schema */river_job_kind ON river_job (kind);
110
+ CREATE INDEX /* TEMPLATE: schema */river_job_state_and_finalized_at_index ON river_job (state, finalized_at) WHERE finalized_at IS NOT NULL;
111
+ CREATE INDEX /* TEMPLATE: schema */river_job_prioritized_fetching_index ON river_job (state, queue, priority, scheduled_at, id);
112
+ CREATE UNIQUE INDEX /* TEMPLATE: schema */river_job_unique_idx ON river_job (unique_key)
113
+ WHERE unique_key IS NOT NULL
114
+ AND unique_states IS NOT NULL
115
+ AND CASE state
116
+ WHEN 'available' THEN unique_states & (1 << 0)
117
+ WHEN 'cancelled' THEN unique_states & (1 << 1)
118
+ WHEN 'completed' THEN unique_states & (1 << 2)
119
+ WHEN 'discarded' THEN unique_states & (1 << 3)
120
+ WHEN 'pending' THEN unique_states & (1 << 4)
121
+ WHEN 'retryable' THEN unique_states & (1 << 5)
122
+ WHEN 'running' THEN unique_states & (1 << 6)
123
+ WHEN 'scheduled' THEN unique_states & (1 << 7)
124
+ ELSE 0
125
+ END >= 1;
126
+
127
+ --
128
+ -- river_queue
129
+ --
130
+
131
+ ALTER TABLE /* TEMPLATE: schema */river_queue RENAME TO river_queue_old;
132
+
133
+ CREATE TABLE /* TEMPLATE: schema */river_queue (
134
+ name text PRIMARY KEY NOT NULL,
135
+ created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
136
+ metadata blob NOT NULL DEFAULT (jsonb('{}')),
137
+ paused_at timestamp,
138
+ updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
139
+ );
140
+
141
+ INSERT INTO /* TEMPLATE: schema */river_queue (
142
+ name,
143
+ created_at,
144
+ metadata,
145
+ paused_at,
146
+ updated_at
147
+ )
148
+ SELECT
149
+ name,
150
+ created_at,
151
+ jsonb(metadata),
152
+ paused_at,
153
+ updated_at
154
+ FROM /* TEMPLATE: schema */river_queue_old;
155
+
156
+ DROP TABLE /* TEMPLATE: schema */river_queue_old;
157
+
158
+ --
159
+ -- river_client
160
+ --
161
+
162
+ ALTER TABLE /* TEMPLATE: schema */river_client RENAME TO river_client_old;
163
+
164
+ CREATE TABLE /* TEMPLATE: schema */river_client (
165
+ id text PRIMARY KEY NOT NULL,
166
+ created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
167
+ metadata blob NOT NULL DEFAULT (jsonb('{}')),
168
+ paused_at timestamp,
169
+ updated_at timestamp NOT NULL,
170
+ CONSTRAINT name_length CHECK (length(id) > 0 AND length(id) < 128)
171
+ );
172
+
173
+ INSERT INTO /* TEMPLATE: schema */river_client (
174
+ id,
175
+ created_at,
176
+ metadata,
177
+ paused_at,
178
+ updated_at
179
+ )
180
+ SELECT
181
+ id,
182
+ created_at,
183
+ jsonb(metadata),
184
+ paused_at,
185
+ updated_at
186
+ FROM /* TEMPLATE: schema */river_client_old;
187
+
188
+ --
189
+ -- river_client_queue
190
+ --
191
+
192
+ ALTER TABLE /* TEMPLATE: schema */river_client_queue RENAME TO river_client_queue_old;
193
+
194
+ CREATE TABLE /* TEMPLATE: schema */river_client_queue (
195
+ river_client_id text NOT NULL REFERENCES river_client (id) ON DELETE CASCADE,
196
+ name text NOT NULL,
197
+ created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
198
+ max_workers integer NOT NULL DEFAULT 0,
199
+ metadata blob NOT NULL DEFAULT (jsonb('{}')),
200
+ num_jobs_completed integer NOT NULL DEFAULT 0,
201
+ num_jobs_running integer NOT NULL DEFAULT 0,
202
+ updated_at timestamp NOT NULL,
203
+ PRIMARY KEY (river_client_id, name),
204
+ CONSTRAINT name_length CHECK (length(name) > 0 AND length(name) < 128),
205
+ CONSTRAINT num_jobs_completed_zero_or_positive CHECK (num_jobs_completed >= 0),
206
+ CONSTRAINT num_jobs_running_zero_or_positive CHECK (num_jobs_running >= 0)
207
+ );
208
+
209
+ INSERT INTO /* TEMPLATE: schema */river_client_queue (
210
+ river_client_id,
211
+ name,
212
+ created_at,
213
+ max_workers,
214
+ metadata,
215
+ num_jobs_completed,
216
+ num_jobs_running,
217
+ updated_at
218
+ )
219
+ SELECT
220
+ river_client_id,
221
+ name,
222
+ created_at,
223
+ max_workers,
224
+ jsonb(metadata),
225
+ num_jobs_completed,
226
+ num_jobs_running,
227
+ updated_at
228
+ FROM /* TEMPLATE: schema */river_client_queue_old;
229
+
230
+ DROP TABLE /* TEMPLATE: schema */river_client_queue_old;
231
+ DROP TABLE /* TEMPLATE: schema */river_client_old;
232
+
233
+ --
234
+ -- SQL cleanup.
235
+ --
236
+
237
+ --
238
+ -- Drop unused tables `river_client` and `river_client_queue`.
239
+ --
240
+
241
+ DROP TABLE /* TEMPLATE: schema */river_client_queue;
242
+ DROP TABLE /* TEMPLATE: schema */river_client;
243
+
244
+ --
245
+ -- Adds `DEFAULT 25` to `river_job.max_attempts`.
246
+ --
247
+
248
+ -- This may look odd in that we're adding a brand new column, but it's because
249
+ -- SQLite doesn't support anything beyond the most trivial DDL.
250
+
251
+ ALTER TABLE /* TEMPLATE: schema */river_job
252
+ RENAME COLUMN max_attempts TO max_attempts_old;
253
+
254
+ ALTER TABLE /* TEMPLATE: schema */river_job
255
+ ADD COLUMN max_attempts integer NOT NULL DEFAULT 25;
256
+
257
+ UPDATE /* TEMPLATE: schema */river_job
258
+ SET max_attempts = max_attempts_old;
259
+
260
+ ALTER TABLE /* TEMPLATE: schema */river_job
261
+ DROP COLUMN max_attempts_old;
@@ -0,0 +1,121 @@
1
+ -- Rebuild river_job to restore SQLite's default ROWID allocation behavior.
2
+
3
+ -- Rebuilding river_job would discard schema installed by River Pro. Check
4
+ -- schema objects instead of migration records to also catch manually applied
5
+ -- Pro migrations and the legacy workflow migration line.
6
+ CREATE TEMP TABLE river_job_pro_schema_guard (
7
+ id integer NOT NULL
8
+ );
9
+
10
+ CREATE TEMP TRIGGER river_job_pro_schema_guard_enforce
11
+ BEFORE INSERT ON river_job_pro_schema_guard
12
+ WHEN EXISTS (
13
+ SELECT 1
14
+ FROM /* TEMPLATE: schema */sqlite_master
15
+ WHERE name IN ('river_job_sequence', 'river_job_workflow_scheduling', 'river_workflow')
16
+ )
17
+ BEGIN
18
+ SELECT RAISE(ABORT, 'River SQLite migration 008 cannot run while River Pro schema is installed');
19
+ END;
20
+
21
+ INSERT INTO river_job_pro_schema_guard (id) VALUES (1);
22
+
23
+ DROP TRIGGER river_job_pro_schema_guard_enforce;
24
+ DROP TABLE river_job_pro_schema_guard;
25
+
26
+ DROP INDEX /* TEMPLATE: schema */river_job_kind;
27
+ DROP INDEX /* TEMPLATE: schema */river_job_state_and_finalized_at_index;
28
+ DROP INDEX /* TEMPLATE: schema */river_job_prioritized_fetching_index;
29
+ DROP INDEX /* TEMPLATE: schema */river_job_unique_idx;
30
+
31
+ ALTER TABLE /* TEMPLATE: schema */river_job RENAME TO river_job_old;
32
+
33
+ CREATE TABLE /* TEMPLATE: schema */river_job (
34
+ id integer PRIMARY KEY,
35
+ args blob NOT NULL DEFAULT (jsonb('{}')),
36
+ attempt integer NOT NULL DEFAULT 0,
37
+ attempted_at timestamp,
38
+ attempted_by blob, -- json
39
+ created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
40
+ errors blob, -- json
41
+ finalized_at timestamp,
42
+ kind text NOT NULL,
43
+ max_attempts integer NOT NULL DEFAULT 25,
44
+ metadata blob NOT NULL DEFAULT (jsonb('{}')),
45
+ priority integer NOT NULL DEFAULT 1,
46
+ queue text NOT NULL DEFAULT 'default',
47
+ state text NOT NULL DEFAULT 'available',
48
+ scheduled_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
49
+ tags blob NOT NULL DEFAULT (jsonb('[]')),
50
+ unique_key blob,
51
+ unique_states integer,
52
+ CONSTRAINT finalized_or_finalized_at_null CHECK (
53
+ (finalized_at IS NULL AND state NOT IN ('cancelled', 'completed', 'discarded')) OR
54
+ (finalized_at IS NOT NULL AND state IN ('cancelled', 'completed', 'discarded'))
55
+ ),
56
+ CONSTRAINT priority_in_range CHECK (priority >= 1 AND priority <= 4),
57
+ CONSTRAINT queue_length CHECK (length(queue) > 0 AND length(queue) < 128),
58
+ CONSTRAINT kind_length CHECK (length(kind) > 0 AND length(kind) < 128),
59
+ CONSTRAINT state_valid CHECK (state IN ('available', 'cancelled', 'completed', 'discarded', 'pending', 'retryable', 'running', 'scheduled'))
60
+ );
61
+
62
+ INSERT INTO /* TEMPLATE: schema */river_job (
63
+ id,
64
+ args,
65
+ attempt,
66
+ attempted_at,
67
+ attempted_by,
68
+ created_at,
69
+ errors,
70
+ finalized_at,
71
+ kind,
72
+ max_attempts,
73
+ metadata,
74
+ priority,
75
+ queue,
76
+ state,
77
+ scheduled_at,
78
+ tags,
79
+ unique_key,
80
+ unique_states
81
+ )
82
+ SELECT
83
+ id,
84
+ args,
85
+ attempt,
86
+ attempted_at,
87
+ attempted_by,
88
+ created_at,
89
+ errors,
90
+ finalized_at,
91
+ kind,
92
+ max_attempts,
93
+ metadata,
94
+ priority,
95
+ queue,
96
+ state,
97
+ scheduled_at,
98
+ tags,
99
+ unique_key,
100
+ unique_states
101
+ FROM /* TEMPLATE: schema */river_job_old;
102
+
103
+ DROP TABLE /* TEMPLATE: schema */river_job_old;
104
+
105
+ CREATE INDEX /* TEMPLATE: schema */river_job_kind ON river_job (kind);
106
+ CREATE INDEX /* TEMPLATE: schema */river_job_state_and_finalized_at_index ON river_job (state, finalized_at) WHERE finalized_at IS NOT NULL;
107
+ CREATE INDEX /* TEMPLATE: schema */river_job_prioritized_fetching_index ON river_job (state, queue, priority, scheduled_at, id);
108
+ CREATE UNIQUE INDEX /* TEMPLATE: schema */river_job_unique_idx ON river_job (unique_key)
109
+ WHERE unique_key IS NOT NULL
110
+ AND unique_states IS NOT NULL
111
+ AND CASE state
112
+ WHEN 'available' THEN unique_states & (1 << 0)
113
+ WHEN 'cancelled' THEN unique_states & (1 << 1)
114
+ WHEN 'completed' THEN unique_states & (1 << 2)
115
+ WHEN 'discarded' THEN unique_states & (1 << 3)
116
+ WHEN 'pending' THEN unique_states & (1 << 4)
117
+ WHEN 'retryable' THEN unique_states & (1 << 5)
118
+ WHEN 'running' THEN unique_states & (1 << 6)
119
+ WHEN 'scheduled' THEN unique_states & (1 << 7)
120
+ ELSE 0
121
+ END >= 1;