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.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -0
  3. data/lib/rom/plugins/relation/sql/postgres/explain.rb +54 -0
  4. data/lib/rom/sql.rb +1 -1
  5. data/lib/rom/sql/attribute.rb +17 -18
  6. data/lib/rom/sql/errors.rb +3 -0
  7. data/lib/rom/sql/extensions/mysql.rb +1 -1
  8. data/lib/rom/sql/extensions/mysql/type_builder.rb +28 -0
  9. data/lib/rom/sql/extensions/postgres.rb +3 -1
  10. data/lib/rom/sql/extensions/postgres/commands.rb +30 -13
  11. data/lib/rom/sql/extensions/postgres/{attributes_inferrer.rb → type_builder.rb} +24 -28
  12. data/lib/rom/sql/extensions/postgres/type_serializer.rb +39 -0
  13. data/lib/rom/sql/extensions/postgres/types.rb +24 -477
  14. data/lib/rom/sql/extensions/postgres/types/array.rb +163 -0
  15. data/lib/rom/sql/extensions/postgres/types/geometric.rb +135 -0
  16. data/lib/rom/sql/extensions/postgres/types/json.rb +235 -0
  17. data/lib/rom/sql/extensions/postgres/types/network.rb +15 -0
  18. data/lib/rom/sql/extensions/sqlite.rb +1 -1
  19. data/lib/rom/sql/extensions/sqlite/{attributes_inferrer.rb → type_builder.rb} +5 -5
  20. data/lib/rom/sql/extensions/sqlite/types.rb +8 -3
  21. data/lib/rom/sql/foreign_key.rb +17 -0
  22. data/lib/rom/sql/function.rb +86 -8
  23. data/lib/rom/sql/gateway.rb +26 -26
  24. data/lib/rom/sql/index.rb +4 -0
  25. data/lib/rom/sql/migration.rb +3 -3
  26. data/lib/rom/sql/migration/inline_runner.rb +9 -83
  27. data/lib/rom/sql/migration/migrator.rb +35 -12
  28. data/lib/rom/sql/migration/recorder.rb +21 -0
  29. data/lib/rom/sql/migration/runner.rb +115 -0
  30. data/lib/rom/sql/migration/schema_diff.rb +108 -53
  31. data/lib/rom/sql/migration/writer.rb +61 -0
  32. data/lib/rom/sql/relation.rb +2 -1
  33. data/lib/rom/sql/relation/reading.rb +63 -3
  34. data/lib/rom/sql/relation/writing.rb +38 -0
  35. data/lib/rom/sql/schema.rb +9 -3
  36. data/lib/rom/sql/schema/attributes_inferrer.rb +3 -119
  37. data/lib/rom/sql/schema/inferrer.rb +99 -18
  38. data/lib/rom/sql/schema/type_builder.rb +94 -0
  39. data/lib/rom/sql/type_dsl.rb +30 -0
  40. data/lib/rom/sql/type_extensions.rb +11 -6
  41. data/lib/rom/sql/type_serializer.rb +46 -0
  42. data/lib/rom/sql/types.rb +12 -0
  43. data/lib/rom/sql/version.rb +1 -1
  44. metadata +26 -244
  45. data/.codeclimate.yml +0 -15
  46. data/.gitignore +0 -17
  47. data/.rspec +0 -3
  48. data/.travis.yml +0 -39
  49. data/.yardopts +0 -2
  50. data/Gemfile +0 -33
  51. data/Guardfile +0 -24
  52. data/LICENSE.txt +0 -22
  53. data/Rakefile +0 -19
  54. data/circle.yml +0 -10
  55. data/lib/rom/sql/extensions/mysql/attributes_inferrer.rb +0 -10
  56. data/lib/rom/sql/relation/sequel_api.rb +0 -133
  57. data/log/.gitkeep +0 -0
  58. data/rom-sql.gemspec +0 -29
  59. data/spec/extensions/postgres/attribute_spec.rb +0 -217
  60. data/spec/extensions/postgres/integration_spec.rb +0 -59
  61. data/spec/extensions/postgres/types_spec.rb +0 -252
  62. data/spec/extensions/sqlite/types_spec.rb +0 -11
  63. data/spec/fixtures/migrations/20150403090603_create_carrots.rb +0 -8
  64. data/spec/integration/associations/many_to_many/custom_fks_spec.rb +0 -76
  65. data/spec/integration/associations/many_to_many/from_view_spec.rb +0 -88
  66. data/spec/integration/associations/many_to_many_spec.rb +0 -162
  67. data/spec/integration/associations/many_to_one/custom_fks_spec.rb +0 -64
  68. data/spec/integration/associations/many_to_one/from_view_spec.rb +0 -84
  69. data/spec/integration/associations/many_to_one/self_ref_spec.rb +0 -53
  70. data/spec/integration/associations/many_to_one_spec.rb +0 -117
  71. data/spec/integration/associations/one_to_many/custom_fks_spec.rb +0 -54
  72. data/spec/integration/associations/one_to_many/from_view_spec.rb +0 -57
  73. data/spec/integration/associations/one_to_many/self_ref_spec.rb +0 -54
  74. data/spec/integration/associations/one_to_many_spec.rb +0 -86
  75. data/spec/integration/associations/one_to_one_spec.rb +0 -69
  76. data/spec/integration/associations/one_to_one_through_spec.rb +0 -92
  77. data/spec/integration/auto_migrations/errors_spec.rb +0 -31
  78. data/spec/integration/auto_migrations/indexes_spec.rb +0 -253
  79. data/spec/integration/auto_migrations/managing_columns_spec.rb +0 -156
  80. data/spec/integration/auto_migrations/postgres/column_types_spec.rb +0 -63
  81. data/spec/integration/combine_with_spec.rb +0 -43
  82. data/spec/integration/commands/create_spec.rb +0 -304
  83. data/spec/integration/commands/delete_spec.rb +0 -84
  84. data/spec/integration/commands/update_spec.rb +0 -90
  85. data/spec/integration/commands/upsert_spec.rb +0 -83
  86. data/spec/integration/gateway_spec.rb +0 -107
  87. data/spec/integration/migration_spec.rb +0 -55
  88. data/spec/integration/plugins/associates/many_to_many_spec.rb +0 -69
  89. data/spec/integration/plugins/associates_spec.rb +0 -250
  90. data/spec/integration/plugins/auto_restrictions_spec.rb +0 -74
  91. data/spec/integration/relation_schema_spec.rb +0 -271
  92. data/spec/integration/schema/call_spec.rb +0 -24
  93. data/spec/integration/schema/inferrer/mysql_spec.rb +0 -45
  94. data/spec/integration/schema/inferrer/postgres_spec.rb +0 -203
  95. data/spec/integration/schema/inferrer/sqlite_spec.rb +0 -37
  96. data/spec/integration/schema/inferrer_spec.rb +0 -390
  97. data/spec/integration/schema/prefix_spec.rb +0 -16
  98. data/spec/integration/schema/qualified_spec.rb +0 -16
  99. data/spec/integration/schema/rename_spec.rb +0 -21
  100. data/spec/integration/schema/view_spec.rb +0 -29
  101. data/spec/integration/sequel_api_spec.rb +0 -36
  102. data/spec/integration/setup_spec.rb +0 -26
  103. data/spec/integration/support/active_support_notifications_spec.rb +0 -24
  104. data/spec/integration/support/rails_log_subscriber_spec.rb +0 -30
  105. data/spec/integration/wrap_spec.rb +0 -91
  106. data/spec/shared/accounts.rb +0 -48
  107. data/spec/shared/database_setup.rb +0 -70
  108. data/spec/shared/notes.rb +0 -23
  109. data/spec/shared/posts.rb +0 -34
  110. data/spec/shared/puppies.rb +0 -15
  111. data/spec/shared/relations.rb +0 -8
  112. data/spec/shared/users.rb +0 -32
  113. data/spec/shared/users_and_tasks.rb +0 -50
  114. data/spec/spec_helper.rb +0 -122
  115. data/spec/support/env_helper.rb +0 -25
  116. data/spec/support/helpers.rb +0 -24
  117. data/spec/support/oracle/create_users.sql +0 -7
  118. data/spec/support/oracle/set_sys_passwords.sql +0 -2
  119. data/spec/support/test_configuration.rb +0 -16
  120. data/spec/unit/attribute_spec.rb +0 -104
  121. data/spec/unit/function_spec.rb +0 -48
  122. data/spec/unit/gateway_spec.rb +0 -70
  123. data/spec/unit/logger_spec.rb +0 -14
  124. data/spec/unit/migration_tasks_spec.rb +0 -111
  125. data/spec/unit/migrator_spec.rb +0 -25
  126. data/spec/unit/order_dsl_spec.rb +0 -43
  127. data/spec/unit/plugin/associates_spec.rb +0 -94
  128. data/spec/unit/plugin/pagination_spec.rb +0 -91
  129. data/spec/unit/plugin/timestamp_spec.rb +0 -117
  130. data/spec/unit/projection_dsl_spec.rb +0 -110
  131. data/spec/unit/relation/assoc_spec.rb +0 -87
  132. data/spec/unit/relation/associations_spec.rb +0 -27
  133. data/spec/unit/relation/avg_spec.rb +0 -11
  134. data/spec/unit/relation/by_pk_spec.rb +0 -62
  135. data/spec/unit/relation/dataset_spec.rb +0 -50
  136. data/spec/unit/relation/distinct_spec.rb +0 -15
  137. data/spec/unit/relation/exclude_spec.rb +0 -11
  138. data/spec/unit/relation/exist_predicate_spec.rb +0 -25
  139. data/spec/unit/relation/exists_spec.rb +0 -18
  140. data/spec/unit/relation/fetch_spec.rb +0 -21
  141. data/spec/unit/relation/group_spec.rb +0 -61
  142. data/spec/unit/relation/having_spec.rb +0 -22
  143. data/spec/unit/relation/inner_join_spec.rb +0 -158
  144. data/spec/unit/relation/inspect_spec.rb +0 -11
  145. data/spec/unit/relation/instrument_spec.rb +0 -45
  146. data/spec/unit/relation/invert_spec.rb +0 -11
  147. data/spec/unit/relation/left_join_spec.rb +0 -55
  148. data/spec/unit/relation/lock_spec.rb +0 -93
  149. data/spec/unit/relation/map_spec.rb +0 -16
  150. data/spec/unit/relation/max_spec.rb +0 -11
  151. data/spec/unit/relation/min_spec.rb +0 -11
  152. data/spec/unit/relation/order_spec.rb +0 -51
  153. data/spec/unit/relation/pluck_spec.rb +0 -11
  154. data/spec/unit/relation/prefix_spec.rb +0 -29
  155. data/spec/unit/relation/primary_key_spec.rb +0 -27
  156. data/spec/unit/relation/project_spec.rb +0 -24
  157. data/spec/unit/relation/qualified_columns_spec.rb +0 -30
  158. data/spec/unit/relation/qualified_spec.rb +0 -25
  159. data/spec/unit/relation/read_spec.rb +0 -25
  160. data/spec/unit/relation/rename_spec.rb +0 -23
  161. data/spec/unit/relation/right_join_spec.rb +0 -57
  162. data/spec/unit/relation/select_append_spec.rb +0 -21
  163. data/spec/unit/relation/select_spec.rb +0 -40
  164. data/spec/unit/relation/sum_spec.rb +0 -11
  165. data/spec/unit/relation/union_spec.rb +0 -19
  166. data/spec/unit/relation/unique_predicate_spec.rb +0 -18
  167. data/spec/unit/relation/where_spec.rb +0 -133
  168. data/spec/unit/restriction_dsl_spec.rb +0 -34
  169. data/spec/unit/schema_spec.rb +0 -25
  170. data/spec/unit/types_spec.rb +0 -65
@@ -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
- # @return [Relation]
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
@@ -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] attributes
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(&:qualified))
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 :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
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
- 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
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 = build_type(**definition, foreign_key: fks[name])
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
- AttributesInferrer.get(gateway.database_type).with(options).(schema, gateway)
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 = { attributes: EMPTY_ARRAY, indexes: EMPTY_SET }.freeze
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
- inferred = super
26
-
27
- indexes = get_indexes(gateway, schema, inferred[:attributes])
28
-
29
- { **inferred, indexes: indexes }
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 get_indexes(gateway, schema, attributes)
37
- dataset = schema.name.dataset
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
- if enabled? && gateway.connection.respond_to?(:indexes)
40
- gateway.connection.indexes(dataset).map { |name, body|
41
- columns = body[:columns].map { |name|
42
- attributes.find { |attr| attr.name == name }.unwrap
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
- SQL::Index.new(columns, name: name, unique: body[:unique])
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
- schema.indexes | indexes_from_attributes(attributes)
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.select(&:indexed?).map { |attr| SQL::Index.new([attr.unwrap]) }.to_set
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