Skip to content

Commit d344ca0

Browse files
committed
Add wal command
1 parent c1c666a commit d344ca0

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
@@ -368,7 +368,7 @@ name generation lag start end
368368
s3 a295b16a796689f3 -156ms 2024-04-17T00:01:19Z 2024-04-17T00:01:19Z
369369
```
370370
371-
Finally, you can list the snapshots available for a database:
371+
You can list the snapshots available for a database:
372372
373373
```shell
374374
bin/rails litestream:snapshots -- --database=storage/production.sqlite3
@@ -381,6 +381,19 @@ replica generation index size created
381381
s3 a295b16a796689f3 1 4645465 2024-04-17T00:01:19Z
382382
```
383383

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

386399
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.
@@ -406,7 +419,14 @@ Litestream::Commands.snapshots('storage/production.sqlite3')
406419
# => [{"replica"=>"s3", "generation"=>"5f4341bc3d22d615", "index"=>"0", "size"=>"4645465", "created"=>"2024-04-17T19:48:09Z"}]
407420
```
408421

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

411431
```ruby
412432
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)