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.
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
@@ -0,0 +1,15 @@
1
+ require 'ipaddr'
2
+
3
+ module ROM
4
+ module SQL
5
+ module Postgres
6
+ module Types
7
+ IPAddress = Type('inet') do
8
+ read = SQL::Types.Constructor(IPAddr) { |ip| IPAddr.new(ip.to_s) }
9
+
10
+ SQL::Types.Constructor(IPAddr, &:to_s).meta(read: read)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,2 +1,2 @@
1
1
  require 'rom/sql/extensions/sqlite/types'
2
- require 'rom/sql/extensions/sqlite/attributes_inferrer'
2
+ require 'rom/sql/extensions/sqlite/type_builder'
@@ -1,19 +1,19 @@
1
- require 'rom/sql/schema/attributes_inferrer'
2
-
3
1
  module ROM
4
2
  module SQL
5
- class Schema
6
- class SqliteInferrer < AttributesInferrer[:sqlite]
3
+ module SQLite
4
+ class TypeBuilder < Schema::TypeBuilder
7
5
  NO_TYPE = EMPTY_STRING
8
6
 
9
7
  def map_type(_, db_type, **_kw)
10
8
  if db_type.eql?(NO_TYPE)
11
- ROM::SQL::Types::SQLite::Object
9
+ ROM::SQL::Types::SQLite::Any
12
10
  else
13
11
  super
14
12
  end
15
13
  end
16
14
  end
17
15
  end
16
+
17
+ Schema::TypeBuilder.register(:sqlite, SQLite::TypeBuilder.new.freeze)
18
18
  end
19
19
  end
@@ -2,10 +2,15 @@ require 'dry-types'
2
2
 
3
3
  module ROM
4
4
  module SQL
5
- module Types
6
- module SQLite
7
- Object = ::ROM::SQL::Types::Object
5
+ module SQLite
6
+ module Types
7
+ Any = ::ROM::SQL::Types::Any
8
+ Object = Any
8
9
  end
9
10
  end
11
+
12
+ module Types
13
+ SQLite = ::ROM::SQL::SQLite::Types
14
+ end
10
15
  end
11
16
  end
@@ -0,0 +1,17 @@
1
+ module ROM
2
+ module SQL
3
+ # @api private
4
+ class ForeignKey
5
+ extend Initializer
6
+ include Dry::Equalizer(:attributes, :parent_table, :options)
7
+
8
+ DEFAULT_PARENT_KEYS = %i(id).freeze
9
+
10
+ param :attributes
11
+
12
+ param :parent_table, type: Dry::Types['strict.symbol']
13
+
14
+ option :parent_keys, default: -> { DEFAULT_PARENT_KEYS }
15
+ end
16
+ end
17
+ end
@@ -1,9 +1,41 @@
1
- require 'rom/schema/attribute'
1
+ require 'rom/attribute'
2
2
 
3
3
  module ROM
4
4
  module SQL
5
- # @api private
6
- class Function < ROM::Schema::Attribute
5
+ # @api public
6
+ class Function < ROM::Attribute
7
+ class << self
8
+ # @api private
9
+ def frame_limit(value)
10
+ case value
11
+ when :current then 'CURRENT ROW'
12
+ when :start then 'UNBOUNDED PRECEDING'
13
+ when :end then 'UNBOUNDED FOLLOWING'
14
+ else
15
+ if value > 0
16
+ "#{ value } FOLLOWING"
17
+ else
18
+ "#{ value.abs } PRECEDING"
19
+ end
20
+ end
21
+ end
22
+
23
+ private :frame_limit
24
+ end
25
+
26
+ # @api private
27
+ WINDOW_FRAMES = Hash.new do |cache, frame|
28
+ type = frame.key?(:rows) ? 'ROWS' : 'RANGE'
29
+ bounds = frame[:rows] || frame[:range]
30
+ cache[frame] = "#{ type } BETWEEN #{ frame_limit(bounds[0]) } AND #{ frame_limit(bounds[1]) }"
31
+ end
32
+
33
+ WINDOW_FRAMES[nil] = nil
34
+ WINDOW_FRAMES[:all] = WINDOW_FRAMES[rows: [:start, :end]]
35
+ WINDOW_FRAMES[:rows] = WINDOW_FRAMES[rows: [:start, :current]]
36
+ WINDOW_FRAMES[range: :current] = WINDOW_FRAMES[range: [:current, :current]]
37
+
38
+ # @api private
7
39
  def sql_literal(ds)
8
40
  if name
9
41
  ds.literal(func.as(name))
@@ -12,41 +44,87 @@ module ROM
12
44
  end
13
45
  end
14
46
 
47
+ # @api private
15
48
  def name
16
49
  meta[:alias] || super
17
50
  end
18
51
 
19
- def qualified
52
+ # @api private
53
+ def qualified(table_alias = nil)
20
54
  meta(
21
- func: ::Sequel::SQL::Function.new(func.name, *func.args.map { |arg| arg.respond_to?(:qualified) ? arg.qualified : arg })
55
+ func: ::Sequel::SQL::Function.new(func.name, *func.args.map { |arg| arg.respond_to?(:qualified) ? arg.qualified(table_alias) : arg })
22
56
  )
23
57
  end
24
58
 
59
+ # @api private
25
60
  def is(other)
26
61
  ::Sequel::SQL::BooleanExpression.new(:'=', func, other)
27
62
  end
28
63
 
64
+ # Add an OVER clause making a window function call
65
+ # @see https://www.postgresql.org/docs/9.6/static/tutorial-window.html
66
+ #
67
+ # @example
68
+ # users.select { [id, int::row_number().over(partition: name, order: id).as(:row_no)] }
69
+ # users.select { [id, int::row_number().over(partition: [first_name, last_name], order: id).as(:row_no)] }
70
+ #
71
+ # @example frame variants
72
+ # # ROWS BETWEEN 3 PRECEDING AND CURRENT ROW
73
+ # row_number.over(frame: { rows: [-3, :current] })
74
+ #
75
+ # # ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING
76
+ # row_number.over(frame: { rows: [-3, 3] })
77
+ #
78
+ # # ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
79
+ # row_number.over(frame: { rows: [:start, :current] })
80
+ #
81
+ # # ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
82
+ # row_number.over(frame: { rows: [:current, :end] })
83
+ #
84
+ # @example frame shortcuts
85
+ # # ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
86
+ # row_number.over(frame: :all)
87
+ #
88
+ # # ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
89
+ # row_number.over(frame: :rows)
90
+ #
91
+ # # RANGE BETWEEN CURRENT ROW AND CURRENT ROW
92
+ # row_number.over(frame: { range: :current} )
93
+ #
94
+ # @option :partition [Array<SQL::Attribute>,SQL::Attribute] A PARTITION BY part
95
+ # @option :order [Array<SQL::Attribute>,SQL::Attribute] An ORDER BY part
96
+ # @option :frame [Hash,Symbol] A frame part (RANGE or ROWS, see examples)
97
+ # @return [SQL::Function]
98
+ #
99
+ # @api public
100
+ def over(partition: nil, order: nil, frame: nil)
101
+ super(partition: partition, order: order, frame: WINDOW_FRAMES[frame])
102
+ end
103
+
29
104
  # Convert an expression result to another data type
30
105
  #
31
106
  # @example
32
107
  # users.select { bool::cast(json_data.get_text('activated'), :boolean).as(:activated) }
108
+ # users.select { bool::cast(json_data.get_text('activated')).as(:activated) }
33
109
  #
34
110
  # @param [ROM::SQL::Attribute] expr Expression to be cast
35
- # @param [String] db_type Target database type
111
+ # @param [String] db_type Target database type (usually can be inferred from the target data type)
36
112
  #
37
113
  # @return [ROM::SQL::Attribute]
38
114
  #
39
- # @api private
40
- def cast(expr, db_type)
115
+ # @api public
116
+ def cast(expr, db_type = TypeSerializer[:default].call(type))
41
117
  Attribute[type].meta(sql_expr: ::Sequel.cast(expr, db_type))
42
118
  end
43
119
 
44
120
  private
45
121
 
122
+ # @api private
46
123
  def func
47
124
  meta[:func]
48
125
  end
49
126
 
127
+ # @api private
50
128
  def method_missing(meth, *args)
51
129
  if func
52
130
  if func.respond_to?(meth)
@@ -1,4 +1,5 @@
1
1
  require 'logger'
2
+ require 'sequel/core'
2
3
 
3
4
  require 'dry/core/constants'
4
5
 
@@ -31,23 +32,6 @@ module ROM
31
32
  # @return [Hash] Options used for connection
32
33
  attr_reader :options
33
34
 
34
- subscribe('configuration.commands.class.before_build') do |event|
35
- klass = event[:command]
36
- dataset = event[:dataset]
37
- type = dataset.db.database_type
38
-
39
- if type == :postgres
40
- ext =
41
- if klass < Commands::Create
42
- Commands::Postgres::Create
43
- elsif klass < Commands::Update
44
- Commands::Postgres::Update
45
- end
46
-
47
- klass.send(:include, ext) if ext
48
- end
49
- end
50
-
51
35
  # Initialize an SQL gateway
52
36
  #
53
37
  # Gateways are typically initialized via ROM::Configuration object, gateway constructor
@@ -71,14 +55,6 @@ module ROM
71
55
  #
72
56
  # @param [Hash] options connection options
73
57
  #
74
- # @option options [Array<Symbol>] :inferrable_relations
75
- # A list of dataset names that should be inferred. If
76
- # this is set explicitly to an empty array relations
77
- # won't be inferred at all
78
- #
79
- # @option options [Array<Symbol>] :not_inferrable_relations
80
- # A list of dataset names that should NOT be inferred
81
- #
82
58
  # @option options [Array<Symbol>] :extensions
83
59
  # A list of connection extensions supported by Sequel
84
60
  #
@@ -118,7 +94,7 @@ module ROM
118
94
 
119
95
  # Return dataset with the given name
120
96
  #
121
- # Thsi returns a raw Sequel database
97
+ # This returns a raw Sequel database
122
98
  #
123
99
  # @param [String, Symbol] name The dataset name
124
100
  #
@@ -202,6 +178,30 @@ module ROM
202
178
  @database_type ||= connection.database_type.to_sym
203
179
  end
204
180
 
181
+ # Call a SQL function
182
+ #
183
+ # @example
184
+ # gateway.(:upper, 'John Doe') # => "JOHN DOE"
185
+ #
186
+ # @param [Symbol] function Function name
187
+ # @param [Array<Object>] args Function arguments
188
+ #
189
+ # @return [Object]
190
+ #
191
+ # @api public
192
+ def call(function, *args)
193
+ connection[Sequel.function(function, *args)].first.values.first
194
+ end
195
+
196
+ # Execute a statement
197
+ #
198
+ # @param [String] statement
199
+ #
200
+ # @api public
201
+ def run(statement)
202
+ connection.run(statement)
203
+ end
204
+
205
205
  private
206
206
 
207
207
  # Connect to database or reuse established connection instance
data/lib/rom/sql/index.rb CHANGED
@@ -24,6 +24,10 @@ module ROM
24
24
  def partial?
25
25
  !predicate.nil?
26
26
  end
27
+
28
+ def can_access?(attribute)
29
+ !partial? && attributes[0].name == attribute.name
30
+ end
27
31
  end
28
32
  end
29
33
  end
@@ -138,12 +138,12 @@ module ROM
138
138
  end
139
139
 
140
140
  # @api public
141
- def auto_migrate!(conf)
141
+ def auto_migrate!(conf, options = EMPTY_HASH, &block)
142
142
  schemas = conf.relation_classes(self).map do |klass|
143
- klass.schema || klass.schema_proc.call.finalize_attributes!(gateway: self)
143
+ klass.schema_proc.call.finalize_attributes!(gateway: self)
144
144
  end
145
145
 
146
- migrator.auto_migrate!(self, schemas)
146
+ migrator.auto_migrate!(self, schemas, options)
147
147
  end
148
148
  end
149
149
  end
@@ -1,92 +1,18 @@
1
1
  module ROM
2
2
  module SQL
3
3
  module Migration
4
- class Migrator
5
- # @api private
6
- class InlineRunner
7
- attr_reader :gateway
4
+ # @api private
5
+ class InlineRunner < BasicObject
6
+ extend Initializer
8
7
 
9
- def initialize(gateway)
10
- @gateway = gateway
11
- end
8
+ param :connection
12
9
 
13
- def call(changes)
14
- changes.each do |diff|
15
- apply(diff)
16
- end
17
- end
18
-
19
- def apply(diff)
20
- case diff
21
- when SchemaDiff::TableCreated
22
- create_table(diff)
23
- when SchemaDiff::TableAltered
24
- alter_table(diff)
25
- else
26
- raise NotImplementedError
27
- end
28
- end
29
-
30
- def create_table(diff)
31
- gateway.create_table(diff.table_name) do
32
- diff.attributes.each do |attribute|
33
- if attribute.primary_key?
34
- primary_key attribute.name
35
- else
36
- column attribute.name, attribute.type, null: attribute.null?
37
- end
38
- end
39
-
40
- diff.indexes.each do |index|
41
- index index.attributes,
42
- name: index.name,
43
- unique: index.unique?,
44
- type: index.type,
45
- where: index.predicate
46
- end
47
- end
48
- end
49
-
50
- def alter_table(diff)
51
- gateway.connection.alter_table(diff.table_name) do
52
- diff.attribute_changes.each do |attribute|
53
- case attribute
54
- when SchemaDiff::AttributeAdded
55
- add_column attribute.name, attribute.type, null: attribute.null?
56
- when SchemaDiff::AttributeRemoved
57
- drop_column attribute.name
58
- when SchemaDiff::AttributeChanged
59
- if attribute.type_changed?
60
- from, to = attribute.to_a.map(&attribute.method(:unwrap))
61
- raise UnsupportedConversion.new(
62
- "Don't know how to convert #{ from.inspect } to #{ to.inspect }"
63
- )
64
- end
65
-
66
- if attribute.nullability_changed?
67
- if attribute.null?
68
- set_column_allow_null attribute.name
69
- else
70
- set_column_not_null attribute.name
71
- end
72
- end
73
- end
74
- end
10
+ def migration
11
+ yield(connection)
12
+ end
75
13
 
76
- diff.index_changes.each do |index|
77
- case index
78
- when SchemaDiff::IndexAdded
79
- add_index index.attributes,
80
- name: index.name,
81
- unique: index.unique?,
82
- type: index.type,
83
- where: index.predicate
84
- when SchemaDiff::IndexRemoved
85
- drop_index index.attributes, name: index.name
86
- end
87
- end
88
- end
89
- end
14
+ def method_missing(m, *args, &block)
15
+ connection.public_send(m, *args, &block)
90
16
  end
91
17
  end
92
18
  end
@@ -3,7 +3,9 @@ require 'pathname'
3
3
  require 'rom/types'
4
4
  require 'rom/initializer'
5
5
  require 'rom/sql/migration'
6
+ require 'rom/sql/migration/runner'
6
7
  require 'rom/sql/migration/inline_runner'
8
+ require 'rom/sql/migration/writer'
7
9
 
8
10
  module ROM
9
11
  module SQL
@@ -14,11 +16,14 @@ module ROM
14
16
 
15
17
  DEFAULT_PATH = 'db/migrate'.freeze
16
18
  VERSION_FORMAT = '%Y%m%d%H%M%S'.freeze
19
+ DEFAULT_INFERRER = Schema::Inferrer.new.suppress_errors.freeze
17
20
 
18
21
  param :connection
19
22
 
20
23
  option :path, type: ROM::Types.Definition(Pathname), default: -> { DEFAULT_PATH }
21
24
 
25
+ option :inferrer, default: -> { DEFAULT_INFERRER }
26
+
22
27
  # @api private
23
28
  def run(options = {})
24
29
  Sequel::Migrator.run(connection, path.to_s, options)
@@ -35,13 +40,16 @@ module ROM
35
40
  end
36
41
 
37
42
  # @api private
38
- def create_file(name, version = generate_version)
39
- filename = "#{version}_#{name}.rb"
43
+ def create_file(name, version = generate_version, **options)
44
+ sequence = options[:sequence] ? '%03d' % options[:sequence] : nil
45
+ filename = "#{ version }#{ sequence }_#{ name }.rb"
46
+ content = options[:content] || migration_file_content
47
+ path = options[:path] || self.path
40
48
  dirname = Pathname(path)
41
49
  fullpath = dirname.join(filename)
42
50
 
43
51
  FileUtils.mkdir_p(dirname)
44
- File.write(fullpath, migration_file_content)
52
+ File.write(fullpath, content)
45
53
 
46
54
  fullpath
47
55
  end
@@ -57,19 +65,34 @@ module ROM
57
65
  end
58
66
 
59
67
  # @api private
60
- def diff(gateway, inferrer, target)
61
- empty = SQL::Schema.define(target.name)
62
- current = target.with(inferrer.(empty, gateway))
68
+ def auto_migrate!(gateway, schemas, options = EMPTY_HASH, &block)
69
+ diff_finder = SchemaDiff.new(gateway.database_type)
63
70
 
64
- SchemaDiff.new.(current, target)
65
- end
71
+ changes = schemas.map { |target|
72
+ empty = SQL::Schema.define(target.name)
73
+ current = target.with(inferrer.(empty, gateway))
74
+
75
+ diff_finder.(current, target)
76
+ }.reject(&:empty?)
66
77
 
67
- def auto_migrate!(gateway, schemas)
68
- runner = InlineRunner.new(gateway)
69
- inferrer = ROM::SQL::Schema::Inferrer.new.suppress_errors
70
- changes = schemas.map { |schema| diff(gateway, inferrer, schema) }.reject(&:empty?)
78
+ runner = migration_runner(options)
71
79
  runner.(changes)
72
80
  end
81
+
82
+ # @api private
83
+ def migration_runner(options)
84
+ if options[:inline]
85
+ Runner.new(InlineRunner.new(connection))
86
+ else
87
+ counter = 0
88
+ writer = Writer.new do |name, content|
89
+ create_file(name, **options, content: content, sequence: counter)
90
+ counter += 1
91
+ end
92
+
93
+ Runner.new(writer)
94
+ end
95
+ end
73
96
  end
74
97
  end
75
98
  end