rom-sql 2.0.0.beta2 → 2.0.0.beta3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +66 -0
- data/lib/rom/plugins/relation/sql/postgres/explain.rb +54 -0
- data/lib/rom/sql.rb +1 -1
- data/lib/rom/sql/attribute.rb +17 -18
- data/lib/rom/sql/errors.rb +3 -0
- data/lib/rom/sql/extensions/mysql.rb +1 -1
- data/lib/rom/sql/extensions/mysql/type_builder.rb +28 -0
- data/lib/rom/sql/extensions/postgres.rb +3 -1
- data/lib/rom/sql/extensions/postgres/commands.rb +30 -13
- data/lib/rom/sql/extensions/postgres/{attributes_inferrer.rb → type_builder.rb} +24 -28
- data/lib/rom/sql/extensions/postgres/type_serializer.rb +39 -0
- data/lib/rom/sql/extensions/postgres/types.rb +24 -477
- data/lib/rom/sql/extensions/postgres/types/array.rb +163 -0
- data/lib/rom/sql/extensions/postgres/types/geometric.rb +135 -0
- data/lib/rom/sql/extensions/postgres/types/json.rb +235 -0
- data/lib/rom/sql/extensions/postgres/types/network.rb +15 -0
- data/lib/rom/sql/extensions/sqlite.rb +1 -1
- data/lib/rom/sql/extensions/sqlite/{attributes_inferrer.rb → type_builder.rb} +5 -5
- data/lib/rom/sql/extensions/sqlite/types.rb +8 -3
- data/lib/rom/sql/foreign_key.rb +17 -0
- data/lib/rom/sql/function.rb +86 -8
- data/lib/rom/sql/gateway.rb +26 -26
- data/lib/rom/sql/index.rb +4 -0
- data/lib/rom/sql/migration.rb +3 -3
- data/lib/rom/sql/migration/inline_runner.rb +9 -83
- data/lib/rom/sql/migration/migrator.rb +35 -12
- data/lib/rom/sql/migration/recorder.rb +21 -0
- data/lib/rom/sql/migration/runner.rb +115 -0
- data/lib/rom/sql/migration/schema_diff.rb +108 -53
- data/lib/rom/sql/migration/writer.rb +61 -0
- data/lib/rom/sql/relation.rb +2 -1
- data/lib/rom/sql/relation/reading.rb +63 -3
- data/lib/rom/sql/relation/writing.rb +38 -0
- data/lib/rom/sql/schema.rb +9 -3
- data/lib/rom/sql/schema/attributes_inferrer.rb +3 -119
- data/lib/rom/sql/schema/inferrer.rb +99 -18
- data/lib/rom/sql/schema/type_builder.rb +94 -0
- data/lib/rom/sql/type_dsl.rb +30 -0
- data/lib/rom/sql/type_extensions.rb +11 -6
- data/lib/rom/sql/type_serializer.rb +46 -0
- data/lib/rom/sql/types.rb +12 -0
- data/lib/rom/sql/version.rb +1 -1
- metadata +26 -244
- data/.codeclimate.yml +0 -15
- data/.gitignore +0 -17
- data/.rspec +0 -3
- data/.travis.yml +0 -39
- data/.yardopts +0 -2
- data/Gemfile +0 -33
- data/Guardfile +0 -24
- data/LICENSE.txt +0 -22
- data/Rakefile +0 -19
- data/circle.yml +0 -10
- data/lib/rom/sql/extensions/mysql/attributes_inferrer.rb +0 -10
- data/lib/rom/sql/relation/sequel_api.rb +0 -133
- data/log/.gitkeep +0 -0
- data/rom-sql.gemspec +0 -29
- data/spec/extensions/postgres/attribute_spec.rb +0 -217
- data/spec/extensions/postgres/integration_spec.rb +0 -59
- data/spec/extensions/postgres/types_spec.rb +0 -252
- data/spec/extensions/sqlite/types_spec.rb +0 -11
- data/spec/fixtures/migrations/20150403090603_create_carrots.rb +0 -8
- data/spec/integration/associations/many_to_many/custom_fks_spec.rb +0 -76
- data/spec/integration/associations/many_to_many/from_view_spec.rb +0 -88
- data/spec/integration/associations/many_to_many_spec.rb +0 -162
- data/spec/integration/associations/many_to_one/custom_fks_spec.rb +0 -64
- data/spec/integration/associations/many_to_one/from_view_spec.rb +0 -84
- data/spec/integration/associations/many_to_one/self_ref_spec.rb +0 -53
- data/spec/integration/associations/many_to_one_spec.rb +0 -117
- data/spec/integration/associations/one_to_many/custom_fks_spec.rb +0 -54
- data/spec/integration/associations/one_to_many/from_view_spec.rb +0 -57
- data/spec/integration/associations/one_to_many/self_ref_spec.rb +0 -54
- data/spec/integration/associations/one_to_many_spec.rb +0 -86
- data/spec/integration/associations/one_to_one_spec.rb +0 -69
- data/spec/integration/associations/one_to_one_through_spec.rb +0 -92
- data/spec/integration/auto_migrations/errors_spec.rb +0 -31
- data/spec/integration/auto_migrations/indexes_spec.rb +0 -253
- data/spec/integration/auto_migrations/managing_columns_spec.rb +0 -156
- data/spec/integration/auto_migrations/postgres/column_types_spec.rb +0 -63
- data/spec/integration/combine_with_spec.rb +0 -43
- data/spec/integration/commands/create_spec.rb +0 -304
- data/spec/integration/commands/delete_spec.rb +0 -84
- data/spec/integration/commands/update_spec.rb +0 -90
- data/spec/integration/commands/upsert_spec.rb +0 -83
- data/spec/integration/gateway_spec.rb +0 -107
- data/spec/integration/migration_spec.rb +0 -55
- data/spec/integration/plugins/associates/many_to_many_spec.rb +0 -69
- data/spec/integration/plugins/associates_spec.rb +0 -250
- data/spec/integration/plugins/auto_restrictions_spec.rb +0 -74
- data/spec/integration/relation_schema_spec.rb +0 -271
- data/spec/integration/schema/call_spec.rb +0 -24
- data/spec/integration/schema/inferrer/mysql_spec.rb +0 -45
- data/spec/integration/schema/inferrer/postgres_spec.rb +0 -203
- data/spec/integration/schema/inferrer/sqlite_spec.rb +0 -37
- data/spec/integration/schema/inferrer_spec.rb +0 -390
- data/spec/integration/schema/prefix_spec.rb +0 -16
- data/spec/integration/schema/qualified_spec.rb +0 -16
- data/spec/integration/schema/rename_spec.rb +0 -21
- data/spec/integration/schema/view_spec.rb +0 -29
- data/spec/integration/sequel_api_spec.rb +0 -36
- data/spec/integration/setup_spec.rb +0 -26
- data/spec/integration/support/active_support_notifications_spec.rb +0 -24
- data/spec/integration/support/rails_log_subscriber_spec.rb +0 -30
- data/spec/integration/wrap_spec.rb +0 -91
- data/spec/shared/accounts.rb +0 -48
- data/spec/shared/database_setup.rb +0 -70
- data/spec/shared/notes.rb +0 -23
- data/spec/shared/posts.rb +0 -34
- data/spec/shared/puppies.rb +0 -15
- data/spec/shared/relations.rb +0 -8
- data/spec/shared/users.rb +0 -32
- data/spec/shared/users_and_tasks.rb +0 -50
- data/spec/spec_helper.rb +0 -122
- data/spec/support/env_helper.rb +0 -25
- data/spec/support/helpers.rb +0 -24
- data/spec/support/oracle/create_users.sql +0 -7
- data/spec/support/oracle/set_sys_passwords.sql +0 -2
- data/spec/support/test_configuration.rb +0 -16
- data/spec/unit/attribute_spec.rb +0 -104
- data/spec/unit/function_spec.rb +0 -48
- data/spec/unit/gateway_spec.rb +0 -70
- data/spec/unit/logger_spec.rb +0 -14
- data/spec/unit/migration_tasks_spec.rb +0 -111
- data/spec/unit/migrator_spec.rb +0 -25
- data/spec/unit/order_dsl_spec.rb +0 -43
- data/spec/unit/plugin/associates_spec.rb +0 -94
- data/spec/unit/plugin/pagination_spec.rb +0 -91
- data/spec/unit/plugin/timestamp_spec.rb +0 -117
- data/spec/unit/projection_dsl_spec.rb +0 -110
- data/spec/unit/relation/assoc_spec.rb +0 -87
- data/spec/unit/relation/associations_spec.rb +0 -27
- data/spec/unit/relation/avg_spec.rb +0 -11
- data/spec/unit/relation/by_pk_spec.rb +0 -62
- data/spec/unit/relation/dataset_spec.rb +0 -50
- data/spec/unit/relation/distinct_spec.rb +0 -15
- data/spec/unit/relation/exclude_spec.rb +0 -11
- data/spec/unit/relation/exist_predicate_spec.rb +0 -25
- data/spec/unit/relation/exists_spec.rb +0 -18
- data/spec/unit/relation/fetch_spec.rb +0 -21
- data/spec/unit/relation/group_spec.rb +0 -61
- data/spec/unit/relation/having_spec.rb +0 -22
- data/spec/unit/relation/inner_join_spec.rb +0 -158
- data/spec/unit/relation/inspect_spec.rb +0 -11
- data/spec/unit/relation/instrument_spec.rb +0 -45
- data/spec/unit/relation/invert_spec.rb +0 -11
- data/spec/unit/relation/left_join_spec.rb +0 -55
- data/spec/unit/relation/lock_spec.rb +0 -93
- data/spec/unit/relation/map_spec.rb +0 -16
- data/spec/unit/relation/max_spec.rb +0 -11
- data/spec/unit/relation/min_spec.rb +0 -11
- data/spec/unit/relation/order_spec.rb +0 -51
- data/spec/unit/relation/pluck_spec.rb +0 -11
- data/spec/unit/relation/prefix_spec.rb +0 -29
- data/spec/unit/relation/primary_key_spec.rb +0 -27
- data/spec/unit/relation/project_spec.rb +0 -24
- data/spec/unit/relation/qualified_columns_spec.rb +0 -30
- data/spec/unit/relation/qualified_spec.rb +0 -25
- data/spec/unit/relation/read_spec.rb +0 -25
- data/spec/unit/relation/rename_spec.rb +0 -23
- data/spec/unit/relation/right_join_spec.rb +0 -57
- data/spec/unit/relation/select_append_spec.rb +0 -21
- data/spec/unit/relation/select_spec.rb +0 -40
- data/spec/unit/relation/sum_spec.rb +0 -11
- data/spec/unit/relation/union_spec.rb +0 -19
- data/spec/unit/relation/unique_predicate_spec.rb +0 -18
- data/spec/unit/relation/where_spec.rb +0 -133
- data/spec/unit/restriction_dsl_spec.rb +0 -34
- data/spec/unit/schema_spec.rb +0 -25
- data/spec/unit/types_spec.rb +0 -65
data/lib/rom/sql/relation.rb
CHANGED
@@ -6,7 +6,6 @@ require 'rom/sql/transaction'
|
|
6
6
|
|
7
7
|
require 'rom/sql/relation/reading'
|
8
8
|
require 'rom/sql/relation/writing'
|
9
|
-
require 'rom/sql/relation/sequel_api'
|
10
9
|
|
11
10
|
module ROM
|
12
11
|
module SQL
|
@@ -49,6 +48,8 @@ module ROM
|
|
49
48
|
|
50
49
|
# @api private
|
51
50
|
def self.define_default_views!
|
51
|
+
undef_method :by_pk if method_defined?(:by_pk)
|
52
|
+
|
52
53
|
if schema.primary_key.size > 1
|
53
54
|
# @!method by_pk(val1, val2)
|
54
55
|
# Return a relation restricted by its composite primary key
|
@@ -105,8 +105,8 @@ module ROM
|
|
105
105
|
# @return [Relation]
|
106
106
|
#
|
107
107
|
# @api public
|
108
|
-
def qualified
|
109
|
-
schema.qualified.(self)
|
108
|
+
def qualified(table_alias = nil)
|
109
|
+
schema.qualified(table_alias).(self)
|
110
110
|
end
|
111
111
|
|
112
112
|
# Return a list of qualified column names
|
@@ -768,7 +768,7 @@ module ROM
|
|
768
768
|
# @option options [TrueClass, FalseClass] :all Set to true to use UNION ALL instead of UNION, so duplicate rows can occur
|
769
769
|
# @option options [TrueClass, FalseClass] :from_self Set to false to not wrap the returned dataset in a #from_self, use with care.
|
770
770
|
#
|
771
|
-
# @
|
771
|
+
# @returRelation]
|
772
772
|
#
|
773
773
|
# @api public
|
774
774
|
def union(relation, options = EMPTY_HASH, &block)
|
@@ -859,6 +859,66 @@ module ROM
|
|
859
859
|
end
|
860
860
|
end
|
861
861
|
|
862
|
+
# Restrict with rows from another relation.
|
863
|
+
# Accepts only SQL relations and uses the EXISTS
|
864
|
+
# clause under the hood
|
865
|
+
#
|
866
|
+
# @example using associations
|
867
|
+
# users.exists(tasks)
|
868
|
+
#
|
869
|
+
# @example using provided condition
|
870
|
+
# users.exists(tasks, tasks[:user_id] => users[:id])
|
871
|
+
#
|
872
|
+
# @param [SQL::Relation] other The other relation
|
873
|
+
# @param [Hash,Object] condition An optional join condition
|
874
|
+
#
|
875
|
+
# @return [SQL::Relation]
|
876
|
+
#
|
877
|
+
# @api public
|
878
|
+
def exists(other, condition = nil)
|
879
|
+
join_condition = condition || associations[other.name].join_keys
|
880
|
+
where(other.where(join_condition).dataset.exists)
|
881
|
+
end
|
882
|
+
|
883
|
+
# Process the dataset in batches.
|
884
|
+
# The method yields a relation restricted by a primary key value.
|
885
|
+
# This means it discards any order internally and uses the PK sort.
|
886
|
+
# Currently, works only with a single-column primary key.
|
887
|
+
#
|
888
|
+
# @example update in batches
|
889
|
+
# users.each_batch do |rel|
|
890
|
+
# rel.
|
891
|
+
# command(:update).
|
892
|
+
# call(name: users[:first_name].concat(users[:last_name])
|
893
|
+
# end
|
894
|
+
#
|
895
|
+
# @option [Integer] size The size of a batch (max number of records)
|
896
|
+
# @yieldparam [SQL::Relation]
|
897
|
+
#
|
898
|
+
# @api public
|
899
|
+
def each_batch(size: 1000)
|
900
|
+
pks = schema.primary_key
|
901
|
+
|
902
|
+
if pks.size > 1
|
903
|
+
raise ArgumentError, 'Composite primary keys are not supported yet'
|
904
|
+
end
|
905
|
+
|
906
|
+
source = order(pks[0]).limit(size)
|
907
|
+
rel = source
|
908
|
+
|
909
|
+
loop do
|
910
|
+
ids = rel.pluck(primary_key)
|
911
|
+
|
912
|
+
break if ids.empty?
|
913
|
+
|
914
|
+
yield(rel)
|
915
|
+
|
916
|
+
break if ids.size < size
|
917
|
+
|
918
|
+
rel = source.where(pks[0] > ids.last)
|
919
|
+
end
|
920
|
+
end
|
921
|
+
|
862
922
|
private
|
863
923
|
|
864
924
|
# Build a locking clause
|
@@ -77,6 +77,44 @@ module ROM
|
|
77
77
|
def delete(*args, &block)
|
78
78
|
dataset.delete(*args, &block)
|
79
79
|
end
|
80
|
+
|
81
|
+
# Insert tuples from other relation
|
82
|
+
# NOTE: The method implicitly uses a transaction
|
83
|
+
#
|
84
|
+
# @example
|
85
|
+
# users.import(new_users)
|
86
|
+
#
|
87
|
+
# @overload import(other_sql_relation, options)
|
88
|
+
# If both relations uses the same gateway
|
89
|
+
# the INSERT ... SELECT statement will
|
90
|
+
# be used for importing the data
|
91
|
+
#
|
92
|
+
# @params [SQL::Relation] other_sql_relation
|
93
|
+
#
|
94
|
+
# @option [Integer] :slice
|
95
|
+
# Split loading into batches of provided size,
|
96
|
+
# every batch will be processed in a separate
|
97
|
+
# transaction block
|
98
|
+
#
|
99
|
+
# @overload import(other, options)
|
100
|
+
# Import data from another relation. The source
|
101
|
+
# relation will be materialized before loading
|
102
|
+
#
|
103
|
+
# @params [Relation] other
|
104
|
+
#
|
105
|
+
# @option [Integer] :slice
|
106
|
+
#
|
107
|
+
# @api public
|
108
|
+
def import(other, options = EMPTY_HASH)
|
109
|
+
if other.gateway.eql?(gateway)
|
110
|
+
columns = other.schema.map { |a| a.alias || a.name }
|
111
|
+
dataset.import(columns, other.dataset, options)
|
112
|
+
else
|
113
|
+
columns = other.schema.map { |a| a.alias || a.name }
|
114
|
+
keys = columns.map(&:to_sym)
|
115
|
+
dataset.import(columns, other.to_a.map { |record| record.to_h.values_at(*keys) }, options)
|
116
|
+
end
|
117
|
+
end
|
80
118
|
end
|
81
119
|
end
|
82
120
|
end
|
data/lib/rom/sql/schema.rb
CHANGED
@@ -6,15 +6,20 @@ require 'rom/sql/group_dsl'
|
|
6
6
|
require 'rom/sql/projection_dsl'
|
7
7
|
require 'rom/sql/restriction_dsl'
|
8
8
|
require 'rom/sql/index'
|
9
|
+
require 'rom/sql/foreign_key'
|
9
10
|
require 'rom/sql/schema/inferrer'
|
10
11
|
|
11
12
|
module ROM
|
12
13
|
module SQL
|
13
14
|
class Schema < ROM::Schema
|
14
|
-
# @!attribute [r]
|
15
|
+
# @!attribute [r] indexes
|
15
16
|
# @return [Array<Index>] Array with schema indexes
|
16
17
|
option :indexes, default: -> { EMPTY_SET }
|
17
18
|
|
19
|
+
# @!attribute [r] foreign_keys
|
20
|
+
# @return [Array<ForeignKey>] Array with foreign keys
|
21
|
+
option :foreign_keys, default: -> { EMPTY_SET }
|
22
|
+
|
18
23
|
# @api public
|
19
24
|
def restriction(&block)
|
20
25
|
RestrictionDSL.new(self).call(&block)
|
@@ -35,8 +40,8 @@ module ROM
|
|
35
40
|
# @return [Schema]
|
36
41
|
#
|
37
42
|
# @api public
|
38
|
-
def qualified
|
39
|
-
new(map(
|
43
|
+
def qualified(table_alias = nil)
|
44
|
+
new(map { |attr| attr.qualified(table_alias) })
|
40
45
|
end
|
41
46
|
|
42
47
|
# Return a new schema with attributes restored to canonical form
|
@@ -100,6 +105,7 @@ module ROM
|
|
100
105
|
# @api private
|
101
106
|
def finalize_attributes!(options = EMPTY_HASH)
|
102
107
|
super do
|
108
|
+
@attributes = map(&:qualified)
|
103
109
|
initialize_primary_key_names
|
104
110
|
end
|
105
111
|
end
|
@@ -8,42 +8,11 @@ module ROM
|
|
8
8
|
extend Dry::Core::ClassAttributes
|
9
9
|
extend Initializer
|
10
10
|
|
11
|
-
defines :
|
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
|
11
|
+
defines :type_builders
|
28
12
|
|
29
13
|
CONSTRAINT_DB_TYPE = 'add_constraint'.freeze
|
30
|
-
DECIMAL_REGEX = /(?:decimal|numeric)\((\d+)(?:,\s*(\d+))?\)/.freeze
|
31
14
|
|
32
|
-
|
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
|
15
|
+
option :type_builder
|
47
16
|
|
48
17
|
option :attr_class, optional: true
|
49
18
|
|
@@ -52,10 +21,9 @@ module ROM
|
|
52
21
|
dataset = schema.name.dataset
|
53
22
|
|
54
23
|
columns = filter_columns(gateway.connection.schema(dataset))
|
55
|
-
fks = fks_for(gateway, dataset)
|
56
24
|
|
57
25
|
inferred = columns.map do |(name, definition)|
|
58
|
-
type =
|
26
|
+
type = type_builder.(definition)
|
59
27
|
|
60
28
|
attr_class.new(type.meta(name: name, source: schema.name)) if type
|
61
29
|
end.compact
|
@@ -70,94 +38,10 @@ module ROM
|
|
70
38
|
self.class.new(options.merge(new_options))
|
71
39
|
end
|
72
40
|
|
73
|
-
|
74
41
|
# @api private
|
75
42
|
def filter_columns(schema)
|
76
43
|
schema.reject { |(_, definition)| definition[:db_type] == CONSTRAINT_DB_TYPE }
|
77
44
|
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
45
|
end
|
162
46
|
end
|
163
47
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'set'
|
2
2
|
|
3
|
+
require 'rom/sql/schema/type_builder'
|
3
4
|
require 'rom/sql/schema/attributes_inferrer'
|
4
5
|
require 'rom/sql/attribute'
|
5
6
|
|
@@ -8,8 +9,12 @@ module ROM
|
|
8
9
|
class Schema < ROM::Schema
|
9
10
|
# @api private
|
10
11
|
class Inferrer < ROM::Schema::Inferrer
|
12
|
+
defines :type_builders
|
13
|
+
|
11
14
|
attributes_inferrer -> (schema, gateway, options) do
|
12
|
-
|
15
|
+
builder = TypeBuilder[gateway.database_type]
|
16
|
+
inferrer = AttributesInferrer.new(type_builder: builder, **options)
|
17
|
+
inferrer.(schema, gateway)
|
13
18
|
end
|
14
19
|
|
15
20
|
attr_class SQL::Attribute
|
@@ -18,39 +23,87 @@ module ROM
|
|
18
23
|
|
19
24
|
option :raise_on_error, default: -> { true }
|
20
25
|
|
21
|
-
FALLBACK_SCHEMA = {
|
26
|
+
FALLBACK_SCHEMA = {
|
27
|
+
attributes: EMPTY_ARRAY,
|
28
|
+
indexes: EMPTY_SET,
|
29
|
+
foreign_keys: EMPTY_SET
|
30
|
+
}.freeze
|
22
31
|
|
23
32
|
# @api private
|
24
33
|
def call(schema, gateway)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
34
|
+
if enabled?
|
35
|
+
infer_from_database(gateway, schema, super)
|
36
|
+
else
|
37
|
+
infer_from_attributes(gateway, schema, super)
|
38
|
+
end
|
30
39
|
rescue Sequel::Error => error
|
31
40
|
on_error(schema.name, error)
|
32
|
-
FALLBACK_SCHEMA
|
41
|
+
{ **FALLBACK_SCHEMA, indexes: schema.indexes }
|
33
42
|
end
|
34
43
|
|
35
44
|
# @api private
|
36
|
-
def
|
37
|
-
|
45
|
+
def infer_from_database(gateway, schema, attributes:, **rest)
|
46
|
+
idx = attributes_index(attributes)
|
47
|
+
indexes = indexes_from_database(gateway, schema, idx)
|
48
|
+
foreign_keys = foreign_keys_from_database(gateway, schema, idx)
|
49
|
+
|
50
|
+
{ **rest,
|
51
|
+
attributes: attributes.map { |attr| mark_fk(mark_indexed(attr, indexes), foreign_keys) },
|
52
|
+
foreign_keys: foreign_keys,
|
53
|
+
indexes: indexes }
|
54
|
+
end
|
38
55
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
56
|
+
# @api private
|
57
|
+
def infer_from_attributes(gateway, schema, attributes:, **rest)
|
58
|
+
indexes = schema.indexes | indexes_from_attributes(attributes)
|
59
|
+
foreign_keys = foreign_keys_from_attributes(attributes)
|
60
|
+
|
61
|
+
{ **rest,
|
62
|
+
attributes: attributes.map { |attr| mark_indexed(attr, indexes) },
|
63
|
+
foreign_keys: foreign_keys,
|
64
|
+
indexes: indexes }
|
65
|
+
end
|
44
66
|
|
45
|
-
|
67
|
+
# @api private
|
68
|
+
def indexes_from_database(gateway, schema, attributes)
|
69
|
+
if gateway.connection.respond_to?(:indexes)
|
70
|
+
dataset = schema.name.dataset
|
71
|
+
|
72
|
+
gateway.connection.indexes(dataset).map { |index_name, columns:, unique:, **rest|
|
73
|
+
attrs = columns.map { |name| attributes[name] }
|
74
|
+
|
75
|
+
SQL::Index.new(attrs, name: index_name, unique: unique)
|
46
76
|
}.to_set
|
47
77
|
else
|
48
|
-
|
78
|
+
EMPTY_SET
|
49
79
|
end
|
50
80
|
end
|
51
81
|
|
82
|
+
# @api private
|
83
|
+
def foreign_keys_from_database(gateway, schema, attributes)
|
84
|
+
dataset = schema.name.dataset
|
85
|
+
|
86
|
+
gateway.connection.foreign_key_list(dataset).map { |columns:, table:, key:, **rest|
|
87
|
+
attrs = columns.map { |name| attributes[name] }
|
88
|
+
|
89
|
+
SQL::ForeignKey.new(attrs, table, parent_keys: key)
|
90
|
+
}.to_set
|
91
|
+
end
|
92
|
+
|
93
|
+
# @api private
|
52
94
|
def indexes_from_attributes(attributes)
|
53
|
-
attributes.
|
95
|
+
attributes.
|
96
|
+
select(&:indexed?).
|
97
|
+
map { |attr| SQL::Index.new([attr.unwrap]) }.
|
98
|
+
to_set
|
99
|
+
end
|
100
|
+
|
101
|
+
# @api private
|
102
|
+
def foreign_keys_from_attributes(attributes)
|
103
|
+
attributes.
|
104
|
+
select(&:foreign_key?).
|
105
|
+
map { |attr| SQL::ForeignKey.new([attr.unwrap], attr.target) }.
|
106
|
+
to_set
|
54
107
|
end
|
55
108
|
|
56
109
|
# @api private
|
@@ -60,6 +113,34 @@ module ROM
|
|
60
113
|
|
61
114
|
private
|
62
115
|
|
116
|
+
def attributes_index(attributes)
|
117
|
+
Hash.new { |idx, name| idx[name] = attributes.find { |attr| attr.name == name }.unwrap }
|
118
|
+
end
|
119
|
+
|
120
|
+
# @private
|
121
|
+
def mark_indexed(attribute, indexes)
|
122
|
+
if !attribute.indexed? && indexes.any? { |index| index.can_access?(attribute) }
|
123
|
+
attribute.indexed
|
124
|
+
else
|
125
|
+
attribute
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# @private
|
130
|
+
def mark_fk(attribute, foreign_keys)
|
131
|
+
if attribute.foreign_key?
|
132
|
+
attribute
|
133
|
+
else
|
134
|
+
foreign_key = foreign_keys.find { |fk| fk.attributes.map(&:name) == [attribute.name] }
|
135
|
+
|
136
|
+
if foreign_key.nil?
|
137
|
+
attribute
|
138
|
+
else
|
139
|
+
attribute.meta(foreign_key: true, target: foreign_key.parent_table)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
63
144
|
# @api private
|
64
145
|
def on_error(dataset, e)
|
65
146
|
if raise_on_error
|