rom-sql 1.3.5 → 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|