Skip to content

Commit 93de048

Browse files
Merge pull request #41 from alxvernier/wal-command
Add wal command
2 parents 9b6588c + d344ca0 commit 93de048

File tree

5 files changed

+218
-2
lines changed

5 files changed

+218
-2
lines changed

README.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ name generation lag start end
369369
s3 a295b16a796689f3 -156ms 2024-04-17T00:01:19Z 2024-04-17T00:01:19Z
370370
```
371371
372-
Finally, you can list the snapshots available for a database:
372+
You can list the snapshots available for a database:
373373
374374
```shell
375375
bin/rails litestream:snapshots -- --database=storage/production.sqlite3
@@ -382,6 +382,19 @@ replica generation index size created
382382
s3 a295b16a796689f3 1 4645465 2024-04-17T00:01:19Z
383383
```
384384

385+
Finally, you can list the wal files available for a database:
386+
387+
```shell
388+
bin/rails litestream:wal -- --database=storage/production.sqlite3
389+
```
390+
391+
This command lists wal files available for that specified database:
392+
393+
```
394+
replica generation index offset size created
395+
s3 a295b16a796689f3 1 0 2036 2024-04-17T00:01:19Z
396+
```
397+
385398
### Running commands from Ruby
386399

387400
In addition to the provided rake tasks, you can also run Litestream commands directly from Ruby. The gem provides a `Litestream::Commands` module that wraps the Litestream CLI commands. This is particularly useful for the introspection commands, as you can use the output in your Ruby code.
@@ -407,7 +420,14 @@ Litestream::Commands.snapshots('storage/production.sqlite3')
407420
# => [{"replica"=>"s3", "generation"=>"5f4341bc3d22d615", "index"=>"0", "size"=>"4645465", "created"=>"2024-04-17T19:48:09Z"}]
408421
```
409422

410-
You can also restore a database programatically using the `Litestream::Commands.restore` method, which returns the path to the restored database:
423+
The `Litestream::Commands.wal` method returns an array of hashes with the "replica", "generation", "index", "offset","size", and "created" keys for each wal:
424+
425+
```ruby
426+
Litestream::Commands.wal('storage/production.sqlite3')
427+
# => [{"replica"=>"s3", "generation"=>"5f4341bc3d22d615", "index"=>"0", "offset"=>"0", "size"=>"2036", "created"=>"2024-04-17T19:48:09Z"}]
428+
```
429+
430+
You can also restore a database programmatically using the `Litestream::Commands.restore` method, which returns the path to the restored database:
411431

412432
```ruby
413433
Litestream::Commands.restore('storage/production.sqlite3')

lib/litestream/commands.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ def snapshots(database, async: false, **argv)
104104
execute("snapshots", argv, database, async: async, tabled_output: true)
105105
end
106106

107+
def wal(database, async: false, **argv)
108+
raise DatabaseRequiredException, "database argument is required for wal command, e.g. litestream:wal -- --database=path/to/database.sqlite" if database.nil?
109+
110+
execute("wal", argv, database, async: async, tabled_output: true)
111+
end
112+
107113
private
108114

109115
def execute(command, argv = {}, database = nil, async: false, tabled_output: false)

lib/tasks/litestream_tasks.rake

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,18 @@ namespace :litestream do
7575

7676
Litestream::Commands.snapshots(database, async: true, **options)
7777
end
78+
79+
desc "List all wal files for a database or replica, for example `rake litestream:wal -- -database=storage/production.sqlite3`"
80+
task wal: :environment do
81+
options = {}
82+
if (separator_index = ARGV.index("--"))
83+
ARGV.slice(separator_index + 1, ARGV.length)
84+
.map { |pair| pair.split("=") }
85+
.each { |opt| options[opt[0]] = opt[1] || nil }
86+
end
87+
database = options.delete("--database") || options.delete("-database")
88+
options.symbolize_keys!
89+
90+
Litestream::Commands.wal(database, async: true, **options)
91+
end
7892
end

test/litestream/test_commands.rb

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,4 +689,137 @@ def test_snapshots_does_not_set_env_var_from_config_when_env_vars_already_set
689689
assert_equal "original_access", ENV["LITESTREAM_SECRET_ACCESS_KEY"]
690690
end
691691
end
692+
693+
class TestWalCommand < TestCommands
694+
def test_wal_with_no_options
695+
stub = proc do |cmd, async|
696+
executable, command, *argv = cmd
697+
assert_match Regexp.new("exe/test/litestream"), executable
698+
assert_equal "wal", command
699+
assert_equal 3, argv.size
700+
assert_equal "--config", argv[0]
701+
assert_match Regexp.new("dummy/config/litestream.yml"), argv[1]
702+
assert_equal "db/test.sqlite3", argv[2]
703+
end
704+
Litestream::Commands.stub :run, stub do
705+
Litestream::Commands.wal("db/test.sqlite3")
706+
end
707+
end
708+
709+
def test_wal_with_boolean_option
710+
stub = proc do |cmd, async|
711+
executable, command, *argv = cmd
712+
assert_match Regexp.new("exe/test/litestream"), executable
713+
assert_equal "wal", command
714+
assert_equal 4, argv.size
715+
assert_equal "--config", argv[0]
716+
assert_match Regexp.new("dummy/config/litestream.yml"), argv[1]
717+
assert_equal "--if-db-not-exists", argv[2]
718+
assert_equal "db/test.sqlite3", argv[3]
719+
end
720+
Litestream::Commands.stub :run, stub do
721+
Litestream::Commands.wal("db/test.sqlite3", "--if-db-not-exists" => nil)
722+
end
723+
end
724+
725+
def test_wal_with_string_option
726+
stub = proc do |cmd, async|
727+
executable, command, *argv = cmd
728+
assert_match Regexp.new("exe/test/litestream"), executable
729+
assert_equal "wal", command
730+
assert_equal 5, argv.size
731+
assert_equal "--config", argv[0]
732+
assert_match Regexp.new("dummy/config/litestream.yml"), argv[1]
733+
assert_equal "--parallelism", argv[2]
734+
assert_equal 10, argv[3]
735+
assert_equal "db/test.sqlite3", argv[4]
736+
end
737+
Litestream::Commands.stub :run, stub do
738+
Litestream::Commands.wal("db/test.sqlite3", "--parallelism" => 10)
739+
end
740+
end
741+
742+
def test_wal_with_config_option
743+
stub = proc do |cmd, async|
744+
executable, command, *argv = cmd
745+
assert_match Regexp.new("exe/test/litestream"), executable
746+
assert_equal "wal", command
747+
assert_equal 3, argv.size
748+
assert_equal "--config", argv[0]
749+
assert_equal "CONFIG", argv[1]
750+
assert_equal "db/test.sqlite3", argv[2]
751+
end
752+
Litestream::Commands.stub :run, stub do
753+
Litestream::Commands.wal("db/test.sqlite3", "--config" => "CONFIG")
754+
end
755+
end
756+
757+
def test_wal_sets_replica_bucket_env_var_from_config_when_env_var_not_set
758+
Litestream.replica_bucket = "mybkt"
759+
760+
Litestream::Commands.stub :run, nil do
761+
Litestream::Commands.wal("db/test.sqlite3")
762+
end
763+
764+
assert_equal "mybkt", ENV["LITESTREAM_REPLICA_BUCKET"]
765+
assert_nil ENV["LITESTREAM_ACCESS_KEY_ID"]
766+
assert_nil ENV["LITESTREAM_SECRET_ACCESS_KEY"]
767+
end
768+
769+
def test_wal_sets_replica_key_id_env_var_from_config_when_env_var_not_set
770+
Litestream.replica_key_id = "mykey"
771+
772+
Litestream::Commands.stub :run, nil do
773+
Litestream::Commands.wal("db/test.sqlite3")
774+
end
775+
776+
assert_nil ENV["LITESTREAM_REPLICA_BUCKET"]
777+
assert_equal "mykey", ENV["LITESTREAM_ACCESS_KEY_ID"]
778+
assert_nil ENV["LITESTREAM_SECRET_ACCESS_KEY"]
779+
end
780+
781+
def test_wal_sets_replica_access_key_env_var_from_config_when_env_var_not_set
782+
Litestream.replica_access_key = "access"
783+
784+
Litestream::Commands.stub :run, nil do
785+
Litestream::Commands.wal("db/test.sqlite3")
786+
end
787+
788+
assert_nil ENV["LITESTREAM_REPLICA_BUCKET"]
789+
assert_nil ENV["LITESTREAM_ACCESS_KEY_ID"]
790+
assert_equal "access", ENV["LITESTREAM_SECRET_ACCESS_KEY"]
791+
end
792+
793+
def test_wal_sets_all_env_vars_from_config_when_env_vars_not_set
794+
Litestream.replica_bucket = "mybkt"
795+
Litestream.replica_key_id = "mykey"
796+
Litestream.replica_access_key = "access"
797+
798+
Litestream::Commands.stub :run, nil do
799+
Litestream::Commands.wal("db/test.sqlite3")
800+
end
801+
802+
assert_equal "mybkt", ENV["LITESTREAM_REPLICA_BUCKET"]
803+
assert_equal "mykey", ENV["LITESTREAM_ACCESS_KEY_ID"]
804+
assert_equal "access", ENV["LITESTREAM_SECRET_ACCESS_KEY"]
805+
end
806+
807+
def test_wal_does_not_set_env_var_from_config_when_env_vars_already_set
808+
ENV["LITESTREAM_REPLICA_BUCKET"] = "original_bkt"
809+
ENV["LITESTREAM_ACCESS_KEY_ID"] = "original_key"
810+
ENV["LITESTREAM_SECRET_ACCESS_KEY"] = "original_access"
811+
812+
Litestream.replica_bucket = "mybkt"
813+
Litestream.replica_key_id = "mykey"
814+
Litestream.replica_access_key = "access"
815+
816+
Litestream::Commands.stub :run, nil do
817+
Litestream::Commands.wal("db/test.sqlite3")
818+
end
819+
820+
assert_equal "original_bkt", ENV["LITESTREAM_REPLICA_BUCKET"]
821+
assert_equal "original_key", ENV["LITESTREAM_ACCESS_KEY_ID"]
822+
assert_equal "original_access", ENV["LITESTREAM_SECRET_ACCESS_KEY"]
823+
end
824+
end
692825
end

test/tasks/test_litestream_tasks.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ def setup
1111
Rake::Task["litestream:databases"].reenable
1212
Rake::Task["litestream:generations"].reenable
1313
Rake::Task["litestream:snapshots"].reenable
14+
Rake::Task["litestream:wal"].reenable
1415
end
1516

1617
def teardown
@@ -218,4 +219,46 @@ def test_snapshots_task_with_arguments_without_separator
218219
fake.verify
219220
end
220221
end
222+
223+
class TestWalTask < TestLitestreamTasks
224+
def test_wal_task_with_only_database_using_single_dash
225+
ARGV.replace ["--", "-database=db/test.sqlite3"]
226+
fake = Minitest::Mock.new
227+
fake.expect :call, nil, ["db/test.sqlite3"], async: true
228+
Litestream::Commands.stub :wal, fake do
229+
Rake.application.invoke_task "litestream:wal"
230+
end
231+
fake.verify
232+
end
233+
234+
def test_wal_task_with_only_database_using_double_dash
235+
ARGV.replace ["--", "--database=db/test.sqlite3"]
236+
fake = Minitest::Mock.new
237+
fake.expect :call, nil, ["db/test.sqlite3"], async: true
238+
Litestream::Commands.stub :wal, fake do
239+
Rake.application.invoke_task "litestream:wal"
240+
end
241+
fake.verify
242+
end
243+
244+
def test_wal_task_with_arguments
245+
ARGV.replace ["--", "-database=db/test.sqlite3", "--if-db-not-exists"]
246+
fake = Minitest::Mock.new
247+
fake.expect :call, nil, ["db/test.sqlite3"], async: true, "--if-db-not-exists": nil
248+
Litestream::Commands.stub :wal, fake do
249+
Rake.application.invoke_task "litestream:wal"
250+
end
251+
fake.verify
252+
end
253+
254+
def test_wal_task_with_arguments_without_separator
255+
ARGV.replace ["-database=db/test.sqlite3"]
256+
fake = Minitest::Mock.new
257+
fake.expect :call, nil, [nil], async: true
258+
Litestream::Commands.stub :wal, fake do
259+
Rake.application.invoke_task "litestream:wal"
260+
end
261+
fake.verify
262+
end
263+
end
221264
end

0 commit comments

Comments
 (0)