Skip to content

Commit 102f4ca

Browse files
authored
Add sqlite support for db commands (#183)
1 parent 55671c5 commit 102f4ca

File tree

16 files changed

+1129
-1212
lines changed

16 files changed

+1129
-1212
lines changed

Gemfile

-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ gem "hanami-router", github: "hanami/router", branch: "main"
1717
gem "hanami-utils", github: "hanami/utils", branch: "main"
1818

1919
gem "dry-files", github: "dry-rb/dry-files", branch: "main"
20-
gem "dry-system", github: "dry-rb/dry-system", branch: "main"
2120

2221
gem "rack"
2322

lib/hanami/cli/commands/app/db/command.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def build_database(slice)
9494
def warn_on_misconfigured_database(database, slices)
9595
if slices.length > 1
9696
out.puts <<~STR
97-
WARNING: Database #{database.name} has config/db/ directories in multiple slices:
97+
WARNING: Database #{database.name} is configured for multiple config/db/ directories:
9898
9999
#{slices.map { "- " + _1.root.relative_path_from(_1.app.root).join("config", "db").to_s }.join("\n")}
100100

lib/hanami/cli/commands/app/db/structure/load.rb

+13-3
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,32 @@ class Load < DB::Command
1616

1717
# @api private
1818
def call(app: false, slice: nil, command_exit: method(:exit), **)
19-
# TODO: handle exit_codes here
19+
exit_codes = []
2020

2121
databases(app: app, slice: slice).each do |database|
2222
structure_path = database.slice.root.join(STRUCTURE_PATH)
2323
next unless structure_path.exist?
2424

2525
relative_structure_path = structure_path.relative_path_from(database.slice.app.root)
26+
2627
measure("#{database.name} structure loaded from #{relative_structure_path}") do
27-
database.exec_load_command.tap do |result|
28+
catch :load_failed do
29+
result = database.exec_load_command
30+
exit_codes << result.exit_code if result.respond_to?(:exit_code)
31+
2832
unless result.successful?
2933
out.puts result.err
30-
break false
34+
throw :load_failed, false
3135
end
36+
37+
true
3238
end
3339
end
3440
end
41+
42+
exit_codes.each do |code|
43+
break command_exit.(code) if code > 0
44+
end
3545
end
3646
end
3747
end

lib/hanami/cli/commands/app/db/utils/database.rb

+4-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ def initialize(slice:, system_call:)
5454
end
5555

5656
def name
57-
# Strip leading / - should this be skipped for sqlite?
5857
database_uri.path.sub(%r{^/}, "")
5958
end
6059

@@ -136,6 +135,10 @@ def migrations_dir?
136135
migrations_path.directory?
137136
end
138137

138+
def structure_file
139+
slice.root.join("config/db/structure.sql")
140+
end
141+
139142
def schema_migrations_sql_dump
140143
sql = +"INSERT INTO schema_migrations (filename) VALUES\n"
141144
sql << applied_migrations.map { |v| "('#{v}')" }.join(",\n")

lib/hanami/cli/commands/app/db/utils/postgres.rb

+11-4
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,42 @@ module App
1111
module DB
1212
module Utils
1313
# @api private
14+
# @since 2.2.0
1415
class Postgres < Database
16+
# @api private
17+
# @since 2.2.0
1518
def exec_create_command
1619
return true if exists?
1720

1821
system_call.call("createdb #{escaped_name}", env: cli_env_vars)
1922
end
2023

24+
# @api private
25+
# @since 2.2.0
2126
def exec_drop_command
2227
return true unless exists?
2328

2429
system_call.call("dropdb #{escaped_name}", env: cli_env_vars)
2530
end
2631

32+
# @api private
33+
# @since 2.2.0
2734
def exists?
2835
result = system_call.call("psql -t -A -c '\\list #{escaped_name}'", env: cli_env_vars)
2936
result.successful? && result.out.include?("#{name}|") # start_with?
3037
end
3138

39+
# @api private
40+
# @since 2.2.0
3241
def exec_dump_command
3342
system_call.call(
3443
"pg_dump --schema-only --no-privileges --no-owner --file #{structure_file} #{escaped_name}",
3544
env: cli_env_vars
3645
)
3746
end
3847

48+
# @api private
49+
# @since 2.2.0
3950
def exec_load_command
4051
system_call.call(
4152
"psql --set ON_ERROR_STOP=1 --quiet --no-psqlrc --output #{File::NULL} --file #{structure_file} #{escaped_name}",
@@ -56,10 +67,6 @@ def cli_env_vars
5667
end
5768
end
5869

59-
def structure_file
60-
slice.root.join("config/db/structure.sql")
61-
end
62-
6370
def schema_migrations_sql_dump
6471
search_path = slice["db.gateway"].connection
6572
.fetch("SHOW search_path").to_a.first

lib/hanami/cli/commands/app/db/utils/sqlite.rb

+55-16
Original file line numberDiff line numberDiff line change
@@ -9,41 +9,80 @@ module App
99
module DB
1010
module Utils
1111
# @api private
12+
# @since 2.2.0
1213
class Sqlite < Database
13-
def name
14-
@name ||= begin
15-
db_path = Pathname(database_uri.path).expand_path
16-
app_path = slice.app.root.realpath
14+
# @api private
15+
# @since 2.2.0
16+
Failure = Struct.new(:err) do
17+
def successful?
18+
false
19+
end
1720

18-
if db_path.to_s.start_with?("#{app_path.to_s}#{File::SEPARATOR}")
19-
db_path.relative_path_from(app_path).to_s
20-
else
21-
db_path.to_s
22-
end
21+
def exit_code
22+
1
2323
end
2424
end
2525

26-
def create_command
27-
true
26+
# @api private
27+
# @since 2.2.0
28+
def exec_create_command
29+
return true if exists?
30+
31+
FileUtils.mkdir_p(File.dirname(file_path))
32+
33+
system_call.call(%(sqlite3 #{file_path} "VACUUM;"))
2834
end
2935

30-
def drop_command
31-
file_path.unlink
36+
# @api private
37+
# @since 2.2.0
38+
def exec_drop_command
39+
begin
40+
File.unlink(file_path) if exists?
41+
rescue => e
42+
# Mimic a system_call result
43+
return Failure.new(e.message)
44+
end
45+
3246
true
3347
end
3448

49+
# @api private
50+
# @since 2.2.0
51+
def exists?
52+
File.exist?(file_path)
53+
end
54+
55+
# @api private
56+
# @since 2.2.0
3557
def exec_dump_command
36-
raise Hanami::CLI::NotImplementedError
58+
system_call.call(%(sqlite3 #{file_path} ".schema --indent --nosys" > #{structure_file}))
3759
end
3860

61+
# @api private
62+
# @since 2.2.0
3963
def exec_load_command
40-
raise Hanami::CLI::NotImplementedError
64+
system_call.call("sqlite3 #{file_path} < #{structure_file}")
65+
end
66+
67+
# @api private
68+
# @since 2.2.0
69+
def name
70+
# Sequel expects sqlite:// URIs to operate the same as file:// URIs: 2 slashes for
71+
# a relative path, 3 for an absolute path. In the case of 2 slashes, the first part
72+
# of the path is considered by Ruby's `URI` as the `#host`.
73+
@name ||= "#{database_uri.host}#{database_uri.path}"
4174
end
4275

4376
private
4477

4578
def file_path
46-
@file_path ||= Pathname(slice.root.join(config.uri.path)).realpath
79+
@file_path ||= begin
80+
if File.absolute_path?(name)
81+
name
82+
else
83+
slice.app.root.join(name).to_s
84+
end
85+
end
4786
end
4887
end
4988
end

spec/support/app_integration.rb

+3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ def autoloaders_teardown!
5454
Hanami.instance_variable_set(:@_bundled, {})
5555
Hanami.remove_instance_variable(:@_app) if Hanami.instance_variable_defined?(:@_app)
5656

57+
# Clear cached DB gateways across slices
58+
Hanami::Providers::DB.cache.clear
59+
5760
$LOAD_PATH.replace(@load_paths)
5861

5962
# Remove example-specific LOADED_FEATURES added when running each example

spec/support/postgres.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
require "uri"
55

66
# Default to a URL that should work with postgres as installed by asdf/mise.
7-
POSTGRES_BASE_URL = ENV.fetch("POSTGRES_BASE_URL", "postgres://postgres@localhost:5432/hanami_cli_test")
7+
POSTGRES_BASE_DB_NAME = "hanami_cli_test"
8+
POSTGRES_BASE_URL = ENV.fetch("POSTGRES_BASE_URL", "postgres://postgres@localhost:5432/#{POSTGRES_BASE_DB_NAME}")
89
POSTGRES_BASE_URI = URI(POSTGRES_BASE_URL)
910

1011
RSpec.configure do |config|

0 commit comments

Comments
 (0)