sbf-dm-migrations 1.3.0.beta
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 +7 -0
- data/.gitignore +38 -0
- data/.rspec +1 -0
- data/.rubocop.yml +468 -0
- data/.travis.yml +52 -0
- data/Gemfile +61 -0
- data/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +4 -0
- data/db/migrations/1_create_people_table.rb +12 -0
- data/db/migrations/2_add_dob_to_people.rb +13 -0
- data/db/migrations/config.rb +4 -0
- data/dm-migrations.gemspec +20 -0
- data/examples/Rakefile +149 -0
- data/examples/sample_migration.rb +58 -0
- data/examples/sample_migration_spec.rb +46 -0
- data/lib/dm-migrations/adapters/dm-do-adapter.rb +304 -0
- data/lib/dm-migrations/adapters/dm-mysql-adapter.rb +306 -0
- data/lib/dm-migrations/adapters/dm-oracle-adapter.rb +339 -0
- data/lib/dm-migrations/adapters/dm-postgres-adapter.rb +152 -0
- data/lib/dm-migrations/adapters/dm-sqlite-adapter.rb +88 -0
- data/lib/dm-migrations/adapters/dm-sqlserver-adapter.rb +184 -0
- data/lib/dm-migrations/adapters/dm-yaml-adapter.rb +21 -0
- data/lib/dm-migrations/auto_migration.rb +227 -0
- data/lib/dm-migrations/exceptions/duplicate_migration.rb +6 -0
- data/lib/dm-migrations/migration.rb +323 -0
- data/lib/dm-migrations/migration_runner.rb +76 -0
- data/lib/dm-migrations/sql/column.rb +5 -0
- data/lib/dm-migrations/sql/mysql.rb +84 -0
- data/lib/dm-migrations/sql/oracle.rb +9 -0
- data/lib/dm-migrations/sql/postgres.rb +89 -0
- data/lib/dm-migrations/sql/sqlite.rb +59 -0
- data/lib/dm-migrations/sql/sqlserver.rb +9 -0
- data/lib/dm-migrations/sql/table.rb +15 -0
- data/lib/dm-migrations/sql/table_creator.rb +105 -0
- data/lib/dm-migrations/sql/table_modifier.rb +57 -0
- data/lib/dm-migrations/sql.rb +7 -0
- data/lib/dm-migrations/version.rb +5 -0
- data/lib/dm-migrations.rb +3 -0
- data/lib/spec/example/migration_example_group.rb +69 -0
- data/lib/spec/matchers/migration_matchers.rb +96 -0
- data/spec/integration/auto_migration_spec.rb +590 -0
- data/spec/integration/auto_upgrade_spec.rb +41 -0
- data/spec/integration/migration_runner_spec.rb +84 -0
- data/spec/integration/migration_spec.rb +156 -0
- data/spec/integration/sql_spec.rb +290 -0
- data/spec/isolated/require_after_setup_spec.rb +24 -0
- data/spec/isolated/require_before_setup_spec.rb +24 -0
- data/spec/isolated/require_spec.rb +23 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/unit/migration_spec.rb +501 -0
- data/spec/unit/sql/column_spec.rb +14 -0
- data/spec/unit/sql/postgres_spec.rb +90 -0
- data/spec/unit/sql/sqlite_extensions_spec.rb +103 -0
- data/spec/unit/sql/table_creator_spec.rb +91 -0
- data/spec/unit/sql/table_modifier_spec.rb +47 -0
- data/spec/unit/sql/table_spec.rb +26 -0
- data/spec/unit/sql_spec.rb +7 -0
- data/tasks/spec.rake +21 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- metadata +120 -0
@@ -0,0 +1,306 @@
|
|
1
|
+
require 'dm-migrations/auto_migration'
|
2
|
+
require 'dm-migrations/adapters/dm-do-adapter'
|
3
|
+
|
4
|
+
module DataMapper
|
5
|
+
module Migrations
|
6
|
+
module MysqlAdapter
|
7
|
+
DEFAULT_ENGINE = 'InnoDB'.freeze
|
8
|
+
DEFAULT_CHARACTER_SET = 'utf8'.freeze
|
9
|
+
DEFAULT_COLLATION = 'utf8_unicode_ci'.freeze
|
10
|
+
MAXIMUM_CHAR_LENGTH = ((2**16) - 1) / 4 # allow room for utf8mb4
|
11
|
+
|
12
|
+
include SQL, DataObjectsAdapter
|
13
|
+
|
14
|
+
# @api private
|
15
|
+
def self.included(base)
|
16
|
+
base.extend DataObjectsAdapter::ClassMethods
|
17
|
+
base.extend ClassMethods
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api semipublic
|
21
|
+
def storage_exists?(storage_name)
|
22
|
+
select('SHOW TABLES LIKE ?', storage_name).first == storage_name
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api semipublic
|
26
|
+
def field_exists?(storage_name, field)
|
27
|
+
result = select("SHOW COLUMNS FROM #{quote_name(storage_name)} LIKE ?", field).first
|
28
|
+
result ? result.field == field : false
|
29
|
+
end
|
30
|
+
|
31
|
+
module SQL # :nodoc:
|
32
|
+
# private ## This cannot be private for current migrations
|
33
|
+
|
34
|
+
# Allows for specification of the default storage engine to use when creating tables via
|
35
|
+
# migrations. Defaults to DEFAULT_ENGINE.
|
36
|
+
#
|
37
|
+
# adapter = DataMapper.setup(:default, 'mysql://localhost/foo')
|
38
|
+
# adapter.storage_engine = 'MyISAM'
|
39
|
+
#
|
40
|
+
# @api public
|
41
|
+
attr_accessor :storage_engine
|
42
|
+
|
43
|
+
# @api private
|
44
|
+
def supports_serial?
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
# @api private
|
49
|
+
def supports_drop_table_if_exists?
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
# @api private
|
54
|
+
def schema_name
|
55
|
+
# TODO: is there a cleaner way to find out the current DB we are connected to?
|
56
|
+
normalized_uri.path.split('/').last
|
57
|
+
end
|
58
|
+
|
59
|
+
# @api private
|
60
|
+
def create_table_statement(connection, model, properties)
|
61
|
+
"#{super} ENGINE = #{storage_engine} CHARACTER SET #{character_set} COLLATE #{collation}"
|
62
|
+
end
|
63
|
+
|
64
|
+
# @api private
|
65
|
+
def property_schema_hash(property)
|
66
|
+
schema = super
|
67
|
+
|
68
|
+
case property.dump_as
|
69
|
+
when Integer.singleton_class
|
70
|
+
if property.respond_to?(:min) && property.respond_to?(:max)
|
71
|
+
min = property.min
|
72
|
+
max = property.max
|
73
|
+
schema[:primitive] = integer_column_statement(min..max) if min && max
|
74
|
+
end
|
75
|
+
when String.singleton_class
|
76
|
+
if property.is_a?(Property::Text)
|
77
|
+
schema[:primitive] = text_column_statement(property.length)
|
78
|
+
schema.delete(:default)
|
79
|
+
else
|
80
|
+
schema[:length] ||= MAXIMUM_CHAR_LENGTH
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
schema
|
85
|
+
end
|
86
|
+
|
87
|
+
# @api private
|
88
|
+
def property_schema_statement(connection, schema)
|
89
|
+
statement = super
|
90
|
+
|
91
|
+
statement << ' AUTO_INCREMENT' if supports_serial? && schema[:serial]
|
92
|
+
|
93
|
+
statement
|
94
|
+
end
|
95
|
+
|
96
|
+
# @api private
|
97
|
+
def storage_engine
|
98
|
+
# Don't pull the default engine via show_variable for backwards compat where it was hard
|
99
|
+
# coded to InnoDB
|
100
|
+
@storage_engine ||= DEFAULT_ENGINE
|
101
|
+
end
|
102
|
+
|
103
|
+
# @api private
|
104
|
+
def character_set
|
105
|
+
@character_set ||= show_variable('character_set_connection') || DEFAULT_CHARACTER_SET
|
106
|
+
end
|
107
|
+
|
108
|
+
# @api private
|
109
|
+
def collation
|
110
|
+
@collation ||= show_variable('collation_connection') || DEFAULT_COLLATION
|
111
|
+
end
|
112
|
+
|
113
|
+
# @api private
|
114
|
+
def show_variable(name)
|
115
|
+
result = select('SHOW VARIABLES LIKE ?', name).first
|
116
|
+
result ? result.value.freeze : nil
|
117
|
+
end
|
118
|
+
|
119
|
+
# Return SQL statement for the text column
|
120
|
+
#
|
121
|
+
# @param [Integer] length
|
122
|
+
# the max allowed length
|
123
|
+
#
|
124
|
+
# @return [String]
|
125
|
+
# the statement to create the text column
|
126
|
+
#
|
127
|
+
# @api private
|
128
|
+
private def text_column_statement(length)
|
129
|
+
if length < 2**8
|
130
|
+
'TINYTEXT'
|
131
|
+
elsif length < 2**16
|
132
|
+
'TEXT'
|
133
|
+
elsif length < 2**24
|
134
|
+
'MEDIUMTEXT'
|
135
|
+
elsif length < 2**32
|
136
|
+
'LONGTEXT'
|
137
|
+
|
138
|
+
# http://www.postgresql.org/files/documentation/books/aw_pgsql/node90.html
|
139
|
+
# Implies that PostgreSQL doesn't have a size limit on text
|
140
|
+
# fields, so this param validation happens here instead of
|
141
|
+
# DM::Property#initialize.
|
142
|
+
else
|
143
|
+
raise ArgumentError, "length of #{length} exceeds maximum size supported"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Return SQL statement for the integer column
|
148
|
+
#
|
149
|
+
# @param [Range] range
|
150
|
+
# the min/max allowed integers
|
151
|
+
#
|
152
|
+
# @return [String]
|
153
|
+
# the statement to create the integer column
|
154
|
+
#
|
155
|
+
# @api private
|
156
|
+
private def integer_column_statement(range)
|
157
|
+
format('%s(%d)%s', integer_column_type(range), integer_display_size(range), integer_statement_sign(range))
|
158
|
+
end
|
159
|
+
|
160
|
+
# Return the integer column type
|
161
|
+
#
|
162
|
+
# Use the smallest available column type that will satisfy the
|
163
|
+
# allowable range of numbers
|
164
|
+
#
|
165
|
+
# @param [Range] range
|
166
|
+
# the min/max allowed integers
|
167
|
+
#
|
168
|
+
# @return [String]
|
169
|
+
# the column type
|
170
|
+
#
|
171
|
+
# @api private
|
172
|
+
private def integer_column_type(range)
|
173
|
+
if range.first < 0
|
174
|
+
signed_integer_column_type(range)
|
175
|
+
else
|
176
|
+
unsigned_integer_column_type(range)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Return the signed integer column type
|
181
|
+
#
|
182
|
+
# @param [Range] range
|
183
|
+
# the min/max allowed integers
|
184
|
+
#
|
185
|
+
# @return [String]
|
186
|
+
#
|
187
|
+
# @api private
|
188
|
+
private def signed_integer_column_type(range)
|
189
|
+
min = range.first
|
190
|
+
max = range.last
|
191
|
+
|
192
|
+
tinyint = 2**7
|
193
|
+
smallint = 2**15
|
194
|
+
integer = 2**31
|
195
|
+
mediumint = 2**23
|
196
|
+
bigint = 2**63
|
197
|
+
|
198
|
+
if min >= -tinyint && max < tinyint
|
199
|
+
'TINYINT'
|
200
|
+
elsif min >= -smallint && max < smallint
|
201
|
+
'SMALLINT'
|
202
|
+
elsif min >= -mediumint && max < mediumint
|
203
|
+
'MEDIUMINT'
|
204
|
+
elsif min >= -integer && max < integer
|
205
|
+
'INT'
|
206
|
+
elsif min >= -bigint && max < bigint
|
207
|
+
'BIGINT'
|
208
|
+
else
|
209
|
+
raise ArgumentError, "min #{min} and max #{max} exceeds supported range"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Return the unsigned integer column type
|
214
|
+
#
|
215
|
+
# @param [Range] range
|
216
|
+
# the min/max allowed integers
|
217
|
+
#
|
218
|
+
# @return [String]
|
219
|
+
#
|
220
|
+
# @api private
|
221
|
+
private def unsigned_integer_column_type(range)
|
222
|
+
max = range.last
|
223
|
+
|
224
|
+
if max < 2**8
|
225
|
+
'TINYINT'
|
226
|
+
elsif max < 2**16
|
227
|
+
'SMALLINT'
|
228
|
+
elsif max < 2**24
|
229
|
+
'MEDIUMINT'
|
230
|
+
elsif max < 2**32
|
231
|
+
'INT'
|
232
|
+
elsif max < 2**64
|
233
|
+
'BIGINT'
|
234
|
+
else
|
235
|
+
raise ArgumentError, "min #{range.first} and max #{max} exceeds supported range"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# Return the integer column display size
|
240
|
+
#
|
241
|
+
# Adjust the display size to match the maximum number of
|
242
|
+
# expected digits. This is more for documentation purposes
|
243
|
+
# and does not affect what can actually be stored in a
|
244
|
+
# specific column
|
245
|
+
#
|
246
|
+
# @param [Range] range
|
247
|
+
# the min/max allowed integers
|
248
|
+
#
|
249
|
+
# @return [Integer]
|
250
|
+
# the display size for the integer
|
251
|
+
#
|
252
|
+
# @api private
|
253
|
+
private def integer_display_size(range)
|
254
|
+
[range.first.to_s.length, range.last.to_s.length].max
|
255
|
+
end
|
256
|
+
|
257
|
+
# Return the integer sign statement
|
258
|
+
#
|
259
|
+
# @param [Range] range
|
260
|
+
# the min/max allowed integers
|
261
|
+
#
|
262
|
+
# @return [String, nil]
|
263
|
+
# statement if unsigned, nil if signed
|
264
|
+
#
|
265
|
+
# @api private
|
266
|
+
private def integer_statement_sign(range)
|
267
|
+
' UNSIGNED' unless range.first < 0
|
268
|
+
end
|
269
|
+
|
270
|
+
# @api private
|
271
|
+
private def indexes(model)
|
272
|
+
filter_indexes(model, super)
|
273
|
+
end
|
274
|
+
|
275
|
+
# @api private
|
276
|
+
private def unique_indexes(model)
|
277
|
+
filter_indexes(model, super)
|
278
|
+
end
|
279
|
+
|
280
|
+
# Filter out any indexes with a non index-able column in MySQL
|
281
|
+
#
|
282
|
+
# @api private
|
283
|
+
private def filter_indexes(model, indexes)
|
284
|
+
field_map = model.properties(name).field_map
|
285
|
+
indexes.select do |_index_name, fields|
|
286
|
+
fields.all? { |field| !field_map[field].is_a?(Property::Text) }
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
module ClassMethods
|
292
|
+
# Types for MySQL databases.
|
293
|
+
#
|
294
|
+
# @return [Hash] types for MySQL databases.
|
295
|
+
#
|
296
|
+
# @api private
|
297
|
+
def type_map
|
298
|
+
super.merge(
|
299
|
+
DateTime => {primitive: 'DATETIME'},
|
300
|
+
Time => {primitive: 'DATETIME'}
|
301
|
+
).freeze
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
@@ -0,0 +1,339 @@
|
|
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
|
+
include SQL, DataObjectsAdapter
|
8
|
+
|
9
|
+
# @api private
|
10
|
+
def self.included(base)
|
11
|
+
base.extend DataObjectsAdapter::ClassMethods
|
12
|
+
base.extend ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
# @api semipublic
|
16
|
+
def storage_exists?(storage_name)
|
17
|
+
statement = DataMapper::Ext::String.compress_lines(<<-SQL)
|
18
|
+
SELECT COUNT(*)
|
19
|
+
FROM all_tables
|
20
|
+
WHERE owner = ?
|
21
|
+
AND table_name = ?
|
22
|
+
SQL
|
23
|
+
|
24
|
+
select(statement, schema_name, oracle_upcase(storage_name)).first > 0
|
25
|
+
end
|
26
|
+
|
27
|
+
# @api semipublic
|
28
|
+
def sequence_exists?(sequence_name)
|
29
|
+
return false unless sequence_name
|
30
|
+
|
31
|
+
statement = DataMapper::Ext::String.compress_lines(<<-SQL)
|
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 = DataMapper::Ext::String.compress_lines(<<-SQL)
|
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 = DataMapper::Ext::String.compress_lines(<<-SQL)
|
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
|
+
def drop_table_statement(model)
|
67
|
+
table_name = quote_name(model.storage_name(name))
|
68
|
+
"DROP TABLE #{table_name} CASCADE CONSTRAINTS"
|
69
|
+
end
|
70
|
+
|
71
|
+
# @api semipublic
|
72
|
+
def create_model_storage(model)
|
73
|
+
name = self.name
|
74
|
+
properties = model.properties_with_subclasses(name)
|
75
|
+
table_name = model.storage_name(name)
|
76
|
+
truncate_or_delete = self.class.auto_migrate_with
|
77
|
+
table_is_truncated = truncate_or_delete && @truncated_tables && @truncated_tables[table_name]
|
78
|
+
|
79
|
+
return false if storage_exists?(table_name) && !table_is_truncated
|
80
|
+
return false if properties.empty?
|
81
|
+
|
82
|
+
with_connection do |connection|
|
83
|
+
# if table was truncated then check if all columns for properties are present
|
84
|
+
# TODO: check all other column definition options
|
85
|
+
if table_is_truncated && storage_has_all_fields?(table_name, properties)
|
86
|
+
@truncated_tables[table_name] = nil
|
87
|
+
else
|
88
|
+
# forced drop of table if properties are different
|
89
|
+
destroy_model_storage(model, true) if truncate_or_delete
|
90
|
+
|
91
|
+
statements = [create_table_statement(connection, model, properties)]
|
92
|
+
statements.concat(create_index_statements(model))
|
93
|
+
statements.concat(create_unique_index_statements(model))
|
94
|
+
statements.concat(create_sequence_statements(model))
|
95
|
+
|
96
|
+
statements.each do |statement|
|
97
|
+
command = connection.create_command(statement)
|
98
|
+
command.execute_non_query
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
true
|
104
|
+
end
|
105
|
+
|
106
|
+
# @api semipublic
|
107
|
+
def destroy_model_storage(model, forced = false)
|
108
|
+
table_name = model.storage_name(name)
|
109
|
+
klass = self.class
|
110
|
+
truncate_or_delete = klass.auto_migrate_with
|
111
|
+
if storage_exists?(table_name)
|
112
|
+
if truncate_or_delete && !forced
|
113
|
+
case truncate_or_delete
|
114
|
+
when :truncate
|
115
|
+
execute(truncate_table_statement(model))
|
116
|
+
when :delete
|
117
|
+
execute(delete_table_statement(model))
|
118
|
+
else
|
119
|
+
raise ArgumentError, 'Unsupported auto_migrate_with option'
|
120
|
+
end
|
121
|
+
@truncated_tables ||= {}
|
122
|
+
@truncated_tables[table_name] = true
|
123
|
+
else
|
124
|
+
execute(drop_table_statement(model))
|
125
|
+
@truncated_tables[table_name] = nil if @truncated_tables
|
126
|
+
end
|
127
|
+
end
|
128
|
+
# added destroy of sequences
|
129
|
+
reset_sequences = klass.auto_migrate_reset_sequences
|
130
|
+
table_is_truncated = @truncated_tables && @truncated_tables[table_name]
|
131
|
+
unless truncate_or_delete && !reset_sequences && !forced
|
132
|
+
if sequence_exists?(model_sequence_name(model))
|
133
|
+
statement = if table_is_truncated && !forced
|
134
|
+
reset_sequence_statement(model)
|
135
|
+
else
|
136
|
+
drop_sequence_statement(model)
|
137
|
+
end
|
138
|
+
execute(statement) if statement
|
139
|
+
end
|
140
|
+
end
|
141
|
+
true
|
142
|
+
end
|
143
|
+
|
144
|
+
private 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
|
+
private 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 property_schema_hash(property)
|
164
|
+
schema = super
|
165
|
+
|
166
|
+
if property.is_a?(Property::Binary)
|
167
|
+
# BLOB does not support length
|
168
|
+
schema.delete(:length)
|
169
|
+
end
|
170
|
+
|
171
|
+
if property.primitive == String && property.length > 4000
|
172
|
+
# Oracle doesn't support string over 4000 bytes
|
173
|
+
schema[:primitive] = 'NCLOB'
|
174
|
+
# CLOB and NCLOB do not support length
|
175
|
+
schema.delete(:length)
|
176
|
+
end
|
177
|
+
|
178
|
+
schema
|
179
|
+
end
|
180
|
+
|
181
|
+
# @api private
|
182
|
+
def create_sequence_statements(model)
|
183
|
+
name = self.name
|
184
|
+
table_name = model.storage_name(name)
|
185
|
+
serial = model.serial(name)
|
186
|
+
|
187
|
+
statements = []
|
188
|
+
if (sequence_name = model_sequence_name(model))
|
189
|
+
sequence_name = quote_name(sequence_name)
|
190
|
+
column_name = quote_name(serial.field)
|
191
|
+
|
192
|
+
statements << DataMapper::Ext::String.compress_lines(<<-SQL)
|
193
|
+
CREATE SEQUENCE #{sequence_name} NOCACHE
|
194
|
+
SQL
|
195
|
+
|
196
|
+
# create trigger only if custom sequence name was not specified
|
197
|
+
unless serial.options[:sequence]
|
198
|
+
statements << DataMapper::Ext::String.compress_lines(<<-SQL)
|
199
|
+
CREATE OR REPLACE TRIGGER #{quote_name(default_trigger_name(table_name))}
|
200
|
+
BEFORE INSERT ON #{quote_name(table_name)} FOR EACH ROW
|
201
|
+
BEGIN
|
202
|
+
IF inserting THEN
|
203
|
+
IF :new.#{column_name} IS NULL THEN
|
204
|
+
SELECT #{sequence_name}.NEXTVAL INTO :new.#{column_name} FROM dual;
|
205
|
+
END IF;
|
206
|
+
END IF;
|
207
|
+
END;
|
208
|
+
SQL
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
statements
|
213
|
+
end
|
214
|
+
|
215
|
+
# @api private
|
216
|
+
def drop_sequence_statement(model)
|
217
|
+
return unless (sequence_name = model_sequence_name(model))
|
218
|
+
|
219
|
+
"DROP SEQUENCE #{quote_name(sequence_name)}"
|
220
|
+
end
|
221
|
+
|
222
|
+
# @api private
|
223
|
+
def reset_sequence_statement(model)
|
224
|
+
return unless (sequence_name = model_sequence_name(model))
|
225
|
+
|
226
|
+
sequence_name = quote_name(sequence_name)
|
227
|
+
DataMapper::Ext::String.compress_lines(<<-SQL)
|
228
|
+
DECLARE
|
229
|
+
cval INTEGER;
|
230
|
+
BEGIN
|
231
|
+
SELECT #{sequence_name}.NEXTVAL INTO cval FROM dual;
|
232
|
+
EXECUTE IMMEDIATE 'ALTER SEQUENCE #{sequence_name} INCREMENT BY -' || cval || ' MINVALUE 0';
|
233
|
+
SELECT #{sequence_name}.NEXTVAL INTO cval FROM dual;
|
234
|
+
EXECUTE IMMEDIATE 'ALTER SEQUENCE #{sequence_name} INCREMENT BY 1';
|
235
|
+
END;
|
236
|
+
SQL
|
237
|
+
end
|
238
|
+
|
239
|
+
# @api private
|
240
|
+
def truncate_table_statement(model)
|
241
|
+
"TRUNCATE TABLE #{quote_name(model.storage_name(name))}"
|
242
|
+
end
|
243
|
+
|
244
|
+
# @api private
|
245
|
+
def delete_table_statement(model)
|
246
|
+
"DELETE FROM #{quote_name(model.storage_name(name))}"
|
247
|
+
end
|
248
|
+
|
249
|
+
private def model_sequence_name(model)
|
250
|
+
name = self.name
|
251
|
+
table_name = model.storage_name(name)
|
252
|
+
serial = model.serial(name)
|
253
|
+
|
254
|
+
return unless serial
|
255
|
+
|
256
|
+
serial.options[:sequence] || default_sequence_name(table_name)
|
257
|
+
end
|
258
|
+
|
259
|
+
private def default_sequence_name(table_name)
|
260
|
+
# truncate table name if necessary to fit in max length of identifier
|
261
|
+
"#{table_name[0, self.class::IDENTIFIER_MAX_LENGTH - 4]}_seq"
|
262
|
+
end
|
263
|
+
|
264
|
+
private def default_trigger_name(table_name)
|
265
|
+
# truncate table name if necessary to fit in max length of identifier
|
266
|
+
"#{table_name[0, self.class::IDENTIFIER_MAX_LENGTH - 4]}_pkt"
|
267
|
+
end
|
268
|
+
|
269
|
+
# @api private
|
270
|
+
private def add_column_statement
|
271
|
+
'ADD'
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
module ClassMethods
|
276
|
+
# Types for Oracle databases.
|
277
|
+
#
|
278
|
+
# @return [Hash] types for Oracle databases.
|
279
|
+
#
|
280
|
+
# @api private
|
281
|
+
def type_map
|
282
|
+
length = Property::String.length
|
283
|
+
precision = Property::Numeric.precision
|
284
|
+
_scale = Property::Decimal.scale
|
285
|
+
|
286
|
+
super.merge(
|
287
|
+
Integer => {primitive: 'NUMBER', precision: precision, scale: 0},
|
288
|
+
String => {primitive: 'VARCHAR2', length: length},
|
289
|
+
Class => {primitive: 'VARCHAR2', length: length},
|
290
|
+
BigDecimal => {primitive: 'NUMBER', precision: precision, scale: nil},
|
291
|
+
Float => {primitive: 'BINARY_FLOAT'},
|
292
|
+
DateTime => {primitive: 'DATE'},
|
293
|
+
Date => {primitive: 'DATE'},
|
294
|
+
Time => {primitive: 'DATE'},
|
295
|
+
TrueClass => {primitive: 'NUMBER', precision: 1, scale: 0},
|
296
|
+
Property::Text => {primitive: 'CLOB'}
|
297
|
+
).freeze
|
298
|
+
end
|
299
|
+
|
300
|
+
# Use table truncate or delete for auto_migrate! to speed up test execution
|
301
|
+
#
|
302
|
+
# @param [Symbol] :truncate, :delete or :drop_and_create (or nil)
|
303
|
+
# do not specify parameter to return current value
|
304
|
+
#
|
305
|
+
# @return [Mixed]
|
306
|
+
# [Symbol] current value of auto_migrate_with option
|
307
|
+
# (nil returned for :drop_and_create)
|
308
|
+
#
|
309
|
+
# @api semipublic
|
310
|
+
def auto_migrate_with(value = :not_specified)
|
311
|
+
return @auto_migrate_with if value == :not_specified
|
312
|
+
|
313
|
+
value = nil if value == :drop_and_create
|
314
|
+
raise ArgumentError unless [nil, :truncate, :delete].include?(value)
|
315
|
+
|
316
|
+
@auto_migrate_with = value
|
317
|
+
end
|
318
|
+
|
319
|
+
# Set if sequences will or will not be reset during auto_migrate!
|
320
|
+
#
|
321
|
+
# @param [Mixed] value
|
322
|
+
# reset sequences? TrueClass, FalseClass
|
323
|
+
# do not specify parameter to return current value
|
324
|
+
#
|
325
|
+
# @return [Mixed]
|
326
|
+
# [Symbol] current value of auto_migrate_reset_sequences option
|
327
|
+
# (default value is true)
|
328
|
+
#
|
329
|
+
# @api semipublic
|
330
|
+
def auto_migrate_reset_sequences(value = :not_specified)
|
331
|
+
return @auto_migrate_reset_sequences.nil? ? true : @auto_migrate_reset_sequences if value == :not_specified
|
332
|
+
raise ArgumentError unless [true, false].include?(value)
|
333
|
+
|
334
|
+
@auto_migrate_reset_sequences = value
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|