rom-sql 2.0.0.beta2 → 2.0.0.beta3
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 +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
|