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
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'rom/sql/associations'
|
|
2
|
+
|
|
1
3
|
module ROM
|
|
2
4
|
module SQL
|
|
3
5
|
module Plugin
|
|
@@ -5,42 +7,18 @@ module ROM
|
|
|
5
7
|
#
|
|
6
8
|
# @api private
|
|
7
9
|
module Associates
|
|
8
|
-
class MissingJoinKeysError < StandardError
|
|
9
|
-
ERROR_TEMPLATE = ':%{command} command for :%{relation} relation ' \
|
|
10
|
-
'is missing join keys configuration for :%{name} association'
|
|
11
|
-
|
|
12
|
-
def initialize(command, assoc_name)
|
|
13
|
-
super(ERROR_TEMPLATE % tokens(command, assoc_name))
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def tokens(command, assoc_name)
|
|
17
|
-
{ command: command.register_as,
|
|
18
|
-
relation: command.relation,
|
|
19
|
-
name: assoc_name }
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
10
|
class AssociateOptions
|
|
24
11
|
attr_reader :name, :assoc, :opts
|
|
25
12
|
|
|
26
13
|
def initialize(name, relation, opts)
|
|
27
14
|
@name = name
|
|
28
|
-
@
|
|
29
|
-
|
|
30
|
-
relation.associations.try(name) do |assoc|
|
|
31
|
-
@assoc = assoc
|
|
32
|
-
@opts.update(assoc: assoc, keys: assoc.join_keys(relation.__registry__))
|
|
33
|
-
end
|
|
34
|
-
|
|
15
|
+
@assoc = relation.associations[name]
|
|
16
|
+
@opts = { assoc: assoc, keys: assoc.join_keys }
|
|
35
17
|
@opts.update(parent: opts[:parent]) if opts[:parent]
|
|
36
18
|
end
|
|
37
19
|
|
|
38
20
|
def after?
|
|
39
|
-
assoc.is_a?(
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def ensure_valid(command)
|
|
43
|
-
raise MissingJoinKeysError.new(command, name) unless opts[:keys]
|
|
21
|
+
assoc.is_a?(SQL::Associations::ManyToMany)
|
|
44
22
|
end
|
|
45
23
|
|
|
46
24
|
def to_hash
|
|
@@ -53,6 +31,7 @@ module ROM
|
|
|
53
31
|
klass.class_eval do
|
|
54
32
|
extend ClassMethods
|
|
55
33
|
include InstanceMethods
|
|
34
|
+
|
|
56
35
|
defines :associations
|
|
57
36
|
|
|
58
37
|
associations Hash.new
|
|
@@ -77,8 +56,6 @@ module ROM
|
|
|
77
56
|
AssociateOptions.new(name, relation, opts)
|
|
78
57
|
}.compact
|
|
79
58
|
|
|
80
|
-
associate_options.each { |opts| opts.ensure_valid(self) }
|
|
81
|
-
|
|
82
59
|
before_hooks = associate_options.reject(&:after?).map(&:to_hash)
|
|
83
60
|
after_hooks = associate_options.select(&:after?).map(&:to_hash)
|
|
84
61
|
|
|
@@ -133,18 +110,12 @@ module ROM
|
|
|
133
110
|
|
|
134
111
|
output_tuples =
|
|
135
112
|
case assoc
|
|
136
|
-
when
|
|
137
|
-
fk, pk = keys
|
|
138
|
-
|
|
139
|
-
with_input_tuples(tuples).map { |tuple|
|
|
140
|
-
tuple.merge(fk => parent.fetch(pk))
|
|
141
|
-
}
|
|
142
|
-
when Association::ManyToMany
|
|
113
|
+
when SQL::Associations::ManyToMany
|
|
143
114
|
result_type = tuples.is_a?(Array) ? :many : :one
|
|
144
115
|
|
|
145
|
-
assoc.persist(
|
|
116
|
+
assoc.persist(tuples, parent)
|
|
146
117
|
|
|
147
|
-
pk, fk = assoc.parent_combine_keys
|
|
118
|
+
pk, fk = assoc.parent_combine_keys
|
|
148
119
|
|
|
149
120
|
case parent
|
|
150
121
|
when Array
|
|
@@ -154,9 +125,9 @@ module ROM
|
|
|
154
125
|
else
|
|
155
126
|
tuples.map { |tuple| Hash(tuple).update(fk => parent[pk]) }
|
|
156
127
|
end
|
|
157
|
-
|
|
128
|
+
else
|
|
158
129
|
with_input_tuples(tuples).map { |tuple|
|
|
159
|
-
assoc.associate(
|
|
130
|
+
assoc.associate(tuple, parent)
|
|
160
131
|
}
|
|
161
132
|
end
|
|
162
133
|
|
|
@@ -171,11 +142,6 @@ module ROM
|
|
|
171
142
|
associations: associations.merge(name => opts)
|
|
172
143
|
)
|
|
173
144
|
end
|
|
174
|
-
|
|
175
|
-
# @api private
|
|
176
|
-
def __registry__
|
|
177
|
-
relation.__registry__
|
|
178
|
-
end
|
|
179
145
|
end
|
|
180
146
|
end
|
|
181
147
|
end
|
|
@@ -59,9 +59,9 @@ module ROM
|
|
|
59
59
|
# Paginate a relation
|
|
60
60
|
#
|
|
61
61
|
# @example
|
|
62
|
-
# rom.
|
|
63
|
-
# rom.
|
|
64
|
-
# rom.
|
|
62
|
+
# rom.relations[:users].class.per_page(10)
|
|
63
|
+
# rom.relations[:users].page(1)
|
|
64
|
+
# rom.relations[:users].pager # => info about pagination
|
|
65
65
|
#
|
|
66
66
|
# @return [Relation]
|
|
67
67
|
#
|
|
@@ -74,7 +74,7 @@ module ROM
|
|
|
74
74
|
# Set limit for pagination
|
|
75
75
|
#
|
|
76
76
|
# @example
|
|
77
|
-
# rom.
|
|
77
|
+
# rom.relations[:users].page(2).per_page(10)
|
|
78
78
|
#
|
|
79
79
|
# @api public
|
|
80
80
|
def per_page(num)
|
data/lib/rom/sql/relation.rb
CHANGED
|
@@ -1,70 +1,50 @@
|
|
|
1
1
|
require 'rom/sql/types'
|
|
2
2
|
require 'rom/sql/schema'
|
|
3
|
+
require 'rom/sql/attribute'
|
|
4
|
+
require 'rom/sql/wrap'
|
|
3
5
|
|
|
4
6
|
require 'rom/sql/relation/reading'
|
|
5
7
|
require 'rom/sql/relation/writing'
|
|
6
8
|
require 'rom/sql/relation/sequel_api'
|
|
7
9
|
|
|
8
|
-
require 'rom/plugins/relation/key_inference'
|
|
9
|
-
require 'rom/plugins/relation/sql/auto_combine'
|
|
10
|
-
require 'rom/plugins/relation/sql/auto_wrap'
|
|
11
|
-
|
|
12
10
|
module ROM
|
|
13
11
|
module SQL
|
|
14
12
|
# Sequel-specific relation extensions
|
|
15
13
|
#
|
|
16
14
|
# @api public
|
|
17
15
|
class Relation < ROM::Relation
|
|
18
|
-
include SQL
|
|
19
|
-
|
|
20
16
|
adapter :sql
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
use :auto_combine
|
|
24
|
-
use :auto_wrap
|
|
25
|
-
|
|
18
|
+
include SQL
|
|
26
19
|
include Writing
|
|
27
20
|
include Reading
|
|
28
21
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
# Set default dataset for a relation sub-class
|
|
32
|
-
#
|
|
33
|
-
# @api private
|
|
34
|
-
def self.inherited(klass)
|
|
35
|
-
super
|
|
22
|
+
extend Notifications::Listener
|
|
36
23
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
inferrer_for_db.new.call(name, gateway)
|
|
42
|
-
rescue Sequel::Error => e
|
|
43
|
-
inferrer_for_db.on_error(klass, e)
|
|
44
|
-
ROM::Schema::DEFAULT_INFERRER.()
|
|
45
|
-
end
|
|
46
|
-
end
|
|
24
|
+
schema_class SQL::Schema
|
|
25
|
+
schema_attr_class SQL::Attribute
|
|
26
|
+
schema_inferrer ROM::SQL::Schema::Inferrer.new.freeze
|
|
27
|
+
wrap_class SQL::Wrap
|
|
47
28
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
schema = klass.schema
|
|
29
|
+
subscribe('configuration.relations.schema.set', adapter: :sql) do |event|
|
|
30
|
+
schema = event[:schema]
|
|
31
|
+
relation = event[:relation]
|
|
52
32
|
|
|
53
|
-
|
|
33
|
+
relation.dataset do
|
|
34
|
+
table = opts[:from].first
|
|
54
35
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
select(*columns).order(*klass.primary_key_columns(db, table))
|
|
60
|
-
end
|
|
61
|
-
else
|
|
62
|
-
self
|
|
63
|
-
end
|
|
36
|
+
if db.table_exists?(table)
|
|
37
|
+
select(*schema.map(&:qualified)).order(*schema.project(*schema.primary_key_names).qualified)
|
|
38
|
+
else
|
|
39
|
+
self
|
|
64
40
|
end
|
|
65
41
|
end
|
|
66
42
|
end
|
|
67
43
|
|
|
44
|
+
subscribe('configuration.relations.dataset.allocated', adapter: :sql) do |event|
|
|
45
|
+
event[:relation].define_default_views!
|
|
46
|
+
end
|
|
47
|
+
|
|
68
48
|
# @api private
|
|
69
49
|
def self.define_default_views!
|
|
70
50
|
if schema.primary_key.size > 1
|
|
@@ -130,7 +110,7 @@ module ROM
|
|
|
130
110
|
#
|
|
131
111
|
# @api public
|
|
132
112
|
def assoc(name)
|
|
133
|
-
associations[name].(
|
|
113
|
+
associations[name].()
|
|
134
114
|
end
|
|
135
115
|
|
|
136
116
|
# Return raw column names
|
|
@@ -902,17 +902,17 @@ module ROM
|
|
|
902
902
|
#
|
|
903
903
|
# @api private
|
|
904
904
|
def __join__(type, other, join_cond = EMPTY_HASH, opts = EMPTY_HASH, &block)
|
|
905
|
-
if other.is_a?(Symbol) || other.is_a?(
|
|
905
|
+
if other.is_a?(Symbol) || other.is_a?(ROM::Relation::Name)
|
|
906
906
|
if join_cond.empty?
|
|
907
907
|
assoc = associations[other]
|
|
908
|
-
assoc.join(
|
|
908
|
+
assoc.join(type, self)
|
|
909
909
|
else
|
|
910
910
|
new(dataset.__send__(type, other.to_sym, join_cond, opts, &block))
|
|
911
911
|
end
|
|
912
912
|
elsif other.is_a?(Sequel::SQL::AliasedExpression)
|
|
913
913
|
new(dataset.__send__(type, other, join_cond, opts, &block))
|
|
914
914
|
elsif other.respond_to?(:name) && other.name.is_a?(Relation::Name)
|
|
915
|
-
associations[other.name.
|
|
915
|
+
associations[other.name.key].join(type, self, other)
|
|
916
916
|
else
|
|
917
917
|
raise ArgumentError, "+other+ must be either a symbol or a relation, #{other.class} given"
|
|
918
918
|
end
|
data/lib/rom/sql/schema.rb
CHANGED
|
@@ -1,26 +1,18 @@
|
|
|
1
1
|
require 'rom/schema'
|
|
2
|
+
|
|
2
3
|
require 'rom/sql/order_dsl'
|
|
3
4
|
require 'rom/sql/group_dsl'
|
|
4
5
|
require 'rom/sql/projection_dsl'
|
|
5
6
|
require 'rom/sql/restriction_dsl'
|
|
7
|
+
require 'rom/sql/index'
|
|
8
|
+
require 'rom/sql/schema/inferrer'
|
|
6
9
|
|
|
7
10
|
module ROM
|
|
8
11
|
module SQL
|
|
9
12
|
class Schema < ROM::Schema
|
|
10
|
-
# @!attribute [r]
|
|
11
|
-
# @return [
|
|
12
|
-
|
|
13
|
-
attr_reader :primary_key_name
|
|
14
|
-
|
|
15
|
-
# @!attribute [r] primary_key_names
|
|
16
|
-
# @return [Array<Symbol>] A list of all pk names
|
|
17
|
-
attr_reader :primary_key_names
|
|
18
|
-
|
|
19
|
-
# @api private
|
|
20
|
-
def initialize(*)
|
|
21
|
-
super
|
|
22
|
-
initialize_primary_key_names
|
|
23
|
-
end
|
|
13
|
+
# @!attribute [r] attributes
|
|
14
|
+
# @return [Array<Index>] Array with schema indexes
|
|
15
|
+
option :indexes, default: -> { EMPTY_ARRAY }
|
|
24
16
|
|
|
25
17
|
# @api public
|
|
26
18
|
def restriction(&block)
|
|
@@ -105,21 +97,22 @@ module ROM
|
|
|
105
97
|
end
|
|
106
98
|
|
|
107
99
|
# @api private
|
|
108
|
-
def
|
|
100
|
+
def finalize_attributes!(options = EMPTY_HASH)
|
|
109
101
|
super do
|
|
110
102
|
initialize_primary_key_names
|
|
111
103
|
end
|
|
112
104
|
end
|
|
113
105
|
|
|
114
106
|
# @api private
|
|
115
|
-
def
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
107
|
+
def finalize_associations!(relations:)
|
|
108
|
+
super do
|
|
109
|
+
associations.map do |definition|
|
|
110
|
+
SQL::Associations.const_get(definition.type).new(definition, relations)
|
|
111
|
+
end
|
|
119
112
|
end
|
|
120
113
|
end
|
|
114
|
+
|
|
115
|
+
memoize :qualified, :canonical, :joined, :project_pk
|
|
121
116
|
end
|
|
122
117
|
end
|
|
123
118
|
end
|
|
124
|
-
|
|
125
|
-
require 'rom/sql/schema/dsl'
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'dry/core/inflector'
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
require 'rom/associations/definitions'
|
|
3
4
|
|
|
4
5
|
module ROM
|
|
5
6
|
module SQL
|
|
@@ -52,7 +53,7 @@ module ROM
|
|
|
52
53
|
if options[:through]
|
|
53
54
|
many_to_many(target, options)
|
|
54
55
|
else
|
|
55
|
-
add(
|
|
56
|
+
add(::ROM::Associations::Definitions::OneToMany.new(source, target, options))
|
|
56
57
|
end
|
|
57
58
|
end
|
|
58
59
|
alias_method :has_many, :one_to_many
|
|
@@ -77,7 +78,7 @@ module ROM
|
|
|
77
78
|
if options[:through]
|
|
78
79
|
one_to_one_through(target, options)
|
|
79
80
|
else
|
|
80
|
-
add(
|
|
81
|
+
add(::ROM::Associations::Definitions::OneToOne.new(source, target, options))
|
|
81
82
|
end
|
|
82
83
|
end
|
|
83
84
|
|
|
@@ -90,7 +91,7 @@ module ROM
|
|
|
90
91
|
#
|
|
91
92
|
# @api public
|
|
92
93
|
def one_to_one_through(target, options = {})
|
|
93
|
-
add(
|
|
94
|
+
add(::ROM::Associations::Definitions::OneToOneThrough.new(source, target, options))
|
|
94
95
|
end
|
|
95
96
|
|
|
96
97
|
# Establish a many-to-many association
|
|
@@ -107,7 +108,7 @@ module ROM
|
|
|
107
108
|
#
|
|
108
109
|
# @api public
|
|
109
110
|
def many_to_many(target, options = {})
|
|
110
|
-
add(
|
|
111
|
+
add(::ROM::Associations::Definitions::ManyToMany.new(source, target, options))
|
|
111
112
|
end
|
|
112
113
|
|
|
113
114
|
# Establish a many-to-one association
|
|
@@ -124,7 +125,7 @@ module ROM
|
|
|
124
125
|
#
|
|
125
126
|
# @api public
|
|
126
127
|
def many_to_one(target, options = {})
|
|
127
|
-
add(
|
|
128
|
+
add(::ROM::Associations::Definitions::ManyToOne.new(source, target, options))
|
|
128
129
|
end
|
|
129
130
|
|
|
130
131
|
# Shortcut for many_to_one which sets alias automatically
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
require 'dry/core/class_attributes'
|
|
2
|
+
|
|
3
|
+
module ROM
|
|
4
|
+
module SQL
|
|
5
|
+
class Schema < ROM::Schema
|
|
6
|
+
# @api private
|
|
7
|
+
class AttributesInferrer
|
|
8
|
+
extend Dry::Core::ClassAttributes
|
|
9
|
+
extend Initializer
|
|
10
|
+
|
|
11
|
+
defines :ruby_type_mapping, :numeric_pk_type, :db_type, :registry
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def inherited(klass)
|
|
15
|
+
super
|
|
16
|
+
|
|
17
|
+
registry[klass.db_type] = klass.new.freeze unless klass.name.nil?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def [](type)
|
|
21
|
+
Class.new(self) { db_type(type) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def get(db_type)
|
|
25
|
+
registry[db_type]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
CONSTRAINT_DB_TYPE = 'add_constraint'.freeze
|
|
30
|
+
DECIMAL_REGEX = /(?:decimal|numeric)\((\d+)(?:,\s*(\d+))?\)/.freeze
|
|
31
|
+
|
|
32
|
+
registry Hash.new(new.freeze)
|
|
33
|
+
|
|
34
|
+
ruby_type_mapping(
|
|
35
|
+
integer: Types::Int,
|
|
36
|
+
string: Types::String,
|
|
37
|
+
time: Types::Time,
|
|
38
|
+
date: Types::Date,
|
|
39
|
+
datetime: Types::Time,
|
|
40
|
+
boolean: Types::Bool,
|
|
41
|
+
decimal: Types::Decimal,
|
|
42
|
+
float: Types::Float,
|
|
43
|
+
blob: Types::Blob
|
|
44
|
+
).freeze
|
|
45
|
+
|
|
46
|
+
numeric_pk_type Types::Serial
|
|
47
|
+
|
|
48
|
+
option :attr_class, optional: true
|
|
49
|
+
|
|
50
|
+
# @api private
|
|
51
|
+
def call(schema, gateway)
|
|
52
|
+
dataset = schema.name.dataset
|
|
53
|
+
|
|
54
|
+
columns = filter_columns(gateway.connection.schema(dataset))
|
|
55
|
+
fks = fks_for(gateway, dataset)
|
|
56
|
+
|
|
57
|
+
inferred = columns.map do |(name, definition)|
|
|
58
|
+
type = build_type(**definition, foreign_key: fks[name])
|
|
59
|
+
|
|
60
|
+
attr_class.new(type.meta(name: name, source: schema.name)) if type
|
|
61
|
+
end.compact
|
|
62
|
+
|
|
63
|
+
missing = columns.map(&:first) - inferred.map { |attr| attr.meta[:name] }
|
|
64
|
+
|
|
65
|
+
[inferred, missing]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @api private
|
|
69
|
+
def with(new_options)
|
|
70
|
+
self.class.new(options.merge(new_options))
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# @api private
|
|
75
|
+
def filter_columns(schema)
|
|
76
|
+
schema.reject { |(_, definition)| definition[:db_type] == CONSTRAINT_DB_TYPE }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @api private
|
|
80
|
+
def build_type(primary_key:, db_type:, type:, allow_null:, foreign_key:, **rest)
|
|
81
|
+
if primary_key
|
|
82
|
+
map_pk_type(type, db_type)
|
|
83
|
+
else
|
|
84
|
+
mapped_type = map_type(type, db_type, rest)
|
|
85
|
+
|
|
86
|
+
if mapped_type
|
|
87
|
+
read_type = mapped_type.meta[:read]
|
|
88
|
+
mapped_type = mapped_type.optional if allow_null
|
|
89
|
+
mapped_type = mapped_type.meta(foreign_key: true, target: foreign_key) if foreign_key
|
|
90
|
+
|
|
91
|
+
if read_type && allow_null
|
|
92
|
+
mapped_type.meta(read: read_type.optional)
|
|
93
|
+
elsif read_type
|
|
94
|
+
mapped_type.meta(read: read_type)
|
|
95
|
+
else
|
|
96
|
+
mapped_type
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @api private
|
|
103
|
+
def map_pk_type(_ruby_type, _db_type)
|
|
104
|
+
self.class.numeric_pk_type.meta(primary_key: true)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @api private
|
|
108
|
+
def map_type(ruby_type, db_type, **kw)
|
|
109
|
+
type = self.class.ruby_type_mapping[ruby_type]
|
|
110
|
+
|
|
111
|
+
if db_type.is_a?(String) && db_type.include?('numeric') || db_type.include?('decimal')
|
|
112
|
+
map_decimal_type(db_type)
|
|
113
|
+
elsif db_type.is_a?(String) && db_type.include?('char') && kw[:max_length]
|
|
114
|
+
type.meta(limit: kw[:max_length])
|
|
115
|
+
else
|
|
116
|
+
type
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @api private
|
|
121
|
+
def fks_for(gateway, dataset)
|
|
122
|
+
gateway.connection.foreign_key_list(dataset).each_with_object({}) do |definition, fks|
|
|
123
|
+
column, fk = build_fk(definition)
|
|
124
|
+
|
|
125
|
+
fks[column] = fk if fk
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @api private
|
|
130
|
+
def column_indexes(indexes, column)
|
|
131
|
+
indexes.each_with_object(Set.new) do |(name, idx), set|
|
|
132
|
+
set << name if idx[:columns][0] == column
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# @api private
|
|
137
|
+
def build_fk(columns: , table: , **rest)
|
|
138
|
+
if columns.size == 1
|
|
139
|
+
[columns[0], table]
|
|
140
|
+
else
|
|
141
|
+
# We don't have support for multicolumn foreign keys
|
|
142
|
+
columns[0]
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# @api private
|
|
147
|
+
def map_decimal_type(type)
|
|
148
|
+
precision = DECIMAL_REGEX.match(type)
|
|
149
|
+
|
|
150
|
+
if precision
|
|
151
|
+
prcsn, scale = precision[1..2].map(&:to_i)
|
|
152
|
+
|
|
153
|
+
self.class.ruby_type_mapping[:decimal].meta(
|
|
154
|
+
precision: prcsn,
|
|
155
|
+
scale: scale
|
|
156
|
+
)
|
|
157
|
+
else
|
|
158
|
+
self.class.ruby_type_mapping[:decimal]
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|