dm-hibernate-migrations 1.0.0

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 (40) hide show
  1. data/lib/dm-migrations.rb +3 -0
  2. data/lib/dm-migrations/adapters/dm-do-adapter.rb +284 -0
  3. data/lib/dm-migrations/adapters/dm-mysql-adapter.rb +283 -0
  4. data/lib/dm-migrations/adapters/dm-oracle-adapter.rb +321 -0
  5. data/lib/dm-migrations/adapters/dm-postgres-adapter.rb +159 -0
  6. data/lib/dm-migrations/adapters/dm-sqlite-adapter.rb +96 -0
  7. data/lib/dm-migrations/adapters/dm-sqlserver-adapter.rb +177 -0
  8. data/lib/dm-migrations/adapters/dm-yaml-adapter.rb +23 -0
  9. data/lib/dm-migrations/auto_migration.rb +237 -0
  10. data/lib/dm-migrations/migration.rb +217 -0
  11. data/lib/dm-migrations/migration_runner.rb +85 -0
  12. data/lib/dm-migrations/sql.rb +5 -0
  13. data/lib/dm-migrations/sql/column.rb +5 -0
  14. data/lib/dm-migrations/sql/mysql.rb +53 -0
  15. data/lib/dm-migrations/sql/postgres.rb +78 -0
  16. data/lib/dm-migrations/sql/sqlite.rb +45 -0
  17. data/lib/dm-migrations/sql/table.rb +15 -0
  18. data/lib/dm-migrations/sql/table_creator.rb +102 -0
  19. data/lib/dm-migrations/sql/table_modifier.rb +51 -0
  20. data/lib/spec/example/migration_example_group.rb +73 -0
  21. data/lib/spec/matchers/migration_matchers.rb +106 -0
  22. data/spec/integration/auto_migration_spec.rb +506 -0
  23. data/spec/integration/migration_runner_spec.rb +89 -0
  24. data/spec/integration/migration_spec.rb +138 -0
  25. data/spec/integration/sql_spec.rb +190 -0
  26. data/spec/isolated/require_after_setup_spec.rb +30 -0
  27. data/spec/isolated/require_before_setup_spec.rb +30 -0
  28. data/spec/isolated/require_spec.rb +25 -0
  29. data/spec/rcov.opts +6 -0
  30. data/spec/spec.opts +4 -0
  31. data/spec/spec_helper.rb +16 -0
  32. data/spec/unit/migration_spec.rb +453 -0
  33. data/spec/unit/sql/column_spec.rb +14 -0
  34. data/spec/unit/sql/postgres_spec.rb +97 -0
  35. data/spec/unit/sql/sqlite_extensions_spec.rb +108 -0
  36. data/spec/unit/sql/table_creator_spec.rb +94 -0
  37. data/spec/unit/sql/table_modifier_spec.rb +49 -0
  38. data/spec/unit/sql/table_spec.rb +28 -0
  39. data/spec/unit/sql_spec.rb +7 -0
  40. metadata +157 -0
@@ -0,0 +1,321 @@
1
+ require 'dm-migrations/auto_migration'
2
+ require 'dm-migrations/adapters/dm-do-adapter'
3
+
4
+ module DataMapper
5
+ module Migrations
6
+ module OracleAdapter
7
+
8
+ include DataObjectsAdapter
9
+
10
+ # @api private
11
+ def self.included(base)
12
+ base.extend DataObjectsAdapter::ClassMethods
13
+ base.extend ClassMethods
14
+ end
15
+
16
+ # @api semipublic
17
+ def storage_exists?(storage_name)
18
+ statement = <<-SQL.compress_lines
19
+ SELECT COUNT(*)
20
+ FROM all_tables
21
+ WHERE owner = ?
22
+ AND table_name = ?
23
+ SQL
24
+
25
+ select(statement, schema_name, oracle_upcase(storage_name)).first > 0
26
+ end
27
+
28
+ # @api semipublic
29
+ def sequence_exists?(sequence_name)
30
+ return false unless sequence_name
31
+ statement = <<-SQL.compress_lines
32
+ SELECT COUNT(*)
33
+ FROM all_sequences
34
+ WHERE sequence_owner = ?
35
+ AND sequence_name = ?
36
+ SQL
37
+
38
+ select(statement, schema_name, oracle_upcase(sequence_name)).first > 0
39
+ end
40
+
41
+ # @api semipublic
42
+ def field_exists?(storage_name, field_name)
43
+ statement = <<-SQL.compress_lines
44
+ SELECT COUNT(*)
45
+ FROM all_tab_columns
46
+ WHERE owner = ?
47
+ AND table_name = ?
48
+ AND column_name = ?
49
+ SQL
50
+
51
+ select(statement, schema_name, oracle_upcase(storage_name), oracle_upcase(field_name)).first > 0
52
+ end
53
+
54
+ # @api semipublic
55
+ def storage_fields(storage_name)
56
+ statement = <<-SQL.compress_lines
57
+ SELECT column_name
58
+ FROM all_tab_columns
59
+ WHERE owner = ?
60
+ AND table_name = ?
61
+ SQL
62
+
63
+ select(statement, schema_name, oracle_upcase(storage_name))
64
+ end
65
+
66
+ # @api semipublic
67
+ def create_model_storage(model)
68
+ name = self.name
69
+ properties = model.properties_with_subclasses(name)
70
+ table_name = model.storage_name(name)
71
+ truncate_or_delete = self.class.auto_migrate_with
72
+ table_is_truncated = truncate_or_delete && @truncated_tables && @truncated_tables[table_name]
73
+
74
+ return false if storage_exists?(table_name) && !table_is_truncated
75
+ return false if properties.empty?
76
+
77
+ with_connection do |connection|
78
+ # if table was truncated then check if all columns for properties are present
79
+ # TODO: check all other column definition options
80
+ if table_is_truncated && storage_has_all_fields?(table_name, properties)
81
+ @truncated_tables[table_name] = nil
82
+ else
83
+ # forced drop of table if properties are different
84
+ if truncate_or_delete
85
+ destroy_model_storage(model, true)
86
+ end
87
+
88
+ statements = [ create_table_statement(connection, model, properties) ]
89
+ statements.concat(create_index_statements(model))
90
+ statements.concat(create_unique_index_statements(model))
91
+ statements.concat(create_sequence_statements(model))
92
+
93
+ statements.each do |statement|
94
+ command = connection.create_command(statement)
95
+ command.execute_non_query
96
+ end
97
+ end
98
+
99
+ end
100
+
101
+ true
102
+ end
103
+
104
+ # @api semipublic
105
+ def destroy_model_storage(model, forced = false)
106
+ table_name = model.storage_name(name)
107
+ klass = self.class
108
+ truncate_or_delete = klass.auto_migrate_with
109
+ if storage_exists?(table_name)
110
+ if truncate_or_delete && !forced
111
+ case truncate_or_delete
112
+ when :truncate
113
+ execute(truncate_table_statement(model))
114
+ when :delete
115
+ execute(delete_table_statement(model))
116
+ else
117
+ raise ArgumentError, "Unsupported auto_migrate_with option"
118
+ end
119
+ @truncated_tables ||= {}
120
+ @truncated_tables[table_name] = true
121
+ else
122
+ execute(drop_table_statement(model))
123
+ @truncated_tables[table_name] = nil if @truncated_tables
124
+ end
125
+ end
126
+ # added destroy of sequences
127
+ reset_sequences = klass.auto_migrate_reset_sequences
128
+ table_is_truncated = @truncated_tables && @truncated_tables[table_name]
129
+ unless truncate_or_delete && !reset_sequences && !forced
130
+ if sequence_exists?(model_sequence_name(model))
131
+ statement = if table_is_truncated && !forced
132
+ reset_sequence_statement(model)
133
+ else
134
+ drop_sequence_statement(model)
135
+ end
136
+ execute(statement) if statement
137
+ end
138
+ end
139
+ true
140
+ end
141
+
142
+ private
143
+
144
+ def storage_has_all_fields?(table_name, properties)
145
+ properties.map { |property| oracle_upcase(property.field) }.sort == storage_fields(table_name).sort
146
+ end
147
+
148
+ # If table or column name contains just lowercase characters then do uppercase
149
+ # as uppercase version will be used in Oracle data dictionary tables
150
+ def oracle_upcase(name)
151
+ name =~ /[A-Z]/ ? name : name.upcase
152
+ end
153
+
154
+ module SQL #:nodoc:
155
+ # private ## This cannot be private for current migrations
156
+
157
+ # @api private
158
+ def schema_name
159
+ @schema_name ||= select("SELECT SYS_CONTEXT('userenv','current_schema') FROM dual").first.freeze
160
+ end
161
+
162
+ # @api private
163
+ def create_sequence_statements(model)
164
+ name = self.name
165
+ table_name = model.storage_name(name)
166
+ serial = model.serial(name)
167
+
168
+ statements = []
169
+ if sequence_name = model_sequence_name(model)
170
+ sequence_name = quote_name(sequence_name)
171
+ column_name = quote_name(serial.field)
172
+
173
+ statements << <<-SQL.compress_lines
174
+ CREATE SEQUENCE #{sequence_name} NOCACHE
175
+ SQL
176
+
177
+ # create trigger only if custom sequence name was not specified
178
+ unless serial.options[:sequence]
179
+ statements << <<-SQL.compress_lines
180
+ CREATE OR REPLACE TRIGGER #{quote_name(default_trigger_name(table_name))}
181
+ BEFORE INSERT ON #{quote_name(table_name)} FOR EACH ROW
182
+ BEGIN
183
+ IF inserting THEN
184
+ IF :new.#{column_name} IS NULL THEN
185
+ SELECT #{sequence_name}.NEXTVAL INTO :new.#{column_name} FROM dual;
186
+ END IF;
187
+ END IF;
188
+ END;
189
+ SQL
190
+ end
191
+ end
192
+
193
+ statements
194
+ end
195
+
196
+ # @api private
197
+ def drop_sequence_statement(model)
198
+ if sequence_name = model_sequence_name(model)
199
+ "DROP SEQUENCE #{quote_name(sequence_name)}"
200
+ else
201
+ nil
202
+ end
203
+ end
204
+
205
+ # @api private
206
+ def reset_sequence_statement(model)
207
+ if sequence_name = model_sequence_name(model)
208
+ sequence_name = quote_name(sequence_name)
209
+ <<-SQL.compress_lines
210
+ DECLARE
211
+ cval INTEGER;
212
+ BEGIN
213
+ SELECT #{sequence_name}.NEXTVAL INTO cval FROM dual;
214
+ EXECUTE IMMEDIATE 'ALTER SEQUENCE #{sequence_name} INCREMENT BY -' || cval || ' MINVALUE 0';
215
+ SELECT #{sequence_name}.NEXTVAL INTO cval FROM dual;
216
+ EXECUTE IMMEDIATE 'ALTER SEQUENCE #{sequence_name} INCREMENT BY 1';
217
+ END;
218
+ SQL
219
+ else
220
+ nil
221
+ end
222
+
223
+ end
224
+
225
+ # @api private
226
+ def truncate_table_statement(model)
227
+ "TRUNCATE TABLE #{quote_name(model.storage_name(name))}"
228
+ end
229
+
230
+ # @api private
231
+ def delete_table_statement(model)
232
+ "DELETE FROM #{quote_name(model.storage_name(name))}"
233
+ end
234
+
235
+ private
236
+
237
+ def model_sequence_name(model)
238
+ name = self.name
239
+ table_name = model.storage_name(name)
240
+ serial = model.serial(name)
241
+
242
+ if serial
243
+ serial.options[:sequence] || default_sequence_name(table_name)
244
+ else
245
+ nil
246
+ end
247
+ end
248
+
249
+ def default_sequence_name(table_name)
250
+ # truncate table name if necessary to fit in max length of identifier
251
+ "#{table_name[0,self.class::IDENTIFIER_MAX_LENGTH-4]}_seq"
252
+ end
253
+
254
+ def default_trigger_name(table_name)
255
+ # truncate table name if necessary to fit in max length of identifier
256
+ "#{table_name[0,self.class::IDENTIFIER_MAX_LENGTH-4]}_pkt"
257
+ end
258
+
259
+ end # module SQL
260
+
261
+ include SQL
262
+
263
+ module ClassMethods
264
+ # Types for Oracle databases.
265
+ #
266
+ # @return [Hash] types for Oracle databases.
267
+ #
268
+ # @api private
269
+ def type_map
270
+ length = Property::String::DEFAULT_LENGTH
271
+ precision = Property::Numeric::DEFAULT_PRECISION
272
+ scale = Property::Decimal::DEFAULT_SCALE
273
+
274
+ @type_map ||= {
275
+ Integer => { :primitive => 'NUMBER', :precision => precision, :scale => 0 },
276
+ String => { :primitive => 'VARCHAR2', :length => length },
277
+ Class => { :primitive => 'VARCHAR2', :length => length },
278
+ BigDecimal => { :primitive => 'NUMBER', :precision => precision, :scale => nil },
279
+ Float => { :primitive => 'BINARY_FLOAT', },
280
+ DateTime => { :primitive => 'DATE' },
281
+ Date => { :primitive => 'DATE' },
282
+ Time => { :primitive => 'DATE' },
283
+ TrueClass => { :primitive => 'NUMBER', :precision => 1, :scale => 0 },
284
+ Property::Text => { :primitive => 'CLOB' },
285
+ }.freeze
286
+ end
287
+
288
+ # Use table truncate or delete for auto_migrate! to speed up test execution
289
+ #
290
+ # @param [Symbol] :truncate, :delete or :drop_and_create (or nil)
291
+ # do not specify parameter to return current value
292
+ #
293
+ # @return [Symbol] current value of auto_migrate_with option (nil returned for :drop_and_create)
294
+ #
295
+ # @api semipublic
296
+ def auto_migrate_with(value = :not_specified)
297
+ return @auto_migrate_with if value == :not_specified
298
+ value = nil if value == :drop_and_create
299
+ raise ArgumentError unless [nil, :truncate, :delete].include?(value)
300
+ @auto_migrate_with = value
301
+ end
302
+
303
+ # Set if sequences will or will not be reset during auto_migrate!
304
+ #
305
+ # @param [TrueClass, FalseClass] reset sequences?
306
+ # do not specify parameter to return current value
307
+ #
308
+ # @return [Symbol] current value of auto_migrate_reset_sequences option (default value is true)
309
+ #
310
+ # @api semipublic
311
+ def auto_migrate_reset_sequences(value = :not_specified)
312
+ return @auto_migrate_reset_sequences.nil? ? true : @auto_migrate_reset_sequences if value == :not_specified
313
+ raise ArgumentError unless [true, false].include?(value)
314
+ @auto_migrate_reset_sequences = value
315
+ end
316
+
317
+ end
318
+
319
+ end
320
+ end
321
+ end
@@ -0,0 +1,159 @@
1
+ require 'dm-migrations/auto_migration'
2
+ require 'dm-migrations/adapters/dm-do-adapter'
3
+
4
+ module DataMapper
5
+ module Migrations
6
+ module PostgresAdapter
7
+
8
+ include DataObjectsAdapter
9
+
10
+ # @api private
11
+ def self.included(base)
12
+ base.extend DataObjectsAdapter::ClassMethods
13
+ base.extend ClassMethods
14
+ end
15
+
16
+ # @api semipublic
17
+ def upgrade_model_storage(model)
18
+ without_notices { super }
19
+ end
20
+
21
+ # @api semipublic
22
+ def create_model_storage(model)
23
+ without_notices { super }
24
+ end
25
+
26
+ # @api semipublic
27
+ def destroy_model_storage(model)
28
+ if supports_drop_table_if_exists?
29
+ without_notices { super }
30
+ else
31
+ super
32
+ end
33
+ end
34
+
35
+ module SQL #:nodoc:
36
+ # private ## This cannot be private for current migrations
37
+
38
+ # @api private
39
+ def supports_drop_table_if_exists?
40
+ @supports_drop_table_if_exists ||= postgres_version >= '8.2'
41
+ end
42
+
43
+ # @api private
44
+ def schema_name
45
+ @schema_name ||= select('SELECT current_schema()').first.freeze
46
+ end
47
+
48
+ # @api private
49
+ def postgres_version
50
+ @postgres_version ||= select('SELECT version()').first.split[1].freeze
51
+ end
52
+
53
+ # @api private
54
+ def without_notices
55
+ # execute the block with NOTICE messages disabled
56
+ begin
57
+ execute('SET client_min_messages = warning')
58
+ yield
59
+ ensure
60
+ execute('RESET client_min_messages')
61
+ end
62
+ end
63
+
64
+ # @api private
65
+ def property_schema_hash(property)
66
+ schema = super
67
+
68
+ primitive = property.primitive
69
+
70
+ # Postgres does not support precision and scale for Float
71
+ if primitive == Float
72
+ schema.delete(:precision)
73
+ schema.delete(:scale)
74
+ end
75
+
76
+ if property.kind_of?(Property::Integer)
77
+ min = property.min
78
+ max = property.max
79
+
80
+ schema[:primitive] = integer_column_statement(min..max) if min && max
81
+ end
82
+
83
+ if schema[:serial]
84
+ schema[:primitive] = serial_column_statement(min..max)
85
+ end
86
+
87
+ schema
88
+ end
89
+
90
+ private
91
+
92
+ # Return SQL statement for the integer column
93
+ #
94
+ # @param [Range] range
95
+ # the min/max allowed integers
96
+ #
97
+ # @return [String]
98
+ # the statement to create the integer column
99
+ #
100
+ # @api private
101
+ def integer_column_statement(range)
102
+ min = range.first
103
+ max = range.last
104
+
105
+ smallint = 2**15
106
+ integer = 2**31
107
+ bigint = 2**63
108
+
109
+ if min >= -smallint && max < smallint then 'SMALLINT'
110
+ elsif min >= -integer && max < integer then 'INTEGER'
111
+ elsif min >= -bigint && max < bigint then 'BIGINT'
112
+ else
113
+ raise ArgumentError, "min #{min} and max #{max} exceeds supported range"
114
+ end
115
+ end
116
+
117
+ # Return SQL statement for the serial column
118
+ #
119
+ # @param [Integer] max
120
+ # the max allowed integer
121
+ #
122
+ # @return [String]
123
+ # the statement to create the serial column
124
+ #
125
+ # @api private
126
+ def serial_column_statement(range)
127
+ max = range.last
128
+
129
+ if max.nil? || max < 2**31 then 'SERIAL'
130
+ elsif max < 2**63 then 'BIGSERIAL'
131
+ else
132
+ raise ArgumentError, "min #{range.first} and max #{max} exceeds supported range"
133
+ end
134
+ end
135
+ end # module SQL
136
+
137
+ include SQL
138
+
139
+ module ClassMethods
140
+ # Types for PostgreSQL databases.
141
+ #
142
+ # @return [Hash] types for PostgreSQL databases.
143
+ #
144
+ # @api private
145
+ def type_map
146
+ precision = Property::Numeric::DEFAULT_PRECISION
147
+ scale = Property::Decimal::DEFAULT_SCALE
148
+
149
+ @type_map ||= super.merge(
150
+ Property::Binary => { :primitive => 'BYTEA' },
151
+ BigDecimal => { :primitive => 'NUMERIC', :precision => precision, :scale => scale },
152
+ Float => { :primitive => 'DOUBLE PRECISION' }
153
+ ).freeze
154
+ end
155
+ end
156
+
157
+ end
158
+ end
159
+ end