riverqueue 0.14.0 → 0.15.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/CHANGELOG.md +6 -0
- data/docs/migrations.md +62 -0
- data/lib/cli.rb +27 -2
- data/lib/migration_cli.rb +76 -9
- data/lib/migrator.rb +12 -5
- data/sig/migrator.rbs +3 -0
- data/sig/worker_runner.rbs +5 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c862e762c9679461633535e60ce0063ea7cad3fbe42d1f4b35c97350826ee3e0
|
|
4
|
+
data.tar.gz: 9a80c1852a2d587408fdc8e4c0c0c6b1af20bba5c60c0b777787f87248eb4cc9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1b189d118fbb38fbf14f961ce645505c0a47e28421c1b0af11154c2dde5d6d057a4ddf25f88b4fc5a3d8f602c931db792281d89a73c8d0dab230fb9266aab1d4
|
|
7
|
+
data.tar.gz: ebf174f14f464c6be842647ddfc3a23a5ee3b33ffd84ce8c10b471849192bedcbc1e05db8e15d457ccf6cfbdfe8e3c04090882c636abb9fa2b6111ad2ffd483b
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.15.0] - 2026-10-30
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Add `river migrate-get` for offline Postgres/SQLite SQL export with version selection, exclusions, and schema substitution; `river validate` to check migration state without applying changes; and `river version` / `river --version` to report active core, Pro, and Ruby runtime versions. Export and validation support `--line pro`. [PR #1560](https://github.com/riverqueue/river/pull/1560).
|
|
15
|
+
|
|
10
16
|
## [0.14.0] - 2026-10-26
|
|
11
17
|
|
|
12
18
|
### Added
|
data/docs/migrations.md
CHANGED
|
@@ -40,6 +40,68 @@ bundle exec river migrate-down --database-url postgres://localhost/my_app --yes
|
|
|
40
40
|
Down migrations can delete jobs and other data. Back up the database and stop
|
|
41
41
|
workers first. `--target 0 --yes` removes the complete selected migration line.
|
|
42
42
|
|
|
43
|
+
## Validating migration state
|
|
44
|
+
|
|
45
|
+
Use `validate` in a deployment check to require every bundled migration in the
|
|
46
|
+
selected line to be applied:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
bundle exec river validate --database-url postgres://localhost/my_app
|
|
50
|
+
bundle exec river validate --database-url postgres://localhost/my_app --line pro
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Validation exits with status 0 and no output when the selected line is current.
|
|
54
|
+
Pending migrations are listed on stderr with exit status 1. Invalid history or
|
|
55
|
+
connection errors also return 1. Validation reads migration history without
|
|
56
|
+
applying migrations or changing data; it does not inspect individual tables or
|
|
57
|
+
columns for schema drift. It accepts the same `DATABASE_URL`, `--schema`, and
|
|
58
|
+
driver selection as the migration commands. Validate main and Pro separately.
|
|
59
|
+
|
|
60
|
+
## Version information
|
|
61
|
+
|
|
62
|
+
`version` and the top-level `--version` flag print the active core gem version
|
|
63
|
+
and Ruby runtime version, plus the Pro gem version when `riverqueue-pro` is
|
|
64
|
+
active in the bundle:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
bundle exec river version
|
|
68
|
+
bundle exec river --version
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Neither command connects to a database or loads a database adapter. The
|
|
72
|
+
`--version` option on `migrate-get` still selects migration versions to export.
|
|
73
|
+
|
|
74
|
+
## Exporting SQL for other frameworks
|
|
75
|
+
|
|
76
|
+
`migrate-get` prints SQL without connecting to a database or requiring a River
|
|
77
|
+
driver gem. Select exactly one of `--version` or `--all`, and one of `--up` or
|
|
78
|
+
`--down`:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
bundle exec river migrate-get --version 3 --up > river3.up.sql
|
|
82
|
+
bundle exec river migrate-get --version 3 --down > river3.down.sql
|
|
83
|
+
bundle exec river migrate-get --all --exclude-version 1 --up > river_all.up.sql
|
|
84
|
+
bundle exec river migrate-get --all --exclude-version 1 --down > river_all.down.sql
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`--version` and `--exclude-version` accept comma-separated values and may be
|
|
88
|
+
repeated. Explicit versions retain the supplied order. `--all` exports up SQL
|
|
89
|
+
in ascending order and down SQL in descending order. Version 1 creates River's
|
|
90
|
+
migration-tracking table and can usually be omitted with another framework.
|
|
91
|
+
Preserve version boundaries and apply each migration in its own transaction.
|
|
92
|
+
|
|
93
|
+
Export defaults to Postgres and ignores `DATABASE_URL`. Use
|
|
94
|
+
`--database-url sqlite://` (or `sqlite3://`) to select SQLite SQL; the URL is only
|
|
95
|
+
a dialect hint. `--schema jobs` qualifies Postgres objects with that schema.
|
|
96
|
+
Without it, objects are unqualified. Schema placeholders are always substituted.
|
|
97
|
+
|
|
98
|
+
With `riverqueue-pro` installed, export Pro's bundled SQL using the same command:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
bundle exec river migrate-get --line pro --version 1 --up > river_pro_1.up.sql
|
|
102
|
+
bundle exec river migrate-get --line pro --version 1 --down > river_pro_1.down.sql
|
|
103
|
+
```
|
|
104
|
+
|
|
43
105
|
## Ruby API
|
|
44
106
|
|
|
45
107
|
```ruby
|
data/lib/cli.rb
CHANGED
|
@@ -5,14 +5,16 @@ require_relative "migration_cli"
|
|
|
5
5
|
require_relative "worker_runner"
|
|
6
6
|
|
|
7
7
|
module River
|
|
8
|
-
# Shared command dispatch for migrations
|
|
8
|
+
# Shared command dispatch for migrations, version information, and workers.
|
|
9
9
|
module CLI
|
|
10
10
|
# Runs a River command and returns its process exit status.
|
|
11
11
|
def self.run(argv, err: $stderr, out: $stdout)
|
|
12
|
+
return version(argv.drop(1), err: err, out: out) if %w[version --version].include?(argv.first)
|
|
12
13
|
return worker(argv.drop(1), out: out) if argv.first == "worker"
|
|
13
14
|
|
|
14
15
|
if argv.empty? || %w[-h --help].include?(argv.first)
|
|
15
|
-
out.puts("Usage: river worker|migrate-up|migrate-down|migrate-status [options]")
|
|
16
|
+
out.puts("Usage: river worker|migrate-up|migrate-down|migrate-get|migrate-status|validate|version [options]")
|
|
17
|
+
out.puts("Use river --version to print gem and Ruby versions.")
|
|
16
18
|
return 0
|
|
17
19
|
end
|
|
18
20
|
|
|
@@ -22,6 +24,29 @@ module River
|
|
|
22
24
|
1
|
|
23
25
|
end
|
|
24
26
|
|
|
27
|
+
# Prints active gem versions and the Ruby runtime without loading adapters.
|
|
28
|
+
def self.version(argv, err:, out:)
|
|
29
|
+
parser = OptionParser.new do |args|
|
|
30
|
+
args.banner = "Usage: river version | river --version"
|
|
31
|
+
args.on("-h", "--help") do
|
|
32
|
+
out.puts(args)
|
|
33
|
+
return 0
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
args = argv.dup
|
|
37
|
+
parser.parse!(args)
|
|
38
|
+
raise ArgumentError, parser.banner unless args.empty?
|
|
39
|
+
|
|
40
|
+
out.puts("River version #{Gem.loaded_specs["riverqueue"]&.version || "(unknown)"}")
|
|
41
|
+
pro = Gem.loaded_specs["riverqueue-pro"]
|
|
42
|
+
out.puts("River Pro version #{pro.version}") if pro
|
|
43
|
+
out.puts("Ruby #{RUBY_VERSION}")
|
|
44
|
+
0
|
|
45
|
+
rescue ArgumentError, OptionParser::ParseError => error
|
|
46
|
+
err.puts("Version failed: #{error.message}")
|
|
47
|
+
1
|
|
48
|
+
end
|
|
49
|
+
|
|
25
50
|
def self.worker(argv, out:)
|
|
26
51
|
options = {} #: Hash[Symbol, untyped]
|
|
27
52
|
parser = OptionParser.new do |args|
|
data/lib/migration_cli.rb
CHANGED
|
@@ -11,14 +11,21 @@ module River
|
|
|
11
11
|
# @type var active_record_base: untyped
|
|
12
12
|
options = {line: "main"} #: Hash[Symbol, untyped]
|
|
13
13
|
parser = OptionParser.new do |args|
|
|
14
|
-
args.banner = "Usage: river migrate-up|migrate-down|migrate-status [options]"
|
|
15
|
-
args.on("--database-url URL", "Defaults to DATABASE_URL") { |value| options[:url] = value }
|
|
14
|
+
args.banner = "Usage: river migrate-up|migrate-down|migrate-get|migrate-status|validate [options]"
|
|
15
|
+
args.on("--database-url URL", "Defaults to DATABASE_URL; migrate-get uses only the dialect (default: Postgres)") { |value| options[:url] = value }
|
|
16
16
|
args.on("--line NAME", %w[main pro]) { |value| options[:line] = value }
|
|
17
17
|
args.on("--schema NAME") { |value| options[:schema] = value }
|
|
18
18
|
args.on("--steps N", Integer) { |value| options[:steps] = value }
|
|
19
19
|
args.on("--target VERSION", Integer) { |value| options[:target] = value }
|
|
20
20
|
args.on("--dry-run") { options[:dry_run] = true }
|
|
21
21
|
args.on("--yes", "Confirm destructive down migrations") { options[:yes] = true }
|
|
22
|
+
args.separator ""
|
|
23
|
+
args.separator "migrate-get: export SQL without a database connection or driver gem"
|
|
24
|
+
args.on("--all", "Export all versions in migration order") { options[:all] = true }
|
|
25
|
+
args.on("--up", "Export up SQL") { options[:up] = true }
|
|
26
|
+
args.on("--down", "Export down SQL") { options[:down] = true }
|
|
27
|
+
args.on("--version N[,N...]", "Versions in the supplied order; repeatable") { |value| (options[:version] ||= []).concat(parse_versions(value, "--version")) }
|
|
28
|
+
args.on("--exclude-version N[,N...]", "Versions to omit; repeatable") { |value| (options[:exclude_version] ||= []).concat(parse_versions(value, "--exclude-version")) }
|
|
22
29
|
args.on("-h", "--help") {
|
|
23
30
|
out.puts(args)
|
|
24
31
|
return 0
|
|
@@ -28,7 +35,19 @@ module River
|
|
|
28
35
|
args = argv.dup
|
|
29
36
|
parser.parse!(args)
|
|
30
37
|
command = args.shift
|
|
31
|
-
raise ArgumentError, parser.banner unless %w[migrate-up migrate-down migrate-status].include?(command) && args.empty?
|
|
38
|
+
raise ArgumentError, parser.banner unless %w[migrate-up migrate-down migrate-get migrate-status validate].include?(command) && args.empty?
|
|
39
|
+
|
|
40
|
+
incompatible_options = options.keys & ((command == "migrate-get") ? %i[dry_run steps target yes] : %i[all down exclude_version up version])
|
|
41
|
+
incompatible_options |= options.keys & %i[dry_run steps target yes] if command == "validate"
|
|
42
|
+
unless incompatible_options.empty?
|
|
43
|
+
raise ArgumentError, "#{incompatible_options.map { |key| "--#{key.to_s.tr("_", "-")}" }.join(", ")} cannot be used with #{command}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
if options[:line] == "pro"
|
|
47
|
+
require "riverqueue-pro"
|
|
48
|
+
end
|
|
49
|
+
migrator_class = (options[:line] == "pro") ? River.const_get(:Pro).const_get(:Migrator) : River::Migrator
|
|
50
|
+
return export(options, migrator_class, out: out) if command == "migrate-get"
|
|
32
51
|
|
|
33
52
|
url = options[:url] || ENV["DATABASE_URL"]
|
|
34
53
|
raise ArgumentError, "provide --database-url or DATABASE_URL" if url.nil? || url.empty?
|
|
@@ -39,11 +58,6 @@ module River
|
|
|
39
58
|
|
|
40
59
|
adapter = detect_driver
|
|
41
60
|
|
|
42
|
-
if options[:line] == "pro"
|
|
43
|
-
require "riverqueue-pro"
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
migrator_class = (options[:line] == "pro") ? River.const_get(:Pro).const_get(:Migrator) : River::Migrator
|
|
47
61
|
if adapter == "activerecord"
|
|
48
62
|
require "riverqueue-activerecord"
|
|
49
63
|
active_record_base = Object.const_get("ActiveRecord::Base")
|
|
@@ -56,7 +70,13 @@ module River
|
|
|
56
70
|
end
|
|
57
71
|
|
|
58
72
|
migrator = migrator_class.new(driver, schema: options[:schema])
|
|
59
|
-
if command == "
|
|
73
|
+
if command == "validate"
|
|
74
|
+
pending = migrator.status.reject(&:applied)
|
|
75
|
+
unless pending.empty?
|
|
76
|
+
err.puts("unapplied migrations: #{pending.map(&:version).join(", ")}")
|
|
77
|
+
return 1
|
|
78
|
+
end
|
|
79
|
+
elsif command == "migrate-status"
|
|
60
80
|
migrator.status.each { |migration| out.puts("#{migration.applied ? "applied" : "pending"} #{migration.version.to_s.rjust(3, "0")} #{migration.name}") }
|
|
61
81
|
else
|
|
62
82
|
migrations = migrator.migrate(direction: (command == "migrate-up") ? :up : :down,
|
|
@@ -86,5 +106,52 @@ module River
|
|
|
86
106
|
driver
|
|
87
107
|
end
|
|
88
108
|
private_class_method :detect_driver
|
|
109
|
+
|
|
110
|
+
def self.export(options, migrator_class, out:)
|
|
111
|
+
versions = options.fetch(:version, [])
|
|
112
|
+
excluded = options.fetch(:exclude_version, [])
|
|
113
|
+
raise ArgumentError, "pass exactly one of --all or --version" if !!options[:all] == versions.any?
|
|
114
|
+
raise ArgumentError, "pass exactly one of --up or --down" if !!options[:up] == !!options[:down]
|
|
115
|
+
|
|
116
|
+
backend = case options[:url] || "postgres://"
|
|
117
|
+
when /\Apostgres(?:ql)?:/i then :postgresql
|
|
118
|
+
when /\Asqlite3?:/i then :sqlite
|
|
119
|
+
else raise ArgumentError, "--database-url must use postgres:, postgresql:, sqlite:, or sqlite3:"
|
|
120
|
+
end
|
|
121
|
+
schema = options[:schema]
|
|
122
|
+
raise ArgumentError, "SQLite does not support a migration schema option" if backend == :sqlite && schema
|
|
123
|
+
raise ArgumentError, "schema must be a simple SQL identifier" if schema && !schema.match?(/\A[a-zA-Z_][a-zA-Z0-9_]*\z/)
|
|
124
|
+
|
|
125
|
+
migrations = migrator_class.migrations(backend: backend, line: options[:line])
|
|
126
|
+
selected = if options[:all]
|
|
127
|
+
options[:down] ? migrations.reverse : migrations
|
|
128
|
+
else
|
|
129
|
+
versions.map do |version|
|
|
130
|
+
migrations.find { |migration| migration.version == version } || raise(ArgumentError, "migration #{version} does not exist")
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
selected = selected.reject { |migration| excluded.include?(migration.version) }
|
|
134
|
+
direction = options[:down] ? "down" : "up"
|
|
135
|
+
selected.each_with_index do |migration, index|
|
|
136
|
+
out.puts if index.positive?
|
|
137
|
+
out.puts("-- River #{options[:line]} migration #{migration.version.to_s.rjust(3, "0")} [#{direction}]")
|
|
138
|
+
sql = options[:down] ? migration.sql_down : migration.sql_up
|
|
139
|
+
out.puts(sql.gsub("/* TEMPLATE: schema */", schema ? %("#{schema}".) : "").strip)
|
|
140
|
+
end
|
|
141
|
+
0
|
|
142
|
+
end
|
|
143
|
+
private_class_method :export
|
|
144
|
+
|
|
145
|
+
def self.parse_versions(value, flag)
|
|
146
|
+
raise ArgumentError, "#{flag} must contain positive integers separated by commas" unless value.match?(/\A[0-9]+(?:,[0-9]+)*\z/)
|
|
147
|
+
|
|
148
|
+
value.split(",").map do |entry|
|
|
149
|
+
version = Integer(entry, 10)
|
|
150
|
+
raise ArgumentError, "#{flag} must contain positive integers" unless version.positive?
|
|
151
|
+
|
|
152
|
+
version
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
private_class_method :parse_versions
|
|
89
156
|
end
|
|
90
157
|
end
|
data/lib/migrator.rb
CHANGED
|
@@ -15,22 +15,29 @@ module River
|
|
|
15
15
|
def initialize(driver, line: "main", migrations_path: File.expand_path("../migration", __dir__.to_s), schema: nil)
|
|
16
16
|
@backend = driver.migration_backend
|
|
17
17
|
@driver = driver
|
|
18
|
-
raise ArgumentError, "unsupported migration backend" unless [:postgresql, :sqlite].include?(@backend)
|
|
19
|
-
raise ArgumentError, "invalid migration line" unless line.match?(/\A[a-z][a-z0-9_]*\z/)
|
|
20
18
|
raise ArgumentError, "SQLite does not support a migration schema option" if @backend == :sqlite && schema
|
|
21
19
|
|
|
22
20
|
@line = line
|
|
21
|
+
@migrations = Migrator.migrations(backend: @backend, line: line, migrations_path: migrations_path)
|
|
23
22
|
@mutex = Mutex.new
|
|
24
23
|
@requested_schema = schema
|
|
24
|
+
end
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
# Returns bundled SQL without a driver or database connection.
|
|
27
|
+
def self.migrations(backend: :postgresql, line: "main", migrations_path: File.expand_path("../migration", __dir__.to_s))
|
|
28
|
+
raise ArgumentError, "unsupported migration backend" unless [:postgresql, :sqlite].include?(backend)
|
|
29
|
+
raise ArgumentError, "invalid migration line" unless line.match?(/\A[a-z][a-z0-9_]*\z/)
|
|
30
|
+
|
|
31
|
+
directory = File.join(migrations_path, backend.to_s, line)
|
|
32
|
+
migrations = Dir.glob(File.join(directory, "*.up.sql")).sort.map do |path|
|
|
28
33
|
version, name = File.basename(path).delete_suffix(".up.sql").split("_", 2)
|
|
29
34
|
Migration.new(Integer(version.to_s, 10), name, File.read(path).freeze, File.read(path.sub(/\.up\.sql\z/, ".down.sql")).freeze)
|
|
30
35
|
end.freeze
|
|
31
|
-
unless
|
|
36
|
+
unless migrations.any? && migrations.map(&:version) == (1..migrations.length).to_a
|
|
32
37
|
raise ArgumentError, "migration files must contain contiguous versions starting at 1"
|
|
33
38
|
end
|
|
39
|
+
|
|
40
|
+
migrations
|
|
34
41
|
end
|
|
35
42
|
|
|
36
43
|
# Applies pending migrations up (all by default) or down (one by default).
|
data/sig/migrator.rbs
CHANGED
|
@@ -5,6 +5,8 @@ end
|
|
|
5
5
|
module River
|
|
6
6
|
module MigrationCLI
|
|
7
7
|
def self.detect_driver: () -> String
|
|
8
|
+
def self.export: (Hash[Symbol, untyped], untyped, out: untyped) -> Integer
|
|
9
|
+
def self.parse_versions: (String, String) -> Array[Integer]
|
|
8
10
|
def self.run: (Array[String], ?err: untyped, ?out: untyped) -> Integer
|
|
9
11
|
end
|
|
10
12
|
class Migrator
|
|
@@ -31,6 +33,7 @@ module River
|
|
|
31
33
|
@schema: untyped
|
|
32
34
|
attr_reader migrations: Array[Migration]
|
|
33
35
|
def initialize: (untyped, ?line: String, ?migrations_path: String, ?schema: untyped) -> void
|
|
36
|
+
def self.migrations: (?backend: Symbol, ?line: String, ?migrations_path: String) -> Array[Migration]
|
|
34
37
|
def migrate: (?direction: Symbol, ?dry_run: bool, ?steps: untyped, ?target: untyped) -> Array[Migration]
|
|
35
38
|
def status: () -> Array[Status]
|
|
36
39
|
private
|
data/sig/worker_runner.rbs
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
class Gem::BasicSpecification
|
|
2
|
+
attr_reader version: Gem::Version
|
|
3
|
+
end
|
|
4
|
+
|
|
1
5
|
module Process
|
|
2
6
|
def self.exit!: (?Integer) -> bot
|
|
3
7
|
end
|
|
@@ -5,6 +9,7 @@ end
|
|
|
5
9
|
module River
|
|
6
10
|
module CLI
|
|
7
11
|
def self.run: (Array[String], ?err: untyped, ?out: untyped) -> Integer
|
|
12
|
+
def self.version: (Array[String], err: untyped, out: untyped) -> Integer
|
|
8
13
|
def self.worker: (Array[String], out: untyped) -> Integer
|
|
9
14
|
end
|
|
10
15
|
|