rom-sql 1.3.5 → 2.0.0.beta1
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 +13 -7
- data/Gemfile +7 -5
- data/lib/rom/plugins/relation/sql/auto_restrictions.rb +11 -17
- data/lib/rom/sql.rb +3 -2
- data/lib/rom/sql/associations.rb +5 -0
- data/lib/rom/sql/associations/core.rb +20 -0
- data/lib/rom/sql/associations/many_to_many.rb +83 -0
- data/lib/rom/sql/associations/many_to_one.rb +55 -0
- data/lib/rom/sql/associations/one_to_many.rb +31 -0
- data/lib/rom/sql/{association → associations}/one_to_one.rb +3 -2
- data/lib/rom/sql/{association → associations}/one_to_one_through.rb +3 -2
- data/lib/rom/sql/associations/self_ref.rb +39 -0
- data/lib/rom/sql/attribute.rb +44 -54
- data/lib/rom/sql/errors.rb +2 -0
- data/lib/rom/sql/extensions/mysql.rb +1 -1
- data/lib/rom/sql/extensions/mysql/attributes_inferrer.rb +10 -0
- data/lib/rom/sql/extensions/postgres.rb +1 -1
- data/lib/rom/sql/extensions/postgres/{inferrer.rb → attributes_inferrer.rb} +4 -4
- data/lib/rom/sql/extensions/postgres/types.rb +9 -19
- data/lib/rom/sql/extensions/sqlite.rb +1 -1
- data/lib/rom/sql/extensions/sqlite/{inferrer.rb → attributes_inferrer.rb} +2 -2
- data/lib/rom/sql/gateway.rb +29 -30
- data/lib/rom/sql/index.rb +13 -0
- data/lib/rom/sql/migration.rb +10 -0
- data/lib/rom/sql/migration/inline_runner.rb +86 -0
- data/lib/rom/sql/migration/migrator.rb +17 -0
- data/lib/rom/sql/migration/schema_diff.rb +177 -0
- data/lib/rom/sql/plugin/associates.rb +11 -45
- data/lib/rom/sql/plugin/pagination.rb +4 -4
- data/lib/rom/sql/relation.rb +22 -42
- data/lib/rom/sql/relation/reading.rb +3 -3
- data/lib/rom/sql/schema.rb +14 -21
- data/lib/rom/sql/schema/associations_dsl.rb +7 -6
- data/lib/rom/sql/schema/attributes_inferrer.rb +164 -0
- data/lib/rom/sql/schema/inferrer.rb +40 -141
- data/lib/rom/sql/type_extensions.rb +44 -0
- data/lib/rom/sql/version.rb +1 -1
- data/lib/rom/sql/wrap.rb +25 -0
- data/rom-sql.gemspec +2 -2
- data/spec/integration/{association → associations}/many_to_many/custom_fks_spec.rb +4 -2
- data/spec/integration/{association → associations}/many_to_many/from_view_spec.rb +2 -2
- data/spec/integration/{association → associations}/many_to_many_spec.rb +25 -30
- data/spec/integration/{association → associations}/many_to_one/custom_fks_spec.rb +5 -3
- data/spec/integration/{association → associations}/many_to_one/from_view_spec.rb +3 -3
- data/spec/integration/{association → associations}/many_to_one/self_ref_spec.rb +2 -2
- data/spec/integration/{association → associations}/many_to_one_spec.rb +20 -38
- data/spec/integration/{association → associations}/one_to_many/custom_fks_spec.rb +4 -2
- data/spec/integration/{association → associations}/one_to_many/from_view_spec.rb +2 -2
- data/spec/integration/{association → associations}/one_to_many/self_ref_spec.rb +2 -2
- data/spec/integration/{association → associations}/one_to_many_spec.rb +24 -11
- data/spec/integration/{association → associations}/one_to_one_spec.rb +13 -9
- data/spec/integration/{association → associations}/one_to_one_through_spec.rb +15 -11
- data/spec/integration/auto_migrations/errors_spec.rb +31 -0
- data/spec/integration/auto_migrations/indexes_spec.rb +109 -0
- data/spec/integration/auto_migrations/managing_columns_spec.rb +156 -0
- data/spec/integration/auto_migrations/postgres/column_types_spec.rb +63 -0
- data/spec/integration/commands/create_spec.rb +2 -4
- data/spec/integration/commands/delete_spec.rb +2 -2
- data/spec/integration/commands/update_spec.rb +2 -0
- data/spec/integration/graph_spec.rb +9 -3
- data/spec/integration/plugins/associates_spec.rb +16 -55
- data/spec/integration/plugins/auto_restrictions_spec.rb +0 -11
- data/spec/integration/relation_schema_spec.rb +49 -25
- data/spec/integration/schema/inferrer/postgres_spec.rb +1 -1
- data/spec/integration/schema/inferrer_spec.rb +7 -18
- data/spec/integration/setup_spec.rb +4 -0
- data/spec/integration/{plugins/auto_wrap_spec.rb → wrap_spec.rb} +13 -36
- data/spec/shared/accounts.rb +4 -0
- data/spec/shared/database_setup.rb +2 -1
- data/spec/shared/notes.rb +2 -0
- data/spec/shared/posts.rb +2 -0
- data/spec/shared/puppies.rb +2 -0
- data/spec/shared/relations.rb +2 -2
- data/spec/shared/users.rb +2 -0
- data/spec/shared/users_and_tasks.rb +4 -0
- data/spec/spec_helper.rb +3 -6
- data/spec/support/helpers.rb +11 -8
- data/spec/support/test_configuration.rb +16 -0
- data/spec/unit/plugin/associates_spec.rb +5 -10
- data/spec/unit/plugin/pagination_spec.rb +9 -9
- data/spec/unit/plugin/timestamp_spec.rb +9 -9
- data/spec/unit/relation/dataset_spec.rb +7 -5
- data/spec/unit/relation/inner_join_spec.rb +2 -15
- data/spec/unit/relation/primary_key_spec.rb +1 -1
- data/spec/unit/schema_spec.rb +6 -4
- metadata +65 -70
- data/lib/rom/plugins/relation/sql/auto_combine.rb +0 -71
- data/lib/rom/plugins/relation/sql/auto_wrap.rb +0 -62
- data/lib/rom/sql/association.rb +0 -103
- data/lib/rom/sql/association/many_to_many.rb +0 -119
- data/lib/rom/sql/association/many_to_one.rb +0 -73
- data/lib/rom/sql/association/name.rb +0 -78
- data/lib/rom/sql/association/one_to_many.rb +0 -60
- data/lib/rom/sql/extensions/mysql/inferrer.rb +0 -10
- data/lib/rom/sql/qualified_attribute.rb +0 -53
- data/lib/rom/sql/schema/dsl.rb +0 -75
- data/spec/unit/association/many_to_many_spec.rb +0 -89
- data/spec/unit/association/many_to_one_spec.rb +0 -81
- data/spec/unit/association/name_spec.rb +0 -68
- data/spec/unit/association/one_to_many_spec.rb +0 -82
- data/spec/unit/association/one_to_one_spec.rb +0 -83
- data/spec/unit/association/one_to_one_through_spec.rb +0 -69
data/lib/rom/sql/errors.rb
CHANGED
|
@@ -12,6 +12,8 @@ module ROM
|
|
|
12
12
|
CheckConstraintError = Class.new(ConstraintError)
|
|
13
13
|
UnknownDBTypeError = Class.new(StandardError)
|
|
14
14
|
MissingPrimaryKeyError = Class.new(StandardError)
|
|
15
|
+
MigrationError = Class.new(StandardError)
|
|
16
|
+
UnsupportedConversion = Class.new(MigrationError)
|
|
15
17
|
|
|
16
18
|
ERROR_MAP = {
|
|
17
19
|
Sequel::DatabaseError => DatabaseError,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
require 'rom/sql/extensions/mysql/
|
|
1
|
+
require 'rom/sql/extensions/mysql/attributes_inferrer'
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
require 'set'
|
|
2
|
-
require 'rom/sql/schema/
|
|
2
|
+
require 'rom/sql/schema/attributes_inferrer'
|
|
3
3
|
require 'rom/sql/extensions/postgres/types'
|
|
4
4
|
|
|
5
5
|
module ROM
|
|
6
6
|
module SQL
|
|
7
7
|
class Schema
|
|
8
|
-
class PostgresInferrer <
|
|
8
|
+
class PostgresInferrer < AttributesInferrer[:postgres]
|
|
9
9
|
defines :db_numeric_types, :db_type_mapping, :db_array_type_matcher
|
|
10
10
|
|
|
11
11
|
db_numeric_types %w(
|
|
@@ -34,7 +34,7 @@ module ROM
|
|
|
34
34
|
'path' => Types::PG::PathT
|
|
35
35
|
).freeze
|
|
36
36
|
|
|
37
|
-
db_array_type_matcher
|
|
37
|
+
db_array_type_matcher Sequel::Postgres::PGArray::EMPTY_BRACKET
|
|
38
38
|
|
|
39
39
|
private
|
|
40
40
|
|
|
@@ -50,7 +50,7 @@ module ROM
|
|
|
50
50
|
|
|
51
51
|
def map_type(ruby_type, db_type, enum_values: nil, **_)
|
|
52
52
|
if db_type.end_with?(self.class.db_array_type_matcher)
|
|
53
|
-
Types::PG::Array(db_type
|
|
53
|
+
Types::PG::Array(db_type)
|
|
54
54
|
elsif enum_values
|
|
55
55
|
Types::String.enum(*enum_values)
|
|
56
56
|
else
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
require 'dry
|
|
1
|
+
require 'dry/types'
|
|
2
2
|
require 'sequel'
|
|
3
3
|
require 'ipaddr'
|
|
4
4
|
|
|
5
|
+
require 'rom/sql/type_extensions'
|
|
6
|
+
|
|
5
7
|
Sequel.extension(*%i(pg_array pg_array_ops pg_json pg_json_ops pg_hstore))
|
|
6
8
|
|
|
7
9
|
module ROM
|
|
@@ -16,16 +18,8 @@ module ROM
|
|
|
16
18
|
|
|
17
19
|
Array = Types.Definition(Sequel::Postgres::PGArray)
|
|
18
20
|
|
|
19
|
-
@array_types = ::Hash.new do |hash, type|
|
|
20
|
-
name = "#{ type }[]"
|
|
21
|
-
array_type = Array.constructor(-> (v) { Sequel.pg_array(v, type) }).
|
|
22
|
-
meta(type: name, db_type: name, database: 'postgres')
|
|
23
|
-
Attribute::TypeExtensions.register(array_type) { include ArrayMethods }
|
|
24
|
-
hash[type] = array_type
|
|
25
|
-
end
|
|
26
|
-
|
|
27
21
|
def self.Array(db_type)
|
|
28
|
-
|
|
22
|
+
Array.constructor(-> (v) { Sequel.pg_array(v, db_type) }).meta(type: db_type)
|
|
29
23
|
end
|
|
30
24
|
|
|
31
25
|
# @!parse
|
|
@@ -117,7 +111,7 @@ module ROM
|
|
|
117
111
|
# #
|
|
118
112
|
# # @api public
|
|
119
113
|
# end
|
|
120
|
-
|
|
114
|
+
TypeExtensions.register(Array.constructor -> { }) do
|
|
121
115
|
def contain(type, expr, other)
|
|
122
116
|
Attribute[Types::Bool].meta(sql_expr: expr.pg_array.contains(type[other]))
|
|
123
117
|
end
|
|
@@ -155,10 +149,6 @@ module ROM
|
|
|
155
149
|
end
|
|
156
150
|
end
|
|
157
151
|
|
|
158
|
-
Attribute::TypeExtensions.register(Array.constructor -> { }) do
|
|
159
|
-
include ArrayMethods
|
|
160
|
-
end
|
|
161
|
-
|
|
162
152
|
# JSON
|
|
163
153
|
|
|
164
154
|
JSONArray = Types.Constructor(Sequel::Postgres::JSONArray, &Sequel.method(:pg_json))
|
|
@@ -167,7 +157,7 @@ module ROM
|
|
|
167
157
|
|
|
168
158
|
JSONOp = Types.Constructor(Sequel::Postgres::JSONOp, &Sequel.method(:pg_json))
|
|
169
159
|
|
|
170
|
-
JSON =
|
|
160
|
+
JSON = JSONArray | JSONHash | JSONOp
|
|
171
161
|
|
|
172
162
|
# JSONB
|
|
173
163
|
|
|
@@ -177,7 +167,7 @@ module ROM
|
|
|
177
167
|
|
|
178
168
|
JSONBOp = Types.Constructor(Sequel::Postgres::JSONBOp, &Sequel.method(:pg_jsonb))
|
|
179
169
|
|
|
180
|
-
JSONB =
|
|
170
|
+
JSONB = JSONBArray | JSONBHash | JSONBOp
|
|
181
171
|
|
|
182
172
|
# @!parse
|
|
183
173
|
# class ROM::SQL::Attribute
|
|
@@ -343,11 +333,11 @@ module ROM
|
|
|
343
333
|
end
|
|
344
334
|
end
|
|
345
335
|
|
|
346
|
-
|
|
336
|
+
TypeExtensions.register(JSON) do
|
|
347
337
|
include JSONMethods[JSON, :pg_json.to_proc]
|
|
348
338
|
end
|
|
349
339
|
|
|
350
|
-
|
|
340
|
+
TypeExtensions.register(JSONB) do
|
|
351
341
|
include JSONMethods[JSONB, :pg_jsonb.to_proc]
|
|
352
342
|
|
|
353
343
|
def contain(type, expr, value)
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
require 'rom/sql/extensions/sqlite/types'
|
|
2
|
-
require 'rom/sql/extensions/sqlite/
|
|
2
|
+
require 'rom/sql/extensions/sqlite/attributes_inferrer'
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
require 'rom/sql/schema/
|
|
1
|
+
require 'rom/sql/schema/attributes_inferrer'
|
|
2
2
|
|
|
3
3
|
module ROM
|
|
4
4
|
module SQL
|
|
5
5
|
class Schema
|
|
6
|
-
class SqliteInferrer <
|
|
6
|
+
class SqliteInferrer < AttributesInferrer[:sqlite]
|
|
7
7
|
NO_TYPE = EMPTY_STRING
|
|
8
8
|
|
|
9
9
|
def map_type(_, db_type, **_kw)
|
data/lib/rom/sql/gateway.rb
CHANGED
|
@@ -31,6 +31,23 @@ module ROM
|
|
|
31
31
|
# @return [Hash] Options used for connection
|
|
32
32
|
attr_reader :options
|
|
33
33
|
|
|
34
|
+
subscribe('configuration.commands.class.before_build') do |event|
|
|
35
|
+
klass = event[:command]
|
|
36
|
+
dataset = event[:dataset]
|
|
37
|
+
type = dataset.db.database_type
|
|
38
|
+
|
|
39
|
+
if type == :postgres
|
|
40
|
+
ext =
|
|
41
|
+
if klass < Commands::Create
|
|
42
|
+
Commands::Postgres::Create
|
|
43
|
+
elsif klass < Commands::Update
|
|
44
|
+
Commands::Postgres::Update
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
klass.send(:include, ext) if ext
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
34
51
|
# Initialize an SQL gateway
|
|
35
52
|
#
|
|
36
53
|
# Gateways are typically initialized via ROM::Configuration object, gateway constructor
|
|
@@ -153,31 +170,6 @@ module ROM
|
|
|
153
170
|
schema.include?(name)
|
|
154
171
|
end
|
|
155
172
|
|
|
156
|
-
# Extend the command class with database-specific behavior
|
|
157
|
-
#
|
|
158
|
-
# @param [Class] klass Command class
|
|
159
|
-
# @param [Sequel::Dataset] dataset A dataset that will be used
|
|
160
|
-
#
|
|
161
|
-
# Note: Currently, only postgres is supported.
|
|
162
|
-
#
|
|
163
|
-
# @api public
|
|
164
|
-
def extend_command_class(klass, dataset)
|
|
165
|
-
type = dataset.db.database_type
|
|
166
|
-
|
|
167
|
-
if type == :postgres
|
|
168
|
-
ext =
|
|
169
|
-
if klass < Commands::Create
|
|
170
|
-
Commands::Postgres::Create
|
|
171
|
-
elsif klass < Commands::Update
|
|
172
|
-
Commands::Postgres::Update
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
klass.send(:include, ext) if ext
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
klass
|
|
179
|
-
end
|
|
180
|
-
|
|
181
173
|
# Create a table using the configured connection
|
|
182
174
|
#
|
|
183
175
|
# @api public
|
|
@@ -201,6 +193,15 @@ module ROM
|
|
|
201
193
|
@schema ||= connection.tables
|
|
202
194
|
end
|
|
203
195
|
|
|
196
|
+
# Underlying database type
|
|
197
|
+
#
|
|
198
|
+
# @return [Symbol]
|
|
199
|
+
#
|
|
200
|
+
# @api public
|
|
201
|
+
def database_type
|
|
202
|
+
@database_type ||= connection.database_type.to_sym
|
|
203
|
+
end
|
|
204
|
+
|
|
204
205
|
private
|
|
205
206
|
|
|
206
207
|
# Connect to database or reuse established connection instance
|
|
@@ -221,13 +222,11 @@ module ROM
|
|
|
221
222
|
#
|
|
222
223
|
# @api private
|
|
223
224
|
def load_extensions(exts)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if ROM::SQL.available_extension?(db_type)
|
|
227
|
-
ROM::SQL.load_extensions(db_type)
|
|
225
|
+
if ROM::SQL.available_extension?(database_type)
|
|
226
|
+
ROM::SQL.load_extensions(database_type)
|
|
228
227
|
end
|
|
229
228
|
|
|
230
|
-
extensions = (CONNECTION_EXTENSIONS.fetch(
|
|
229
|
+
extensions = (CONNECTION_EXTENSIONS.fetch(database_type, EMPTY_ARRAY) + exts).uniq
|
|
231
230
|
connection.extension(*extensions)
|
|
232
231
|
|
|
233
232
|
# this will be default in Sequel 5.0.0 and since we don't rely
|
data/lib/rom/sql/migration.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'rom/sql/migration/migrator'
|
|
2
|
+
require 'rom/sql/migration/schema_diff'
|
|
2
3
|
|
|
3
4
|
module ROM
|
|
4
5
|
module SQL
|
|
@@ -135,6 +136,15 @@ module ROM
|
|
|
135
136
|
migrator.run(options)
|
|
136
137
|
}
|
|
137
138
|
end
|
|
139
|
+
|
|
140
|
+
# @api public
|
|
141
|
+
def auto_migrate!(conf)
|
|
142
|
+
schemas = conf.relation_classes(self).map do |klass|
|
|
143
|
+
klass.schema || klass.schema_proc.call.finalize_attributes!(gateway: self)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
migrator.auto_migrate!(self, schemas)
|
|
147
|
+
end
|
|
138
148
|
end
|
|
139
149
|
end
|
|
140
150
|
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module ROM
|
|
2
|
+
module SQL
|
|
3
|
+
module Migration
|
|
4
|
+
class Migrator
|
|
5
|
+
# @api private
|
|
6
|
+
class InlineRunner
|
|
7
|
+
attr_reader :gateway
|
|
8
|
+
|
|
9
|
+
def initialize(gateway)
|
|
10
|
+
@gateway = gateway
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call(changes)
|
|
14
|
+
changes.each do |diff|
|
|
15
|
+
apply(diff)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def apply(diff)
|
|
20
|
+
case diff
|
|
21
|
+
when SchemaDiff::TableCreated
|
|
22
|
+
create_table(diff)
|
|
23
|
+
when SchemaDiff::TableAltered
|
|
24
|
+
alter_table(diff)
|
|
25
|
+
else
|
|
26
|
+
raise NotImplementedError
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def create_table(diff)
|
|
31
|
+
gateway.create_table(diff.table_name) do
|
|
32
|
+
diff.attributes.each do |attribute|
|
|
33
|
+
if attribute.primary_key?
|
|
34
|
+
primary_key attribute.name
|
|
35
|
+
else
|
|
36
|
+
column attribute.name, attribute.type, null: attribute.null?
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
diff.indexes.each do |idx|
|
|
41
|
+
index idx.attribute
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def alter_table(diff)
|
|
47
|
+
gateway.connection.alter_table(diff.table_name) do
|
|
48
|
+
diff.attribute_changes.each do |attribute|
|
|
49
|
+
case attribute
|
|
50
|
+
when SchemaDiff::AttributeAdded
|
|
51
|
+
add_column attribute.name, attribute.type, null: attribute.null?
|
|
52
|
+
when SchemaDiff::AttributeRemoved
|
|
53
|
+
drop_column attribute.name
|
|
54
|
+
when SchemaDiff::AttributeChanged
|
|
55
|
+
if attribute.type_changed?
|
|
56
|
+
from, to = attribute.to_a.map(&attribute.method(:unwrap))
|
|
57
|
+
raise UnsupportedConversion.new(
|
|
58
|
+
"Don't know how to convert #{ from.inspect } to #{ to.inspect }"
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
if attribute.nullability_changed?
|
|
63
|
+
if attribute.null?
|
|
64
|
+
set_column_allow_null attribute.name
|
|
65
|
+
else
|
|
66
|
+
set_column_not_null attribute.name
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
diff.index_changes.each do |index|
|
|
73
|
+
case index
|
|
74
|
+
when SchemaDiff::IndexAdded
|
|
75
|
+
add_index index.attribute
|
|
76
|
+
when SchemaDiff::IndexRemoved
|
|
77
|
+
drop_index index.attribute
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -2,6 +2,8 @@ require 'pathname'
|
|
|
2
2
|
|
|
3
3
|
require 'rom/types'
|
|
4
4
|
require 'rom/initializer'
|
|
5
|
+
require 'rom/sql/migration'
|
|
6
|
+
require 'rom/sql/migration/inline_runner'
|
|
5
7
|
|
|
6
8
|
module ROM
|
|
7
9
|
module SQL
|
|
@@ -53,6 +55,21 @@ module ROM
|
|
|
53
55
|
def migration_file_content
|
|
54
56
|
File.read(Pathname(__FILE__).dirname.join('template.rb').realpath)
|
|
55
57
|
end
|
|
58
|
+
|
|
59
|
+
# @api private
|
|
60
|
+
def diff(gateway, inferrer, target)
|
|
61
|
+
empty = SQL::Schema.define(target.name)
|
|
62
|
+
current = target.with(inferrer.(empty, gateway))
|
|
63
|
+
|
|
64
|
+
SchemaDiff.new.(current, target)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def auto_migrate!(gateway, schemas)
|
|
68
|
+
runner = InlineRunner.new(gateway)
|
|
69
|
+
inferrer = ROM::SQL::Schema::Inferrer.new.suppress_errors
|
|
70
|
+
changes = schemas.map { |schema| diff(gateway, inferrer, schema) }.reject(&:empty?)
|
|
71
|
+
runner.(changes)
|
|
72
|
+
end
|
|
56
73
|
end
|
|
57
74
|
end
|
|
58
75
|
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
module ROM
|
|
2
|
+
module SQL
|
|
3
|
+
module Migration
|
|
4
|
+
class SchemaDiff
|
|
5
|
+
class TableDiff
|
|
6
|
+
attr_reader :current_schema, :target_schema
|
|
7
|
+
|
|
8
|
+
def initialize(current_schema: nil, target_schema: nil)
|
|
9
|
+
@current_schema = current_schema
|
|
10
|
+
@target_schema = target_schema
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def empty?
|
|
14
|
+
false
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def table_name
|
|
18
|
+
target_schema.name.dataset
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class Empty < TableDiff
|
|
23
|
+
def empty?
|
|
24
|
+
true
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class TableCreated < TableDiff
|
|
29
|
+
alias_method :schema, :target_schema
|
|
30
|
+
attr_reader :attributes, :indexes
|
|
31
|
+
|
|
32
|
+
def initialize(attributes:, indexes: EMPTY_ARRAY, **rest)
|
|
33
|
+
super(rest)
|
|
34
|
+
|
|
35
|
+
@attributes = attributes
|
|
36
|
+
@indexes = indexes
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class TableAltered < TableDiff
|
|
41
|
+
attr_reader :attribute_changes, :index_changes
|
|
42
|
+
|
|
43
|
+
def initialize(attribute_changes: EMPTY_ARRAY, index_changes: EMPTY_ARRAY, **rest)
|
|
44
|
+
super(rest)
|
|
45
|
+
|
|
46
|
+
@attribute_changes = attribute_changes
|
|
47
|
+
@index_changes = index_changes
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class AttributeDiff
|
|
52
|
+
attr_reader :attr
|
|
53
|
+
|
|
54
|
+
def initialize(attr)
|
|
55
|
+
@attr = attr
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def name
|
|
59
|
+
attr.name
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def null?
|
|
63
|
+
attr.optional?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def primary_key?
|
|
67
|
+
attr.primary_key?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def unwrap(type)
|
|
71
|
+
type.optional? ? SQL::Attribute[type.right].meta(type.meta) : type
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class AttributeAdded < AttributeDiff
|
|
76
|
+
def type
|
|
77
|
+
unwrap(attr).primitive
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
class AttributeRemoved < AttributeDiff
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
class AttributeChanged < AttributeDiff
|
|
85
|
+
attr_reader :current
|
|
86
|
+
alias_method :target, :attr
|
|
87
|
+
|
|
88
|
+
def initialize(current, target)
|
|
89
|
+
super(target)
|
|
90
|
+
|
|
91
|
+
@current = current
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def to_a
|
|
95
|
+
[current, target]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def nullability_changed?
|
|
99
|
+
current.optional? ^ target.optional?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def type_changed?
|
|
103
|
+
unwrap(current).meta(index: Set.new) != unwrap(target).meta(index: Set.new)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
class IndexDiff
|
|
108
|
+
attr_reader :index
|
|
109
|
+
|
|
110
|
+
def initialize(index)
|
|
111
|
+
@index = index
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def attribute
|
|
115
|
+
index.attributes[0].name
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class IndexAdded < IndexDiff
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
class IndexRemoved < IndexDiff
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def call(current, target)
|
|
126
|
+
if current.empty?
|
|
127
|
+
TableCreated.new(
|
|
128
|
+
target_schema: target,
|
|
129
|
+
attributes: target.map { |attr| AttributeAdded.new(attr) },
|
|
130
|
+
indexes: target.indexes.map { |idx| IndexAdded.new(idx) }
|
|
131
|
+
)
|
|
132
|
+
else
|
|
133
|
+
attribute_changes = compare_attributes(current.to_h, target.to_h)
|
|
134
|
+
index_changes = compare_indexes(current, target)
|
|
135
|
+
|
|
136
|
+
if attribute_changes.empty? && index_changes.empty?
|
|
137
|
+
Empty.new(current_schema: current, target_schema: target)
|
|
138
|
+
else
|
|
139
|
+
TableAltered.new(
|
|
140
|
+
current_schema: current,
|
|
141
|
+
target_schema: target,
|
|
142
|
+
attribute_changes: attribute_changes,
|
|
143
|
+
index_changes: index_changes
|
|
144
|
+
)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def compare_attributes(current, target)
|
|
150
|
+
changed_attributes = target.select { |name, attr|
|
|
151
|
+
current.key?(name) && current[name] != attr
|
|
152
|
+
}.map { |name, target_attr|
|
|
153
|
+
[name, [current[name], target_attr]]
|
|
154
|
+
}.to_h
|
|
155
|
+
added_attributes = target.select { |name, _| !current.key?(name) }
|
|
156
|
+
removed_attributes = current.select { |name, _| !target.key?(name) }
|
|
157
|
+
|
|
158
|
+
removed_attributes.values.map { |attr| AttributeRemoved.new(attr) } +
|
|
159
|
+
added_attributes.values.map { |attr| AttributeAdded.new(attr) } +
|
|
160
|
+
changed_attributes.values.map { |attrs| AttributeChanged.new(*attrs) }
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def compare_indexes(current, target)
|
|
164
|
+
added_indexes = target.indexes.reject { |idx|
|
|
165
|
+
current.indexes.any? { |curr_idx| curr_idx.attributes == idx.attributes }
|
|
166
|
+
}
|
|
167
|
+
removed_indexes = current.indexes.select { |idx|
|
|
168
|
+
target.indexes.none? { |tgt_idx| idx.attributes == tgt_idx }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
removed_indexes.map { |idx| IndexRemoved.new(idx) } +
|
|
172
|
+
added_indexes.map { |idx| IndexAdded.new(idx) }
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|