hanami-cli 2.1.1 → 2.2.0.beta2
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/.github/workflows/ci.yml +29 -13
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +28 -0
- data/Gemfile +6 -1
- data/README.md +13 -7
- data/docker-compose.yml +14 -0
- data/hanami-cli.gemspec +2 -2
- data/lib/hanami/cli/command.rb +2 -2
- data/lib/hanami/cli/commands/app/command.rb +2 -16
- data/lib/hanami/cli/commands/app/db/command.rb +148 -0
- data/lib/hanami/cli/commands/app/db/create.rb +21 -11
- data/lib/hanami/cli/commands/app/db/drop.rb +21 -10
- data/lib/hanami/cli/commands/app/db/migrate.rb +50 -12
- data/lib/hanami/cli/commands/app/db/prepare.rb +68 -0
- data/lib/hanami/cli/commands/app/db/seed.rb +22 -21
- data/lib/hanami/cli/commands/app/db/structure/dump.rb +32 -7
- data/lib/hanami/cli/commands/app/db/structure/load.rb +54 -0
- data/lib/hanami/cli/commands/app/db/utils/database.rb +100 -75
- data/lib/hanami/cli/commands/app/db/utils/mysql.rb +59 -6
- data/lib/hanami/cli/commands/app/db/utils/postgres.rb +42 -21
- data/lib/hanami/cli/commands/app/db/utils/sqlite.rb +58 -10
- data/lib/hanami/cli/commands/app/db/version.rb +14 -8
- data/lib/hanami/cli/commands/app/generate/action.rb +4 -3
- data/lib/hanami/cli/commands/app/generate/command.rb +60 -0
- data/lib/hanami/cli/commands/app/generate/component.rb +49 -0
- data/lib/hanami/cli/commands/app/generate/migration.rb +47 -0
- data/lib/hanami/cli/commands/app/generate/operation.rb +26 -0
- data/lib/hanami/cli/commands/app/generate/part.rb +1 -1
- data/lib/hanami/cli/commands/app/generate/relation.rb +29 -0
- data/lib/hanami/cli/commands/app/generate/repo.rb +42 -0
- data/lib/hanami/cli/commands/app/generate/slice.rb +20 -3
- data/lib/hanami/cli/commands/app/generate/struct.rb +27 -0
- data/lib/hanami/cli/commands/app/install.rb +1 -1
- data/lib/hanami/cli/commands/app/middleware.rb +1 -1
- data/lib/hanami/cli/commands/app/server.rb +2 -2
- data/lib/hanami/cli/commands/app.rb +21 -2
- data/lib/hanami/cli/commands/gem/new.rb +78 -14
- data/lib/hanami/cli/errors.rb +28 -0
- data/lib/hanami/cli/files.rb +22 -0
- data/lib/hanami/cli/generators/app/action_context.rb +5 -13
- data/lib/hanami/cli/generators/app/component/component.erb +8 -0
- data/lib/hanami/cli/generators/app/component/slice_component.erb +8 -0
- data/lib/hanami/cli/generators/app/component.rb +61 -0
- data/lib/hanami/cli/generators/app/component_context.rb +82 -0
- data/lib/hanami/cli/generators/app/migration.rb +66 -0
- data/lib/hanami/cli/generators/app/operation.rb +49 -0
- data/lib/hanami/cli/generators/app/part_context.rb +5 -21
- data/lib/hanami/cli/generators/app/relation.rb +45 -0
- data/lib/hanami/cli/generators/app/repo.rb +41 -0
- data/lib/hanami/cli/generators/app/ruby_file_writer.rb +151 -0
- data/lib/hanami/cli/generators/app/slice/{entities.erb → operation.erb} +1 -3
- data/lib/hanami/cli/generators/app/slice/relation.erb +8 -0
- data/lib/hanami/cli/generators/app/slice/{slice.erb → repo.erb} +3 -1
- data/lib/hanami/cli/generators/app/slice/struct.erb +8 -0
- data/lib/hanami/cli/generators/app/slice.rb +14 -6
- data/lib/hanami/cli/generators/app/slice_context.rb +9 -2
- data/lib/hanami/cli/generators/app/struct.rb +40 -0
- data/lib/hanami/cli/generators/app/view_context.rb +4 -16
- data/lib/hanami/cli/generators/constants.rb +39 -0
- data/lib/hanami/cli/generators/context.rb +48 -0
- data/lib/hanami/cli/generators/gem/app/action.erb +3 -0
- data/lib/hanami/cli/generators/gem/app/env.erb +4 -0
- data/lib/hanami/cli/generators/gem/app/gemfile.erb +11 -0
- data/lib/hanami/cli/generators/gem/app/gitignore.erb +4 -1
- data/lib/hanami/cli/generators/gem/app/operation.erb +13 -0
- data/lib/hanami/cli/generators/gem/app/relation.erb +10 -0
- data/lib/hanami/cli/generators/gem/app/repo.erb +10 -0
- data/lib/hanami/cli/generators/gem/app/struct.erb +10 -0
- data/lib/hanami/cli/generators/gem/app.rb +19 -0
- data/lib/hanami/cli/ruby_file_generator.rb +123 -0
- data/lib/hanami/cli/version.rb +1 -1
- metadata +39 -17
- data/lib/hanami/cli/commands/app/db/create_migration.rb +0 -32
- data/lib/hanami/cli/commands/app/db/reset.rb +0 -28
- data/lib/hanami/cli/commands/app/db/rollback.rb +0 -81
- data/lib/hanami/cli/commands/app/db/sample_data.rb +0 -42
- data/lib/hanami/cli/commands/app/db/setup.rb +0 -26
- data/lib/hanami/cli/commands/app/db/utils/database_config.rb +0 -60
- data/lib/hanami/cli/generators/app/slice/repository.erb +0 -10
@@ -1,38 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "../../app/command"
|
4
|
-
require_relative "structure/dump"
|
5
|
-
|
6
3
|
module Hanami
|
7
4
|
module CLI
|
8
5
|
module Commands
|
9
6
|
module App
|
10
7
|
module DB
|
11
8
|
# @api private
|
12
|
-
class Seed <
|
13
|
-
|
9
|
+
class Seed < DB::Command
|
10
|
+
SEEDS_PATH = "config/db/seeds.rb"
|
11
|
+
private_constant :SEEDS_PATH
|
14
12
|
|
15
13
|
desc "Load seed data"
|
16
14
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
26
|
-
end
|
15
|
+
def call(app: false, slice: nil, **)
|
16
|
+
# We use `databases` below to discover the databases throughout the app and slices. It
|
17
|
+
# yields every database, so in a slice with multiple gateways, we'll see multiple
|
18
|
+
# databases for the slice.
|
19
|
+
#
|
20
|
+
# Since `db seed` is intended to run over whole slices only (not per-gateway), keep
|
21
|
+
# track of the seeded slices here, so we can avoid seeding a slice multiple times.
|
22
|
+
seeded_slices = []
|
27
23
|
|
28
|
-
|
24
|
+
databases(app: app, slice: slice).each do |database|
|
25
|
+
next if seeded_slices.include?(database.slice)
|
29
26
|
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
seeds_path = database.slice.root.join(SEEDS_PATH)
|
28
|
+
next unless seeds_path.file?
|
29
|
+
|
30
|
+
relative_seeds_path = seeds_path.relative_path_from(database.slice.app.root)
|
31
|
+
measure "seed data loaded from #{relative_seeds_path}" do
|
32
|
+
load seeds_path.to_s
|
33
|
+
end
|
33
34
|
|
34
|
-
|
35
|
-
|
35
|
+
seeded_slices << database.slice
|
36
|
+
end
|
36
37
|
end
|
37
38
|
end
|
38
39
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "../../../app/command"
|
4
|
-
|
5
3
|
module Hanami
|
6
4
|
module CLI
|
7
5
|
module Commands
|
@@ -10,13 +8,40 @@ module Hanami
|
|
10
8
|
# @api private
|
11
9
|
module Structure
|
12
10
|
# @api private
|
13
|
-
class Dump <
|
14
|
-
desc "Dumps database structure to db/structure.sql file"
|
11
|
+
class Dump < DB::Command
|
12
|
+
desc "Dumps database structure to config/db/structure.sql file"
|
13
|
+
|
14
|
+
option :gateway, required: false, desc: "Use database for gateway"
|
15
15
|
|
16
16
|
# @api private
|
17
|
-
def call(
|
18
|
-
|
19
|
-
|
17
|
+
def call(app: false, slice: nil, gateway: nil, command_exit: method(:exit), **)
|
18
|
+
exit_codes = []
|
19
|
+
|
20
|
+
databases(app: app, slice: slice, gateway: gateway).each do |database|
|
21
|
+
relative_structure_path = database.structure_file
|
22
|
+
.relative_path_from(database.slice.app.root)
|
23
|
+
|
24
|
+
measure("#{database.name} structure dumped to #{relative_structure_path}") do
|
25
|
+
catch :dump_failed do
|
26
|
+
result = database.exec_dump_command
|
27
|
+
exit_codes << result.exit_code if result.respond_to?(:exit_code)
|
28
|
+
|
29
|
+
unless result.successful?
|
30
|
+
out.puts result.err
|
31
|
+
throw :dump_failed, false
|
32
|
+
end
|
33
|
+
|
34
|
+
File.open(database.structure_file, "a") do |f|
|
35
|
+
f.puts "#{database.schema_migrations_sql_dump}\n"
|
36
|
+
end
|
37
|
+
|
38
|
+
true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
exit_codes.each do |code|
|
44
|
+
break command_exit.(code) if code > 0
|
20
45
|
end
|
21
46
|
end
|
22
47
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module CLI
|
5
|
+
module Commands
|
6
|
+
module App
|
7
|
+
module DB
|
8
|
+
# @api private
|
9
|
+
module Structure
|
10
|
+
# @api private
|
11
|
+
class Load < DB::Command
|
12
|
+
STRUCTURE_PATH = File.join("config", "db", "structure.sql").freeze
|
13
|
+
private_constant :STRUCTURE_PATH
|
14
|
+
|
15
|
+
desc "Loads database from config/db/structure.sql file"
|
16
|
+
|
17
|
+
option :gateway, required: false, desc: "Use database for gateway"
|
18
|
+
|
19
|
+
# @api private
|
20
|
+
def call(app: false, slice: nil, gateway: nil, command_exit: method(:exit), **)
|
21
|
+
exit_codes = []
|
22
|
+
|
23
|
+
databases(app: app, slice: slice, gateway: gateway).each do |database|
|
24
|
+
next unless database.structure_file.exist?
|
25
|
+
|
26
|
+
relative_structure_path = database.structure_file
|
27
|
+
.relative_path_from(database.slice.app.root)
|
28
|
+
|
29
|
+
measure("#{database.name} structure loaded from #{relative_structure_path}") do
|
30
|
+
catch :load_failed do
|
31
|
+
result = database.exec_load_command
|
32
|
+
exit_codes << result.exit_code if result.respond_to?(:exit_code)
|
33
|
+
|
34
|
+
unless result.successful?
|
35
|
+
out.puts result.err
|
36
|
+
throw :load_failed, false
|
37
|
+
end
|
38
|
+
|
39
|
+
true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
exit_codes.each do |code|
|
45
|
+
break command_exit.(code) if code > 0
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require "uri"
|
4
4
|
|
5
5
|
module Hanami
|
6
6
|
module CLI
|
@@ -9,15 +9,11 @@ module Hanami
|
|
9
9
|
module DB
|
10
10
|
module Utils
|
11
11
|
# @api private
|
12
|
+
# @since 2.2.0
|
12
13
|
class Database
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
# @api private
|
17
|
-
attr_reader :config
|
18
|
-
|
19
|
-
# @api private
|
20
|
-
SCHEME_MAP = {
|
14
|
+
DATABASE_CLASS_RESOLVER = Hash.new { |_, key|
|
15
|
+
raise "#{key} is not a supported db scheme"
|
16
|
+
}.update(
|
21
17
|
"sqlite" => -> {
|
22
18
|
require_relative("sqlite")
|
23
19
|
Sqlite
|
@@ -30,88 +26,83 @@ module Hanami
|
|
30
26
|
require_relative("postgres")
|
31
27
|
Postgres
|
32
28
|
},
|
33
|
-
"
|
29
|
+
"mysql2" => -> {
|
34
30
|
require_relative("mysql")
|
35
31
|
Mysql
|
36
32
|
}
|
37
|
-
|
38
|
-
|
39
|
-
# @api private
|
40
|
-
def self.[](app)
|
41
|
-
database_url =
|
42
|
-
if app.key?(:settings) && app[:settings].respond_to?(:database_url)
|
43
|
-
app[:settings].database_url
|
44
|
-
else
|
45
|
-
ENV.fetch("DATABASE_URL")
|
46
|
-
end
|
33
|
+
).freeze
|
47
34
|
|
48
|
-
|
35
|
+
def self.database_class(database_url)
|
36
|
+
database_scheme = URI(database_url).scheme
|
37
|
+
DATABASE_CLASS_RESOLVER[database_scheme].call
|
38
|
+
end
|
49
39
|
|
50
|
-
|
51
|
-
|
52
|
-
|
40
|
+
def self.from_slice(slice:, system_call:)
|
41
|
+
provider = slice.container.providers[:db]
|
42
|
+
raise "No :db provider for #{slice}" unless provider
|
53
43
|
|
54
|
-
|
44
|
+
provider.source.database_urls.map { |(gateway_name, database_url)|
|
45
|
+
database = database_class(database_url).new(
|
46
|
+
slice: slice,
|
47
|
+
gateway_name: gateway_name,
|
48
|
+
system_call: system_call
|
49
|
+
)
|
55
50
|
|
56
|
-
|
51
|
+
[gateway_name, database]
|
52
|
+
}.to_h
|
57
53
|
end
|
58
54
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
55
|
+
attr_reader :slice
|
56
|
+
attr_reader :gateway_name
|
57
|
+
|
58
|
+
attr_reader :system_call
|
59
|
+
|
60
|
+
def initialize(slice:, gateway_name:, system_call:)
|
61
|
+
@slice = slice
|
62
|
+
@gateway_name = gateway_name
|
63
|
+
@system_call = system_call
|
63
64
|
end
|
64
65
|
|
65
|
-
|
66
|
-
|
67
|
-
raise Hanami::CLI::NotImplementedError
|
66
|
+
def name
|
67
|
+
database_uri.path.sub(%r{^/}, "")
|
68
68
|
end
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
raise Hanami::CLI::NotImplementedError
|
70
|
+
def database_url
|
71
|
+
slice.container.providers[:db].source.database_urls.fetch(gateway_name)
|
73
72
|
end
|
74
73
|
|
75
|
-
|
76
|
-
|
77
|
-
raise Hanami::CLI::NotImplementedError
|
74
|
+
def database_uri
|
75
|
+
@database_uri ||= URI(database_url)
|
78
76
|
end
|
79
77
|
|
80
|
-
|
81
|
-
|
82
|
-
raise Hanami::CLI::NotImplementedError
|
78
|
+
def gateway
|
79
|
+
slice["db.config"].gateways[gateway_name]
|
83
80
|
end
|
84
81
|
|
85
|
-
|
86
|
-
|
87
|
-
app.root
|
82
|
+
def connection
|
83
|
+
gateway.connection
|
88
84
|
end
|
89
85
|
|
90
|
-
|
91
|
-
|
92
|
-
@rom_config ||=
|
93
|
-
begin
|
94
|
-
app.prepare(:persistence)
|
95
|
-
app.container["persistence.config"]
|
96
|
-
end
|
86
|
+
def exec_create_command
|
87
|
+
raise Hanami::CLI::NotImplementedError
|
97
88
|
end
|
98
89
|
|
99
|
-
|
100
|
-
|
101
|
-
config.db_name
|
90
|
+
def exec_drop_command
|
91
|
+
raise Hanami::CLI::NotImplementedError
|
102
92
|
end
|
103
93
|
|
104
|
-
|
105
|
-
|
106
|
-
rom_config.gateways[:default]
|
94
|
+
def exists?
|
95
|
+
raise Hanami::CLI::NotImplementedError
|
107
96
|
end
|
108
97
|
|
109
|
-
|
110
|
-
|
111
|
-
|
98
|
+
def exec_dump_command
|
99
|
+
raise Hanami::CLI::NotImplementedError
|
100
|
+
end
|
101
|
+
|
102
|
+
def exec_load_command
|
103
|
+
raise Hanami::CLI::NotImplementedError
|
112
104
|
end
|
113
105
|
|
114
|
-
# @api private
|
115
106
|
def run_migrations(**options)
|
116
107
|
require "rom/sql"
|
117
108
|
ROM::SQL.with_gateway(gateway) do
|
@@ -119,24 +110,19 @@ module Hanami
|
|
119
110
|
end
|
120
111
|
end
|
121
112
|
|
122
|
-
# @api private
|
123
113
|
def migrator
|
124
|
-
@migrator ||=
|
125
|
-
|
126
|
-
require "rom/sql"
|
127
|
-
ROM::SQL::Migration::Migrator.new(connection, path: File.join(root_path, "db/migrate"))
|
128
|
-
end
|
129
|
-
end
|
114
|
+
@migrator ||= begin
|
115
|
+
slice.prepare :db
|
130
116
|
|
131
|
-
|
132
|
-
|
133
|
-
|
117
|
+
require "rom/sql"
|
118
|
+
ROM::SQL::Migration::Migrator.new(connection, path: migrations_path)
|
119
|
+
end
|
134
120
|
end
|
135
121
|
|
136
|
-
private
|
137
|
-
|
138
122
|
def sequel_migrator
|
139
123
|
@sequel_migrator ||= begin
|
124
|
+
slice.prepare :db
|
125
|
+
|
140
126
|
require "sequel"
|
141
127
|
Sequel.extension :migration
|
142
128
|
|
@@ -147,8 +133,47 @@ module Hanami
|
|
147
133
|
end
|
148
134
|
end
|
149
135
|
|
136
|
+
def applied_migrations
|
137
|
+
sequel_migrator.applied_migrations
|
138
|
+
end
|
139
|
+
|
140
|
+
def db_config_path
|
141
|
+
slice.root.join("config", "db")
|
142
|
+
end
|
143
|
+
|
144
|
+
def db_config_dir?
|
145
|
+
db_config_path.directory?
|
146
|
+
end
|
147
|
+
|
150
148
|
def migrations_path
|
151
|
-
|
149
|
+
if gateway_name == :default
|
150
|
+
db_config_path.join("migrate")
|
151
|
+
else
|
152
|
+
db_config_path.join("#{gateway_name}_migrate")
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def migrations_dir?
|
157
|
+
migrations_path.directory?
|
158
|
+
end
|
159
|
+
|
160
|
+
def structure_file
|
161
|
+
path = slice.root.join("config", "db")
|
162
|
+
|
163
|
+
if gateway_name == :default
|
164
|
+
path.join("structure.sql")
|
165
|
+
else
|
166
|
+
path.join("#{gateway_name}_structure.sql")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def schema_migrations_sql_dump
|
171
|
+
return unless migrations_dir?
|
172
|
+
|
173
|
+
sql = +"INSERT INTO schema_migrations (filename) VALUES\n"
|
174
|
+
sql << applied_migrations.map { |v| "('#{v}')" }.join(",\n")
|
175
|
+
sql << ";"
|
176
|
+
sql
|
152
177
|
end
|
153
178
|
end
|
154
179
|
end
|
@@ -11,18 +11,71 @@ module Hanami
|
|
11
11
|
# @api private
|
12
12
|
class Mysql < Database
|
13
13
|
# @api private
|
14
|
-
def
|
15
|
-
|
14
|
+
def exec_create_command
|
15
|
+
return true if exists?
|
16
|
+
|
17
|
+
exec_cli("mysql", %(-e "CREATE DATABASE #{escaped_name}"))
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
# @since 2.2.0
|
22
|
+
def exec_drop_command
|
23
|
+
return true unless exists?
|
24
|
+
|
25
|
+
exec_cli("mysql", %(-e "DROP DATABASE #{escaped_name}"))
|
26
|
+
end
|
27
|
+
|
28
|
+
# @api private
|
29
|
+
# @since 2.2.0
|
30
|
+
def exists?
|
31
|
+
result = exec_cli("mysql", %(-e "SHOW DATABASES LIKE '#{name}'" --batch))
|
32
|
+
|
33
|
+
result.successful? && result.out != ""
|
16
34
|
end
|
17
35
|
|
18
36
|
# @api private
|
19
|
-
|
20
|
-
|
37
|
+
# @since 2.2.0
|
38
|
+
def exec_dump_command
|
39
|
+
exec_cli(
|
40
|
+
"mysqldump",
|
41
|
+
"--no-data --routines --skip-comments --result-file=#{structure_file} #{escaped_name}"
|
42
|
+
)
|
21
43
|
end
|
22
44
|
|
23
45
|
# @api private
|
24
|
-
|
25
|
-
|
46
|
+
# @since 2.2.0
|
47
|
+
def exec_load_command
|
48
|
+
exec_cli(
|
49
|
+
"mysql",
|
50
|
+
%(--execute "SET FOREIGN_KEY_CHECKS = 0; SOURCE #{structure_file}; SET FOREIGN_KEY_CHECKS = 1" --database #{escaped_name})
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def escaped_name
|
57
|
+
Shellwords.escape(name)
|
58
|
+
end
|
59
|
+
|
60
|
+
def exec_cli(cli_name, cli_args)
|
61
|
+
system_call.call(
|
62
|
+
"#{cli_name} #{cli_options} #{cli_args}",
|
63
|
+
env: cli_env_vars
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
def cli_options
|
68
|
+
[].tap { |opts|
|
69
|
+
opts << "--host=#{Shellwords.escape(database_uri.host)}" if database_uri.host
|
70
|
+
opts << "--port=#{Shellwords.escape(database_uri.port)}" if database_uri.port
|
71
|
+
opts << "--user=#{Shellwords.escape(database_uri.user)}" if database_uri.user
|
72
|
+
}.join(" ")
|
73
|
+
end
|
74
|
+
|
75
|
+
def cli_env_vars
|
76
|
+
@cli_env_vars ||= {}.tap do |vars|
|
77
|
+
vars["MYSQL_PWD"] = database_uri.password.to_s if database_uri.password
|
78
|
+
end
|
26
79
|
end
|
27
80
|
end
|
28
81
|
end
|
@@ -11,50 +11,71 @@ module Hanami
|
|
11
11
|
module DB
|
12
12
|
module Utils
|
13
13
|
# @api private
|
14
|
+
# @since 2.2.0
|
14
15
|
class Postgres < Database
|
15
16
|
# @api private
|
16
|
-
|
17
|
-
|
17
|
+
# @since 2.2.0
|
18
|
+
def exec_create_command
|
19
|
+
return true if exists?
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
system(cli_env_vars, "createdb #{escaped_name}")
|
21
|
+
system_call.call("createdb #{escaped_name}", env: cli_env_vars)
|
22
22
|
end
|
23
23
|
|
24
24
|
# @api private
|
25
|
-
|
26
|
-
|
25
|
+
# @since 2.2.0
|
26
|
+
def exec_drop_command
|
27
|
+
return true unless exists?
|
28
|
+
|
29
|
+
system_call.call("dropdb #{escaped_name}", env: cli_env_vars)
|
27
30
|
end
|
28
31
|
|
29
32
|
# @api private
|
30
|
-
|
31
|
-
|
33
|
+
# @since 2.2.0
|
34
|
+
def exists?
|
35
|
+
result = system_call.call("psql -t -A -c '\\list #{escaped_name}'", env: cli_env_vars)
|
36
|
+
result.successful? && result.out.include?("#{name}|") # start_with?
|
32
37
|
end
|
33
38
|
|
34
39
|
# @api private
|
35
|
-
|
36
|
-
|
40
|
+
# @since 2.2.0
|
41
|
+
def exec_dump_command
|
42
|
+
system_call.call(
|
43
|
+
"pg_dump --schema-only --no-privileges --no-owner --file #{structure_file} #{escaped_name}",
|
44
|
+
env: cli_env_vars
|
45
|
+
)
|
37
46
|
end
|
38
47
|
|
39
48
|
# @api private
|
49
|
+
# @since 2.2.0
|
50
|
+
def exec_load_command
|
51
|
+
system_call.call(
|
52
|
+
"psql --set ON_ERROR_STOP=1 --quiet --no-psqlrc --output #{File::NULL} --file #{structure_file} #{escaped_name}",
|
53
|
+
env: cli_env_vars
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
def schema_migrations_sql_dump
|
58
|
+
search_path = slice["db.gateway"].connection
|
59
|
+
.fetch("SHOW search_path").to_a.first
|
60
|
+
.fetch(:search_path)
|
61
|
+
|
62
|
+
+"SET search_path TO #{search_path};\n\n" << super
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
40
67
|
def escaped_name
|
41
68
|
Shellwords.escape(name)
|
42
69
|
end
|
43
70
|
|
44
|
-
# @api private
|
45
71
|
def cli_env_vars
|
46
72
|
@cli_env_vars ||= {}.tap do |vars|
|
47
|
-
vars["PGHOST"] =
|
48
|
-
vars["PGPORT"] =
|
49
|
-
vars["PGUSER"] =
|
50
|
-
vars["PGPASSWORD"] =
|
73
|
+
vars["PGHOST"] = database_uri.host.to_s if database_uri.host
|
74
|
+
vars["PGPORT"] = database_uri.port.to_s if database_uri.port
|
75
|
+
vars["PGUSER"] = database_uri.user.to_s if database_uri.user
|
76
|
+
vars["PGPASSWORD"] = database_uri.password.to_s if database_uri.password
|
51
77
|
end
|
52
78
|
end
|
53
|
-
|
54
|
-
# @api private
|
55
|
-
def dump_file
|
56
|
-
"#{root_path}/db/structure.sql"
|
57
|
-
end
|
58
79
|
end
|
59
80
|
end
|
60
81
|
end
|
@@ -9,32 +9,80 @@ module Hanami
|
|
9
9
|
module DB
|
10
10
|
module Utils
|
11
11
|
# @api private
|
12
|
+
# @since 2.2.0
|
12
13
|
class Sqlite < Database
|
13
14
|
# @api private
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
# @since 2.2.0
|
16
|
+
Failure = Struct.new(:err) do
|
17
|
+
def successful?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def exit_code
|
22
|
+
1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
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;"))
|
17
34
|
end
|
18
35
|
|
19
36
|
# @api private
|
20
|
-
|
21
|
-
|
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
|
+
|
22
46
|
true
|
23
47
|
end
|
24
48
|
|
25
49
|
# @api private
|
26
|
-
|
27
|
-
|
50
|
+
# @since 2.2.0
|
51
|
+
def exists?
|
52
|
+
File.exist?(file_path)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @api private
|
56
|
+
# @since 2.2.0
|
57
|
+
def exec_dump_command
|
58
|
+
system_call.call(%(sqlite3 #{file_path} ".schema --indent --nosys" > #{structure_file}))
|
28
59
|
end
|
29
60
|
|
30
61
|
# @api private
|
31
|
-
|
32
|
-
|
62
|
+
# @since 2.2.0
|
63
|
+
def exec_load_command
|
64
|
+
system_call.call("sqlite3 #{file_path} < #{structure_file}")
|
33
65
|
end
|
34
66
|
|
35
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}"
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
36
78
|
def file_path
|
37
|
-
@file_path ||=
|
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
|
38
86
|
end
|
39
87
|
end
|
40
88
|
end
|