dm-hibernate-migrations 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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