dm-core 0.10.2 → 1.0.0.rc1

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 (183) hide show
  1. data/.gitignore +10 -1
  2. data/Gemfile +143 -0
  3. data/Rakefile +9 -5
  4. data/VERSION +1 -1
  5. data/dm-core.gemspec +160 -57
  6. data/lib/dm-core.rb +131 -56
  7. data/lib/dm-core/adapters.rb +98 -14
  8. data/lib/dm-core/adapters/abstract_adapter.rb +24 -4
  9. data/lib/dm-core/adapters/in_memory_adapter.rb +7 -2
  10. data/lib/dm-core/associations/many_to_many.rb +19 -30
  11. data/lib/dm-core/associations/many_to_one.rb +58 -42
  12. data/lib/dm-core/associations/one_to_many.rb +33 -23
  13. data/lib/dm-core/associations/one_to_one.rb +27 -11
  14. data/lib/dm-core/associations/relationship.rb +4 -4
  15. data/lib/dm-core/collection.rb +23 -16
  16. data/lib/dm-core/core_ext/array.rb +36 -0
  17. data/lib/dm-core/core_ext/hash.rb +30 -0
  18. data/lib/dm-core/core_ext/module.rb +46 -0
  19. data/lib/dm-core/core_ext/object.rb +31 -0
  20. data/lib/dm-core/core_ext/pathname.rb +20 -0
  21. data/lib/dm-core/core_ext/string.rb +22 -0
  22. data/lib/dm-core/core_ext/try_dup.rb +44 -0
  23. data/lib/dm-core/model.rb +88 -27
  24. data/lib/dm-core/model/hook.rb +75 -18
  25. data/lib/dm-core/model/property.rb +50 -9
  26. data/lib/dm-core/model/relationship.rb +31 -31
  27. data/lib/dm-core/model/scope.rb +3 -3
  28. data/lib/dm-core/property.rb +196 -516
  29. data/lib/dm-core/property/binary.rb +7 -0
  30. data/lib/dm-core/property/boolean.rb +35 -0
  31. data/lib/dm-core/property/class.rb +24 -0
  32. data/lib/dm-core/property/date.rb +47 -0
  33. data/lib/dm-core/property/date_time.rb +48 -0
  34. data/lib/dm-core/property/decimal.rb +43 -0
  35. data/lib/dm-core/property/discriminator.rb +48 -0
  36. data/lib/dm-core/property/float.rb +24 -0
  37. data/lib/dm-core/property/integer.rb +32 -0
  38. data/lib/dm-core/property/numeric.rb +43 -0
  39. data/lib/dm-core/property/object.rb +32 -0
  40. data/lib/dm-core/property/serial.rb +8 -0
  41. data/lib/dm-core/property/string.rb +49 -0
  42. data/lib/dm-core/property/text.rb +12 -0
  43. data/lib/dm-core/property/time.rb +48 -0
  44. data/lib/dm-core/property/typecast/numeric.rb +32 -0
  45. data/lib/dm-core/property/typecast/time.rb +28 -0
  46. data/lib/dm-core/property_set.rb +10 -4
  47. data/lib/dm-core/query.rb +14 -37
  48. data/lib/dm-core/query/conditions/comparison.rb +8 -6
  49. data/lib/dm-core/query/conditions/operation.rb +33 -2
  50. data/lib/dm-core/query/operator.rb +2 -5
  51. data/lib/dm-core/query/path.rb +4 -6
  52. data/lib/dm-core/repository.rb +21 -6
  53. data/lib/dm-core/resource.rb +316 -133
  54. data/lib/dm-core/resource/state.rb +79 -0
  55. data/lib/dm-core/resource/state/clean.rb +40 -0
  56. data/lib/dm-core/resource/state/deleted.rb +30 -0
  57. data/lib/dm-core/resource/state/dirty.rb +86 -0
  58. data/lib/dm-core/resource/state/immutable.rb +34 -0
  59. data/lib/dm-core/resource/state/persisted.rb +29 -0
  60. data/lib/dm-core/resource/state/transient.rb +70 -0
  61. data/lib/dm-core/spec/lib/adapter_helpers.rb +52 -0
  62. data/lib/dm-core/spec/lib/collection_helpers.rb +20 -0
  63. data/{spec → lib/dm-core/spec}/lib/counter_adapter.rb +5 -1
  64. data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
  65. data/lib/dm-core/spec/lib/spec_helper.rb +68 -0
  66. data/lib/dm-core/spec/setup.rb +165 -0
  67. data/lib/dm-core/spec/{adapter_shared_spec.rb → shared/adapter_spec.rb} +21 -7
  68. data/{spec/public/shared/resource_shared_spec.rb → lib/dm-core/spec/shared/resource_spec.rb} +120 -83
  69. data/{spec/public/shared/sel_shared_spec.rb → lib/dm-core/spec/shared/sel_spec.rb} +5 -6
  70. data/lib/dm-core/support/assertions.rb +8 -0
  71. data/lib/dm-core/support/equalizer.rb +1 -0
  72. data/lib/dm-core/support/hook.rb +420 -0
  73. data/lib/dm-core/support/lazy_array.rb +453 -0
  74. data/lib/dm-core/support/local_object_space.rb +12 -0
  75. data/lib/dm-core/support/logger.rb +193 -6
  76. data/lib/dm-core/support/naming_conventions.rb +8 -8
  77. data/lib/dm-core/support/subject.rb +33 -0
  78. data/lib/dm-core/type.rb +4 -0
  79. data/lib/dm-core/types/boolean.rb +2 -0
  80. data/lib/dm-core/types/decimal.rb +9 -0
  81. data/lib/dm-core/types/discriminator.rb +2 -0
  82. data/lib/dm-core/types/object.rb +3 -0
  83. data/lib/dm-core/types/serial.rb +2 -0
  84. data/lib/dm-core/types/text.rb +2 -0
  85. data/lib/dm-core/version.rb +1 -1
  86. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +67 -0
  87. data/spec/public/model/hook_spec.rb +209 -0
  88. data/spec/public/model/property_spec.rb +35 -0
  89. data/spec/public/model/relationship_spec.rb +33 -20
  90. data/spec/public/model_spec.rb +142 -10
  91. data/spec/public/property/binary_spec.rb +14 -0
  92. data/spec/public/property/boolean_spec.rb +14 -0
  93. data/spec/public/property/class_spec.rb +20 -0
  94. data/spec/public/property/date_spec.rb +14 -0
  95. data/spec/public/property/date_time_spec.rb +14 -0
  96. data/spec/public/property/decimal_spec.rb +14 -0
  97. data/spec/public/{types → property}/discriminator_spec.rb +2 -12
  98. data/spec/public/property/float_spec.rb +14 -0
  99. data/spec/public/property/integer_spec.rb +14 -0
  100. data/spec/public/property/object_spec.rb +9 -17
  101. data/spec/public/property/serial_spec.rb +14 -0
  102. data/spec/public/property/string_spec.rb +14 -0
  103. data/spec/public/property/text_spec.rb +52 -0
  104. data/spec/public/property/time_spec.rb +14 -0
  105. data/spec/public/property_spec.rb +28 -87
  106. data/spec/public/resource_spec.rb +101 -0
  107. data/spec/public/sel_spec.rb +5 -15
  108. data/spec/public/shared/collection_shared_spec.rb +16 -30
  109. data/spec/public/shared/finder_shared_spec.rb +2 -4
  110. data/spec/public/shared/property_shared_spec.rb +176 -0
  111. data/spec/semipublic/adapters/abstract_adapter_spec.rb +1 -1
  112. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +2 -2
  113. data/spec/semipublic/associations/many_to_many_spec.rb +89 -0
  114. data/spec/semipublic/associations/many_to_one_spec.rb +24 -1
  115. data/spec/semipublic/associations/one_to_many_spec.rb +51 -0
  116. data/spec/semipublic/associations/one_to_one_spec.rb +49 -0
  117. data/spec/semipublic/associations/relationship_spec.rb +3 -3
  118. data/spec/semipublic/associations_spec.rb +1 -1
  119. data/spec/semipublic/property/binary_spec.rb +13 -0
  120. data/spec/semipublic/property/boolean_spec.rb +65 -0
  121. data/spec/semipublic/property/class_spec.rb +33 -0
  122. data/spec/semipublic/property/date_spec.rb +43 -0
  123. data/spec/semipublic/property/date_time_spec.rb +46 -0
  124. data/spec/semipublic/property/decimal_spec.rb +82 -0
  125. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  126. data/spec/semipublic/property/float_spec.rb +82 -0
  127. data/spec/semipublic/property/integer_spec.rb +82 -0
  128. data/spec/semipublic/property/serial_spec.rb +13 -0
  129. data/spec/semipublic/property/string_spec.rb +13 -0
  130. data/spec/semipublic/property/text_spec.rb +31 -0
  131. data/spec/semipublic/property/time_spec.rb +50 -0
  132. data/spec/semipublic/property_spec.rb +2 -532
  133. data/spec/semipublic/query/conditions/comparison_spec.rb +171 -169
  134. data/spec/semipublic/query/conditions/operation_spec.rb +53 -51
  135. data/spec/semipublic/query/path_spec.rb +17 -17
  136. data/spec/semipublic/query_spec.rb +47 -78
  137. data/spec/semipublic/resource/state/clean_spec.rb +88 -0
  138. data/spec/semipublic/resource/state/deleted_spec.rb +78 -0
  139. data/spec/semipublic/resource/state/dirty_spec.rb +133 -0
  140. data/spec/semipublic/resource/state/immutable_spec.rb +99 -0
  141. data/spec/semipublic/resource/state/transient_spec.rb +128 -0
  142. data/spec/semipublic/resource/state_spec.rb +226 -0
  143. data/spec/semipublic/shared/property_shared_spec.rb +143 -0
  144. data/spec/semipublic/shared/resource_shared_spec.rb +16 -15
  145. data/spec/semipublic/shared/resource_state_shared_spec.rb +78 -0
  146. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  147. data/spec/spec_helper.rb +21 -97
  148. data/spec/support/types/huge_integer.rb +17 -0
  149. data/spec/unit/array_spec.rb +48 -0
  150. data/spec/unit/hash_spec.rb +35 -0
  151. data/spec/unit/hook_spec.rb +1234 -0
  152. data/spec/unit/lazy_array_spec.rb +1959 -0
  153. data/spec/unit/module_spec.rb +70 -0
  154. data/spec/unit/object_spec.rb +37 -0
  155. data/spec/unit/try_dup_spec.rb +45 -0
  156. data/tasks/local_gemfile.rake +18 -0
  157. data/tasks/spec.rake +0 -3
  158. metadata +197 -71
  159. data/deps.rip +0 -2
  160. data/lib/dm-core/adapters/data_objects_adapter.rb +0 -712
  161. data/lib/dm-core/adapters/mysql_adapter.rb +0 -42
  162. data/lib/dm-core/adapters/oracle_adapter.rb +0 -229
  163. data/lib/dm-core/adapters/postgres_adapter.rb +0 -22
  164. data/lib/dm-core/adapters/sqlite3_adapter.rb +0 -17
  165. data/lib/dm-core/adapters/sqlserver_adapter.rb +0 -114
  166. data/lib/dm-core/adapters/yaml_adapter.rb +0 -111
  167. data/lib/dm-core/core_ext/enumerable.rb +0 -28
  168. data/lib/dm-core/migrations.rb +0 -1427
  169. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +0 -366
  170. data/lib/dm-core/transaction.rb +0 -508
  171. data/lib/dm-core/types/paranoid_boolean.rb +0 -42
  172. data/lib/dm-core/types/paranoid_datetime.rb +0 -41
  173. data/spec/lib/adapter_helpers.rb +0 -105
  174. data/spec/lib/collection_helpers.rb +0 -18
  175. data/spec/lib/pending_helpers.rb +0 -46
  176. data/spec/public/migrations_spec.rb +0 -503
  177. data/spec/public/transaction_spec.rb +0 -153
  178. data/spec/semipublic/adapters/mysql_adapter_spec.rb +0 -17
  179. data/spec/semipublic/adapters/oracle_adapter_spec.rb +0 -194
  180. data/spec/semipublic/adapters/postgres_adapter_spec.rb +0 -17
  181. data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +0 -17
  182. data/spec/semipublic/adapters/sqlserver_adapter_spec.rb +0 -17
  183. data/spec/semipublic/adapters/yaml_adapter_spec.rb +0 -12
@@ -1,28 +0,0 @@
1
- module Enumerable
2
- def empty?
3
- each { return false }
4
- true
5
- end
6
-
7
- def one?
8
- return one? { |entry| entry } unless block_given?
9
-
10
- matches = 0
11
- each do |entry|
12
- matches += 1 if yield(entry)
13
- return false if matches > 1
14
- end
15
- matches == 1
16
- end
17
-
18
- def first
19
- each { |entry| return entry }
20
- nil
21
- end
22
-
23
- def size
24
- size = 0
25
- each { size += 1 }
26
- size
27
- end
28
- end
@@ -1,1427 +0,0 @@
1
- # TODO: move to dm-more/dm-migrations
2
-
3
- module DataMapper
4
- module Migrations
5
- module SingletonMethods
6
- # destructively migrates the repository upwards to match model definitions
7
- #
8
- # @param [Symbol] name repository to act on, :default is the default
9
- #
10
- # @api public
11
- def migrate!(repository_name = nil)
12
- repository(repository_name).migrate!
13
- end
14
-
15
- # drops and recreates the repository upwards to match model definitions
16
- #
17
- # @param [Symbol] name repository to act on, :default is the default
18
- #
19
- # @api public
20
- def auto_migrate!(repository_name = nil)
21
- auto_migrate_down!(repository_name)
22
- auto_migrate_up!(repository_name)
23
- end
24
-
25
- # @api public
26
- def auto_upgrade!(repository_name = nil)
27
- repository_execute(:auto_upgrade!, repository_name)
28
- end
29
-
30
- private
31
-
32
- # @api private
33
- def auto_migrate_down!(repository_name)
34
- repository_execute(:auto_migrate_down!, repository_name)
35
- end
36
-
37
- # @api private
38
- def auto_migrate_up!(repository_name)
39
- repository_execute(:auto_migrate_up!, repository_name)
40
- end
41
-
42
- # @api private
43
- def repository_execute(method, repository_name)
44
- DataMapper::Model.descendants.each do |model|
45
- model.send(method, repository_name || model.default_repository_name)
46
- end
47
- end
48
- end
49
-
50
- module DataObjectsAdapter
51
- # @api private
52
- def self.included(base)
53
- base.extend ClassMethods
54
-
55
- DataMapper.extend(Migrations::SingletonMethods)
56
-
57
- [ :Repository, :Model ].each do |name|
58
- DataMapper.const_get(name).send(:include, Migrations.const_get(name))
59
- end
60
- end
61
-
62
- # Returns whether the storage_name exists.
63
- #
64
- # @param [String] storage_name
65
- # a String defining the name of a storage, for example a table name.
66
- #
67
- # @return [Boolean]
68
- # true if the storage exists
69
- #
70
- # @api semipublic
71
- def storage_exists?(storage_name)
72
- statement = <<-SQL.compress_lines
73
- SELECT COUNT(*)
74
- FROM "information_schema"."tables"
75
- WHERE "table_type" = 'BASE TABLE'
76
- AND "table_schema" = ?
77
- AND "table_name" = ?
78
- SQL
79
-
80
- select(statement, schema_name, storage_name).first > 0
81
- end
82
-
83
- # Returns whether the field exists.
84
- #
85
- # @param [String] storage_name
86
- # a String defining the name of a storage, for example a table name.
87
- # @param [String] field
88
- # a String defining the name of a field, for example a column name.
89
- #
90
- # @return [Boolean]
91
- # true if the field exists.
92
- #
93
- # @api semipublic
94
- def field_exists?(storage_name, column_name)
95
- statement = <<-SQL.compress_lines
96
- SELECT COUNT(*)
97
- FROM "information_schema"."columns"
98
- WHERE "table_schema" = ?
99
- AND "table_name" = ?
100
- AND "column_name" = ?
101
- SQL
102
-
103
- select(statement, schema_name, storage_name, column_name).first > 0
104
- end
105
-
106
- # @api semipublic
107
- def upgrade_model_storage(model)
108
- name = self.name
109
- properties = model.properties_with_subclasses(name)
110
-
111
- if success = create_model_storage(model)
112
- return properties
113
- end
114
-
115
- table_name = model.storage_name(name)
116
-
117
- with_connection do |connection|
118
- properties.map do |property|
119
- schema_hash = property_schema_hash(property)
120
- next if field_exists?(table_name, schema_hash[:name])
121
-
122
- statement = alter_table_add_column_statement(connection, table_name, schema_hash)
123
- command = connection.create_command(statement)
124
- command.execute_non_query
125
-
126
- property
127
- end.compact
128
- end
129
- end
130
-
131
- # @api semipublic
132
- def create_model_storage(model)
133
- name = self.name
134
- properties = model.properties_with_subclasses(name)
135
-
136
- return false if storage_exists?(model.storage_name(name))
137
- return false if properties.empty?
138
-
139
- with_connection do |connection|
140
- statements = [ create_table_statement(connection, model, properties) ]
141
- statements.concat(create_index_statements(model))
142
- statements.concat(create_unique_index_statements(model))
143
-
144
- statements.each do |statement|
145
- command = connection.create_command(statement)
146
- command.execute_non_query
147
- end
148
- end
149
-
150
- true
151
- end
152
-
153
- # @api semipublic
154
- def destroy_model_storage(model)
155
- return true unless supports_drop_table_if_exists? || storage_exists?(model.storage_name(name))
156
- execute(drop_table_statement(model))
157
- true
158
- end
159
-
160
- module SQL #:nodoc:
161
- # private ## This cannot be private for current migrations
162
-
163
- # Adapters that support AUTO INCREMENT fields for CREATE TABLE
164
- # statements should overwrite this to return true
165
- #
166
- # @api private
167
- def supports_serial?
168
- false
169
- end
170
-
171
- # @api private
172
- def supports_drop_table_if_exists?
173
- false
174
- end
175
-
176
- # @api private
177
- def schema_name
178
- raise NotImplementedError, "#{self.class}#schema_name not implemented"
179
- end
180
-
181
- # @api private
182
- def alter_table_add_column_statement(connection, table_name, schema_hash)
183
- "ALTER TABLE #{quote_name(table_name)} ADD COLUMN #{property_schema_statement(connection, schema_hash)}"
184
- end
185
-
186
- # @api private
187
- def create_table_statement(connection, model, properties)
188
- statement = <<-SQL.compress_lines
189
- CREATE TABLE #{quote_name(model.storage_name(name))}
190
- (#{properties.map { |property| property_schema_statement(connection, property_schema_hash(property)) }.join(', ')},
191
- PRIMARY KEY(#{ properties.key.map { |property| quote_name(property.field) }.join(', ')}))
192
- SQL
193
-
194
- statement
195
- end
196
-
197
- # @api private
198
- def drop_table_statement(model)
199
- table_name = quote_name(model.storage_name(name))
200
- if supports_drop_table_if_exists?
201
- "DROP TABLE IF EXISTS #{table_name}"
202
- else
203
- "DROP TABLE #{table_name}"
204
- end
205
- end
206
-
207
- # @api private
208
- def create_index_statements(model)
209
- name = self.name
210
- table_name = model.storage_name(name)
211
- model.properties(name).indexes.map do |index_name, fields|
212
- <<-SQL.compress_lines
213
- CREATE INDEX #{quote_name("index_#{table_name}_#{index_name}")} ON
214
- #{quote_name(table_name)} (#{fields.map { |field| quote_name(field) }.join(', ')})
215
- SQL
216
- end
217
- end
218
-
219
- # @api private
220
- def create_unique_index_statements(model)
221
- name = self.name
222
- table_name = model.storage_name(name)
223
- model.properties(name).unique_indexes.map do |index_name, fields|
224
- <<-SQL.compress_lines
225
- CREATE UNIQUE INDEX #{quote_name("unique_#{table_name}_#{index_name}")} ON
226
- #{quote_name(table_name)} (#{fields.map { |field| quote_name(field) }.join(', ')})
227
- SQL
228
- end
229
- end
230
-
231
- # @api private
232
- def property_schema_hash(property)
233
- primitive = property.primitive
234
- type = property.type
235
- type_map = self.class.type_map
236
-
237
- schema = (type_map[type] || type_map[primitive]).merge(:name => property.field)
238
-
239
- schema_primitive = schema[:primitive]
240
-
241
- if primitive == String && schema_primitive != 'TEXT' && schema_primitive != 'CLOB' && schema_primitive != 'NVARCHAR'
242
- schema[:length] = property.length
243
- elsif primitive == BigDecimal || primitive == Float
244
- schema[:precision] = property.precision
245
- schema[:scale] = property.scale
246
- end
247
-
248
- schema[:allow_nil] = property.allow_nil?
249
- schema[:serial] = property.serial?
250
-
251
- default = property.default
252
-
253
- if default.nil? || default.respond_to?(:call)
254
- # remove the default if the property does not allow nil
255
- schema.delete(:default) unless schema[:allow_nil]
256
- else
257
- schema[:default] = if type.respond_to?(:dump)
258
- type.dump(default, property)
259
- else
260
- default
261
- end
262
- end
263
-
264
- schema
265
- end
266
-
267
- # @api private
268
- def property_schema_statement(connection, schema)
269
- statement = quote_name(schema[:name])
270
- statement << " #{schema[:primitive]}"
271
-
272
- length = schema[:length]
273
-
274
- if schema[:precision] && schema[:scale]
275
- statement << "(#{[ :precision, :scale ].map { |key| connection.quote_value(schema[key]) }.join(', ')})"
276
- elsif length == 'max'
277
- statement << '(max)'
278
- elsif length
279
- statement << "(#{connection.quote_value(length)})"
280
- end
281
-
282
- statement << " DEFAULT #{connection.quote_value(schema[:default])}" if schema.key?(:default)
283
- statement << ' NOT NULL' unless schema[:allow_nil]
284
- statement
285
- end
286
- end # module SQL
287
-
288
- include SQL
289
-
290
- module ClassMethods
291
- # Default types for all data object based adapters.
292
- #
293
- # @return [Hash] default types for data objects adapters.
294
- #
295
- # @api private
296
- def type_map
297
- length = Property::DEFAULT_LENGTH
298
- precision = Property::DEFAULT_PRECISION
299
- scale = Property::DEFAULT_SCALE_BIGDECIMAL
300
-
301
- @type_map ||= {
302
- Integer => { :primitive => 'INTEGER' },
303
- String => { :primitive => 'VARCHAR', :length => length },
304
- Class => { :primitive => 'VARCHAR', :length => length },
305
- BigDecimal => { :primitive => 'DECIMAL', :precision => precision, :scale => scale },
306
- Float => { :primitive => 'FLOAT', :precision => precision },
307
- DateTime => { :primitive => 'TIMESTAMP' },
308
- Date => { :primitive => 'DATE' },
309
- Time => { :primitive => 'TIMESTAMP' },
310
- TrueClass => { :primitive => 'BOOLEAN' },
311
- Types::Text => { :primitive => 'TEXT' },
312
- }.freeze
313
- end
314
- end # module ClassMethods
315
- end # module DataObjectsAdapter
316
-
317
- module MysqlAdapter
318
- DEFAULT_ENGINE = 'InnoDB'.freeze
319
- DEFAULT_CHARACTER_SET = 'utf8'.freeze
320
- DEFAULT_COLLATION = 'utf8_unicode_ci'.freeze
321
-
322
- # @api private
323
- def self.included(base)
324
- base.extend ClassMethods
325
- end
326
-
327
- # @api semipublic
328
- def storage_exists?(storage_name)
329
- select('SHOW TABLES LIKE ?', storage_name).first == storage_name
330
- end
331
-
332
- # @api semipublic
333
- def field_exists?(storage_name, field)
334
- result = select("SHOW COLUMNS FROM #{quote_name(storage_name)} LIKE ?", field).first
335
- result ? result.field == field : false
336
- end
337
-
338
- module SQL #:nodoc:
339
- # private ## This cannot be private for current migrations
340
-
341
- # @api private
342
- def supports_serial?
343
- true
344
- end
345
-
346
- # @api private
347
- def supports_drop_table_if_exists?
348
- true
349
- end
350
-
351
- # @api private
352
- def schema_name
353
- # TODO: is there a cleaner way to find out the current DB we are connected to?
354
- normalized_uri.path.split('/').last
355
- end
356
-
357
- # TODO: update dkubb/dm-more/dm-migrations to use schema_name and remove this
358
- alias db_name schema_name
359
-
360
- # @api private
361
- def create_table_statement(connection, model, properties)
362
- "#{super} ENGINE = #{DEFAULT_ENGINE} CHARACTER SET #{character_set} COLLATE #{collation}"
363
- end
364
-
365
- # @api private
366
- def property_schema_hash(property)
367
- schema = super
368
-
369
- if schema[:primitive] == 'TEXT'
370
- schema[:primitive] = text_column_statement(property.length)
371
- schema.delete(:default)
372
- end
373
-
374
- min = property.min
375
- max = property.max
376
-
377
- if property.primitive == Integer && min && max
378
- schema[:primitive] = integer_column_statement(min..max)
379
- end
380
-
381
- schema
382
- end
383
-
384
- # @api private
385
- def property_schema_statement(connection, schema)
386
- statement = super
387
-
388
- if supports_serial? && schema[:serial]
389
- statement << ' AUTO_INCREMENT'
390
- end
391
-
392
- statement
393
- end
394
-
395
- # @api private
396
- def character_set
397
- @character_set ||= show_variable('character_set_connection') || DEFAULT_CHARACTER_SET
398
- end
399
-
400
- # @api private
401
- def collation
402
- @collation ||= show_variable('collation_connection') || DEFAULT_COLLATION
403
- end
404
-
405
- # @api private
406
- def show_variable(name)
407
- result = select('SHOW VARIABLES LIKE ?', name).first
408
- result ? result.value.freeze : nil
409
- end
410
-
411
- private
412
-
413
- # Return SQL statement for the text column
414
- #
415
- # @param [Integer] length
416
- # the max allowed length
417
- #
418
- # @return [String]
419
- # the statement to create the text column
420
- #
421
- # @api private
422
- def text_column_statement(length)
423
- if length < 2**8 then 'TINYTEXT'
424
- elsif length < 2**16 then 'TEXT'
425
- elsif length < 2**24 then 'MEDIUMTEXT'
426
- elsif length < 2**32 then 'LONGTEXT'
427
-
428
- # http://www.postgresql.org/files/documentation/books/aw_pgsql/node90.html
429
- # Implies that PostgreSQL doesn't have a size limit on text
430
- # fields, so this param validation happens here instead of
431
- # DM::Property#initialize.
432
- else
433
- raise ArgumentError, "length of #{length} exceeds maximum size supported"
434
- end
435
- end
436
-
437
- # Return SQL statement for the integer column
438
- #
439
- # @param [Range] range
440
- # the min/max allowed integers
441
- #
442
- # @return [String]
443
- # the statement to create the integer column
444
- #
445
- # @api private
446
- def integer_column_statement(range)
447
- '%s(%d)%s' % [
448
- integer_column_type(range),
449
- integer_display_size(range),
450
- integer_statement_sign(range),
451
- ]
452
- end
453
-
454
- # Return the integer column type
455
- #
456
- # Use the smallest available column type that will satisfy the
457
- # allowable range of numbers
458
- #
459
- # @param [Range] range
460
- # the min/max allowed integers
461
- #
462
- # @return [String]
463
- # the column type
464
- #
465
- # @api private
466
- def integer_column_type(range)
467
- if range.first < 0
468
- signed_integer_column_type(range)
469
- else
470
- unsigned_integer_column_type(range)
471
- end
472
- end
473
-
474
- # Return the signed integer column type
475
- #
476
- # @param [Range] range
477
- # the min/max allowed integers
478
- #
479
- # @return [String]
480
- #
481
- # @api private
482
- def signed_integer_column_type(range)
483
- min = range.first
484
- max = range.last
485
-
486
- tinyint = 2**7
487
- smallint = 2**15
488
- integer = 2**31
489
- mediumint = 2**23
490
- bigint = 2**63
491
-
492
- if min >= -tinyint && max < tinyint then 'TINYINT'
493
- elsif min >= -smallint && max < smallint then 'SMALLINT'
494
- elsif min >= -mediumint && max < mediumint then 'MEDIUMINT'
495
- elsif min >= -integer && max < integer then 'INT'
496
- elsif min >= -bigint && max < bigint then 'BIGINT'
497
- else
498
- raise ArgumentError, "min #{min} and max #{max} exceeds supported range"
499
- end
500
- end
501
-
502
- # Return the unsigned integer column type
503
- #
504
- # @param [Range] range
505
- # the min/max allowed integers
506
- #
507
- # @return [String]
508
- #
509
- # @api private
510
- def unsigned_integer_column_type(range)
511
- max = range.last
512
-
513
- if max < 2**8 then 'TINYINT'
514
- elsif max < 2**16 then 'SMALLINT'
515
- elsif max < 2**24 then 'MEDIUMINT'
516
- elsif max < 2**32 then 'INT'
517
- elsif max < 2**64 then 'BIGINT'
518
- else
519
- raise ArgumentError, "min #{range.first} and max #{max} exceeds supported range"
520
- end
521
- end
522
-
523
- # Return the integer column display size
524
- #
525
- # Adjust the display size to match the maximum number of
526
- # expected digits. This is more for documentation purposes
527
- # and does not affect what can actually be stored in a
528
- # specific column
529
- #
530
- # @param [Range] range
531
- # the min/max allowed integers
532
- #
533
- # @return [Integer]
534
- # the display size for the integer
535
- #
536
- # @api private
537
- def integer_display_size(range)
538
- [ range.first.to_s.length, range.last.to_s.length ].max
539
- end
540
-
541
- # Return the integer sign statement
542
- #
543
- # @param [Range] range
544
- # the min/max allowed integers
545
- #
546
- # @return [String, nil]
547
- # statement if unsigned, nil if signed
548
- #
549
- # @api private
550
- def integer_statement_sign(range)
551
- ' UNSIGNED' unless range.first < 0
552
- end
553
- end # module SQL
554
-
555
- include SQL
556
-
557
- module ClassMethods
558
- # Types for MySQL databases.
559
- #
560
- # @return [Hash] types for MySQL databases.
561
- #
562
- # @api private
563
- def type_map
564
- @type_map ||= super.merge(
565
- DateTime => { :primitive => 'DATETIME' },
566
- Time => { :primitive => 'DATETIME' }
567
- ).freeze
568
- end
569
- end # module ClassMethods
570
- end # module MysqlAdapter
571
-
572
- module PostgresAdapter
573
- # @api private
574
- def self.included(base)
575
- base.extend ClassMethods
576
- end
577
-
578
- # @api semipublic
579
- def upgrade_model_storage(model)
580
- without_notices { super }
581
- end
582
-
583
- # @api semipublic
584
- def create_model_storage(model)
585
- without_notices { super }
586
- end
587
-
588
- # @api semipublic
589
- def destroy_model_storage(model)
590
- if supports_drop_table_if_exists?
591
- without_notices { super }
592
- else
593
- super
594
- end
595
- end
596
-
597
- module SQL #:nodoc:
598
- # private ## This cannot be private for current migrations
599
-
600
- # @api private
601
- def supports_drop_table_if_exists?
602
- @supports_drop_table_if_exists ||= postgres_version >= '8.2'
603
- end
604
-
605
- # @api private
606
- def schema_name
607
- @schema_name ||= select('SELECT current_schema()').first.freeze
608
- end
609
-
610
- # @api private
611
- def postgres_version
612
- @postgres_version ||= select('SELECT version()').first.split[1].freeze
613
- end
614
-
615
- # @api private
616
- def without_notices
617
- # execute the block with NOTICE messages disabled
618
- begin
619
- execute('SET client_min_messages = warning')
620
- yield
621
- ensure
622
- execute('RESET client_min_messages')
623
- end
624
- end
625
-
626
- # @api private
627
- def property_schema_hash(property)
628
- schema = super
629
-
630
- primitive = property.primitive
631
-
632
- # Postgres does not support precision and scale for Float
633
- if primitive == Float
634
- schema.delete(:precision)
635
- schema.delete(:scale)
636
- end
637
-
638
- min = property.min
639
- max = property.max
640
-
641
- if primitive == Integer && min && max
642
- schema[:primitive] = integer_column_statement(min..max)
643
- end
644
-
645
- if schema[:serial]
646
- schema[:primitive] = serial_column_statement(min..max)
647
- end
648
-
649
- schema
650
- end
651
-
652
- private
653
-
654
- # Return SQL statement for the integer column
655
- #
656
- # @param [Range] range
657
- # the min/max allowed integers
658
- #
659
- # @return [String]
660
- # the statement to create the integer column
661
- #
662
- # @api private
663
- def integer_column_statement(range)
664
- min = range.first
665
- max = range.last
666
-
667
- smallint = 2**15
668
- integer = 2**31
669
- bigint = 2**63
670
-
671
- if min >= -smallint && max < smallint then 'SMALLINT'
672
- elsif min >= -integer && max < integer then 'INTEGER'
673
- elsif min >= -bigint && max < bigint then 'BIGINT'
674
- else
675
- raise ArgumentError, "min #{min} and max #{max} exceeds supported range"
676
- end
677
- end
678
-
679
- # Return SQL statement for the serial column
680
- #
681
- # @param [Integer] max
682
- # the max allowed integer
683
- #
684
- # @return [String]
685
- # the statement to create the serial column
686
- #
687
- # @api private
688
- def serial_column_statement(range)
689
- max = range.last
690
-
691
- if max.nil? || max < 2**31 then 'SERIAL'
692
- elsif max < 2**63 then 'BIGSERIAL'
693
- else
694
- raise ArgumentError, "min #{range.first} and max #{max} exceeds supported range"
695
- end
696
- end
697
- end # module SQL
698
-
699
- include SQL
700
-
701
- module ClassMethods
702
- # Types for PostgreSQL databases.
703
- #
704
- # @return [Hash] types for PostgreSQL databases.
705
- #
706
- # @api private
707
- def type_map
708
- precision = Property::DEFAULT_PRECISION
709
- scale = Property::DEFAULT_SCALE_BIGDECIMAL
710
-
711
- @type_map ||= super.merge(
712
- BigDecimal => { :primitive => 'NUMERIC', :precision => precision, :scale => scale },
713
- Float => { :primitive => 'DOUBLE PRECISION' }
714
- ).freeze
715
- end
716
- end # module ClassMethods
717
- end # module PostgresAdapter
718
-
719
- module Sqlite3Adapter
720
- # @api private
721
- def self.included(base)
722
- base.extend ClassMethods
723
- end
724
-
725
- # @api semipublic
726
- def storage_exists?(storage_name)
727
- table_info(storage_name).any?
728
- end
729
-
730
- # @api semipublic
731
- def field_exists?(storage_name, column_name)
732
- table_info(storage_name).any? do |row|
733
- row.name == column_name
734
- end
735
- end
736
-
737
- module SQL #:nodoc:
738
- # private ## This cannot be private for current migrations
739
-
740
- # @api private
741
- def supports_serial?
742
- @supports_serial ||= sqlite_version >= '3.1.0'
743
- end
744
-
745
- # @api private
746
- def supports_drop_table_if_exists?
747
- @supports_drop_table_if_exists ||= sqlite_version >= '3.3.0'
748
- end
749
-
750
- # @api private
751
- def table_info(table_name)
752
- select("PRAGMA table_info(#{quote_name(table_name)})")
753
- end
754
-
755
- # @api private
756
- def create_table_statement(connection, model, properties)
757
- statement = <<-SQL.compress_lines
758
- CREATE TABLE #{quote_name(model.storage_name(name))}
759
- (#{properties.map { |property| property_schema_statement(connection, property_schema_hash(property)) }.join(', ')}
760
- SQL
761
-
762
- # skip adding the primary key if one of the columns is serial. In
763
- # SQLite the serial column must be the primary key, so it has already
764
- # been defined
765
- unless properties.any? { |property| property.serial? }
766
- statement << ", PRIMARY KEY(#{properties.key.map { |property| quote_name(property.field) }.join(', ')})"
767
- end
768
-
769
- statement << ')'
770
- statement
771
- end
772
-
773
- # @api private
774
- def property_schema_statement(connection, schema)
775
- statement = super
776
-
777
- if supports_serial? && schema[:serial]
778
- statement << ' PRIMARY KEY AUTOINCREMENT'
779
- end
780
-
781
- statement
782
- end
783
-
784
- # @api private
785
- def sqlite_version
786
- @sqlite_version ||= select('SELECT sqlite_version(*)').first.freeze
787
- end
788
- end # module SQL
789
-
790
- include SQL
791
-
792
- module ClassMethods
793
- # Types for SQLite 3 databases.
794
- #
795
- # @return [Hash] types for SQLite 3 databases.
796
- #
797
- # @api private
798
- def type_map
799
- @type_map ||= super.merge(Class => { :primitive => 'VARCHAR' }).freeze
800
- end
801
- end # module ClassMethods
802
- end # module Sqlite3Adapter
803
-
804
- module OracleAdapter
805
- # @api private
806
- def self.included(base)
807
- base.extend ClassMethods
808
- end
809
-
810
- # @api semipublic
811
- def storage_exists?(storage_name)
812
- statement = <<-SQL.compress_lines
813
- SELECT COUNT(*)
814
- FROM all_tables
815
- WHERE owner = ?
816
- AND table_name = ?
817
- SQL
818
-
819
- select(statement, schema_name, oracle_upcase(storage_name)).first > 0
820
- end
821
-
822
- # @api semipublic
823
- def sequence_exists?(sequence_name)
824
- return false unless sequence_name
825
- statement = <<-SQL.compress_lines
826
- SELECT COUNT(*)
827
- FROM all_sequences
828
- WHERE sequence_owner = ?
829
- AND sequence_name = ?
830
- SQL
831
-
832
- select(statement, schema_name, oracle_upcase(sequence_name)).first > 0
833
- end
834
-
835
- # @api semipublic
836
- def field_exists?(storage_name, field_name)
837
- statement = <<-SQL.compress_lines
838
- SELECT COUNT(*)
839
- FROM all_tab_columns
840
- WHERE owner = ?
841
- AND table_name = ?
842
- AND column_name = ?
843
- SQL
844
-
845
- select(statement, schema_name, oracle_upcase(storage_name), oracle_upcase(field_name)).first > 0
846
- end
847
-
848
- # @api semipublic
849
- def storage_fields(storage_name)
850
- statement = <<-SQL.compress_lines
851
- SELECT column_name
852
- FROM all_tab_columns
853
- WHERE owner = ?
854
- AND table_name = ?
855
- SQL
856
-
857
- select(statement, schema_name, oracle_upcase(storage_name))
858
- end
859
-
860
- # @api semipublic
861
- def create_model_storage(model)
862
- name = self.name
863
- properties = model.properties_with_subclasses(name)
864
- table_name = model.storage_name(name)
865
- truncate_or_delete = self.class.auto_migrate_with
866
- table_is_truncated = truncate_or_delete && @truncated_tables && @truncated_tables[table_name]
867
-
868
- return false if storage_exists?(table_name) && !table_is_truncated
869
- return false if properties.empty?
870
-
871
- with_connection do |connection|
872
- # if table was truncated then check if all columns for properties are present
873
- # TODO: check all other column definition options
874
- if table_is_truncated && storage_has_all_fields?(table_name, properties)
875
- @truncated_tables[table_name] = nil
876
- else
877
- # forced drop of table if properties are different
878
- if truncate_or_delete
879
- destroy_model_storage(model, true)
880
- end
881
-
882
- statements = [ create_table_statement(connection, model, properties) ]
883
- statements.concat(create_index_statements(model))
884
- statements.concat(create_unique_index_statements(model))
885
- statements.concat(create_sequence_statements(model))
886
-
887
- statements.each do |statement|
888
- command = connection.create_command(statement)
889
- command.execute_non_query
890
- end
891
- end
892
-
893
- end
894
-
895
- true
896
- end
897
-
898
- # @api semipublic
899
- def destroy_model_storage(model, forced = false)
900
- table_name = model.storage_name(name)
901
- klass = self.class
902
- truncate_or_delete = klass.auto_migrate_with
903
- if storage_exists?(table_name)
904
- if truncate_or_delete && !forced
905
- case truncate_or_delete
906
- when :truncate
907
- execute(truncate_table_statement(model))
908
- when :delete
909
- execute(delete_table_statement(model))
910
- else
911
- raise ArgumentError, "Unsupported auto_migrate_with option"
912
- end
913
- @truncated_tables ||= {}
914
- @truncated_tables[table_name] = true
915
- else
916
- execute(drop_table_statement(model))
917
- @truncated_tables[table_name] = nil if @truncated_tables
918
- end
919
- end
920
- # added destroy of sequences
921
- reset_sequences = klass.auto_migrate_reset_sequences
922
- table_is_truncated = @truncated_tables && @truncated_tables[table_name]
923
- unless truncate_or_delete && !reset_sequences && !forced
924
- if sequence_exists?(model_sequence_name(model))
925
- statement = if table_is_truncated && !forced
926
- reset_sequence_statement(model)
927
- else
928
- drop_sequence_statement(model)
929
- end
930
- execute(statement) if statement
931
- end
932
- end
933
- true
934
- end
935
-
936
- private
937
-
938
- def storage_has_all_fields?(table_name, properties)
939
- properties.map { |property| oracle_upcase(property.field) }.sort == storage_fields(table_name).sort
940
- end
941
-
942
- # If table or column name contains just lowercase characters then do uppercase
943
- # as uppercase version will be used in Oracle data dictionary tables
944
- def oracle_upcase(name)
945
- name =~ /[A-Z]/ ? name : name.upcase
946
- end
947
-
948
- module SQL #:nodoc:
949
- # private ## This cannot be private for current migrations
950
-
951
- # @api private
952
- def schema_name
953
- @schema_name ||= select("SELECT SYS_CONTEXT('userenv','current_schema') FROM dual").first.freeze
954
- end
955
-
956
- # @api private
957
- def create_sequence_statements(model)
958
- name = self.name
959
- table_name = model.storage_name(name)
960
- serial = model.serial(name)
961
-
962
- statements = []
963
- if sequence_name = model_sequence_name(model)
964
- sequence_name = quote_name(sequence_name)
965
- column_name = quote_name(serial.field)
966
-
967
- statements << <<-SQL.compress_lines
968
- CREATE SEQUENCE #{sequence_name} NOCACHE
969
- SQL
970
-
971
- # create trigger only if custom sequence name was not specified
972
- unless serial.options[:sequence]
973
- statements << <<-SQL.compress_lines
974
- CREATE OR REPLACE TRIGGER #{quote_name(default_trigger_name(table_name))}
975
- BEFORE INSERT ON #{quote_name(table_name)} FOR EACH ROW
976
- BEGIN
977
- IF inserting THEN
978
- IF :new.#{column_name} IS NULL THEN
979
- SELECT #{sequence_name}.NEXTVAL INTO :new.#{column_name} FROM dual;
980
- END IF;
981
- END IF;
982
- END;
983
- SQL
984
- end
985
- end
986
-
987
- statements
988
- end
989
-
990
- # @api private
991
- def drop_sequence_statement(model)
992
- if sequence_name = model_sequence_name(model)
993
- "DROP SEQUENCE #{quote_name(sequence_name)}"
994
- else
995
- nil
996
- end
997
- end
998
-
999
- # @api private
1000
- def reset_sequence_statement(model)
1001
- if sequence_name = model_sequence_name(model)
1002
- sequence_name = quote_name(sequence_name)
1003
- <<-SQL.compress_lines
1004
- DECLARE
1005
- cval INTEGER;
1006
- BEGIN
1007
- SELECT #{sequence_name}.NEXTVAL INTO cval FROM dual;
1008
- EXECUTE IMMEDIATE 'ALTER SEQUENCE #{sequence_name} INCREMENT BY -' || cval || ' MINVALUE 0';
1009
- SELECT #{sequence_name}.NEXTVAL INTO cval FROM dual;
1010
- EXECUTE IMMEDIATE 'ALTER SEQUENCE #{sequence_name} INCREMENT BY 1';
1011
- END;
1012
- SQL
1013
- else
1014
- nil
1015
- end
1016
-
1017
- end
1018
-
1019
- # @api private
1020
- def truncate_table_statement(model)
1021
- "TRUNCATE TABLE #{quote_name(model.storage_name(name))}"
1022
- end
1023
-
1024
- # @api private
1025
- def delete_table_statement(model)
1026
- "DELETE FROM #{quote_name(model.storage_name(name))}"
1027
- end
1028
-
1029
- private
1030
-
1031
- def model_sequence_name(model)
1032
- name = self.name
1033
- table_name = model.storage_name(name)
1034
- serial = model.serial(name)
1035
-
1036
- if serial
1037
- serial.options[:sequence] || default_sequence_name(table_name)
1038
- else
1039
- nil
1040
- end
1041
- end
1042
-
1043
- def default_sequence_name(table_name)
1044
- # truncate table name if necessary to fit in max length of identifier
1045
- "#{table_name[0,self.class::IDENTIFIER_MAX_LENGTH-4]}_seq"
1046
- end
1047
-
1048
- def default_trigger_name(table_name)
1049
- # truncate table name if necessary to fit in max length of identifier
1050
- "#{table_name[0,self.class::IDENTIFIER_MAX_LENGTH-4]}_pkt"
1051
- end
1052
-
1053
- end # module SQL
1054
-
1055
- include SQL
1056
-
1057
- module ClassMethods
1058
- # Types for Oracle databases.
1059
- #
1060
- # @return [Hash] types for Oracle databases.
1061
- #
1062
- # @api private
1063
- def type_map
1064
- length = Property::DEFAULT_LENGTH
1065
- precision = Property::DEFAULT_PRECISION
1066
- scale = Property::DEFAULT_SCALE_BIGDECIMAL
1067
-
1068
- @type_map ||= {
1069
- Integer => { :primitive => 'NUMBER', :precision => precision, :scale => 0 },
1070
- String => { :primitive => 'VARCHAR2', :length => length },
1071
- Class => { :primitive => 'VARCHAR2', :length => length },
1072
- BigDecimal => { :primitive => 'NUMBER', :precision => precision, :scale => nil },
1073
- Float => { :primitive => 'BINARY_FLOAT', },
1074
- DateTime => { :primitive => 'DATE' },
1075
- Date => { :primitive => 'DATE' },
1076
- Time => { :primitive => 'DATE' },
1077
- TrueClass => { :primitive => 'NUMBER', :precision => 1, :scale => 0 },
1078
- Types::Text => { :primitive => 'CLOB' },
1079
- }.freeze
1080
- end
1081
-
1082
- # Use table truncate or delete for auto_migrate! to speed up test execution
1083
- #
1084
- # @param [Symbol] :truncate, :delete or :drop_and_create (or nil)
1085
- # do not specify parameter to return current value
1086
- #
1087
- # @return [Symbol] current value of auto_migrate_with option (nil returned for :drop_and_create)
1088
- #
1089
- # @api semipublic
1090
- def auto_migrate_with(value = :not_specified)
1091
- return @auto_migrate_with if value == :not_specified
1092
- value = nil if value == :drop_and_create
1093
- raise ArgumentError unless [nil, :truncate, :delete].include?(value)
1094
- @auto_migrate_with = value
1095
- end
1096
-
1097
- # Set if sequences will or will not be reset during auto_migrate!
1098
- #
1099
- # @param [TrueClass, FalseClass] reset sequences?
1100
- # do not specify parameter to return current value
1101
- #
1102
- # @return [Symbol] current value of auto_migrate_reset_sequences option (default value is true)
1103
- #
1104
- # @api semipublic
1105
- def auto_migrate_reset_sequences(value = :not_specified)
1106
- return @auto_migrate_reset_sequences.nil? ? true : @auto_migrate_reset_sequences if value == :not_specified
1107
- raise ArgumentError unless [true, false].include?(value)
1108
- @auto_migrate_reset_sequences = value
1109
- end
1110
-
1111
- end # module ClassMethods
1112
- end # module PostgresAdapter
1113
-
1114
- module SqlserverAdapter
1115
- DEFAULT_CHARACTER_SET = 'utf8'.freeze
1116
-
1117
- # @api private
1118
- def self.included(base)
1119
- base.extend ClassMethods
1120
- end
1121
-
1122
- # @api semipublic
1123
- def storage_exists?(storage_name)
1124
- select("SELECT name FROM sysobjects WHERE name LIKE ?", storage_name).first == storage_name
1125
- end
1126
-
1127
- # @api semipublic
1128
- def field_exists?(storage_name, field_name)
1129
- result = select("SELECT c.name FROM sysobjects as o JOIN syscolumns AS c ON o.id = c.id WHERE o.name = #{quote_name(storage_name)} AND c.name LIKE ?", field_name).first
1130
- result ? result.field == field_name : false
1131
- end
1132
-
1133
- module SQL #:nodoc:
1134
- # private ## This cannot be private for current migrations
1135
-
1136
- # @api private
1137
- def supports_serial?
1138
- true
1139
- end
1140
-
1141
- # @api private
1142
- def supports_drop_table_if_exists?
1143
- false
1144
- end
1145
-
1146
- # @api private
1147
- def schema_name
1148
- # TODO: is there a cleaner way to find out the current DB we are connected to?
1149
- @options[:path].split('/').last
1150
- end
1151
-
1152
- # TODO: update dkubb/dm-more/dm-migrations to use schema_name and remove this
1153
-
1154
- alias db_name schema_name
1155
-
1156
- # @api private
1157
- def create_table_statement(connection, model, properties)
1158
- statement = <<-SQL.compress_lines
1159
- CREATE TABLE #{quote_name(model.storage_name(name))}
1160
- (#{properties.map { |property| property_schema_statement(connection, property_schema_hash(property)) }.join(', ')}
1161
- SQL
1162
-
1163
- unless properties.any? { |property| property.serial? }
1164
- statement << ", PRIMARY KEY(#{properties.key.map { |property| quote_name(property.field) }.join(', ')})"
1165
- end
1166
-
1167
- statement << ')'
1168
- statement
1169
- end
1170
-
1171
- # @api private
1172
- def property_schema_hash(property)
1173
- schema = super
1174
-
1175
- min = property.min
1176
- max = property.max
1177
-
1178
- if property.primitive == Integer && min && max
1179
- schema[:primitive] = integer_column_statement(min..max)
1180
- end
1181
-
1182
- if schema[:primitive] == 'TEXT'
1183
- schema.delete(:default)
1184
- end
1185
-
1186
- schema
1187
- end
1188
-
1189
- # @api private
1190
- def property_schema_statement(connection, schema)
1191
- if supports_serial? && schema[:serial]
1192
- statement = quote_name(schema[:name])
1193
- statement << " #{schema[:primitive]}"
1194
-
1195
- length = schema[:length]
1196
-
1197
- if schema[:precision] && schema[:scale]
1198
- statement << "(#{[ :precision, :scale ].map { |key| connection.quote_value(schema[key]) }.join(', ')})"
1199
- elsif length
1200
- statement << "(#{connection.quote_value(length)})"
1201
- end
1202
-
1203
- statement << ' IDENTITY'
1204
- else
1205
- statement = super
1206
- end
1207
-
1208
- statement
1209
- end
1210
-
1211
- # @api private
1212
- def character_set
1213
- @character_set ||= show_variable('character_set_connection') || DEFAULT_CHARACTER_SET
1214
- end
1215
-
1216
- # @api private
1217
- def collation
1218
- @collation ||= show_variable('collation_connection') || DEFAULT_COLLATION
1219
- end
1220
-
1221
- # @api private
1222
- def show_variable(name)
1223
- raise "SqlserverAdapter#show_variable: Not implemented"
1224
- end
1225
-
1226
- private
1227
-
1228
- # Return SQL statement for the integer column
1229
- #
1230
- # @param [Range] range
1231
- # the min/max allowed integers
1232
- #
1233
- # @return [String]
1234
- # the statement to create the integer column
1235
- #
1236
- # @api private
1237
- def integer_column_statement(range)
1238
- min = range.first
1239
- max = range.last
1240
-
1241
- smallint = 2**15
1242
- integer = 2**31
1243
- bigint = 2**63
1244
-
1245
- if min >= 0 && max < 2**8 then 'TINYINT'
1246
- elsif min >= -smallint && max < smallint then 'SMALLINT'
1247
- elsif min >= -integer && max < integer then 'INT'
1248
- elsif min >= -bigint && max < bigint then 'BIGINT'
1249
- else
1250
- raise ArgumentError, "min #{min} and max #{max} exceeds supported range"
1251
- end
1252
- end
1253
-
1254
- end # module SQL
1255
-
1256
- include SQL
1257
-
1258
- module ClassMethods
1259
- # Types for Sqlserver databases.
1260
- #
1261
- # @return [Hash] types for Sqlserver databases.
1262
- #
1263
- # @api private
1264
- def type_map
1265
- length = Property::DEFAULT_LENGTH
1266
- precision = Property::DEFAULT_PRECISION
1267
- scale = Property::DEFAULT_SCALE_BIGDECIMAL
1268
-
1269
- @type_map ||= super.merge(
1270
- DateTime => { :primitive => 'DATETIME' },
1271
- Date => { :primitive => 'SMALLDATETIME' },
1272
- Time => { :primitive => 'SMALLDATETIME' },
1273
- TrueClass => { :primitive => 'BIT', },
1274
- Types::Text => { :primitive => 'NVARCHAR', :length => 'max' }
1275
- ).freeze
1276
- end
1277
- end # module ClassMethods
1278
- end # module SqlserverAdapter
1279
-
1280
-
1281
- module Repository
1282
- # Determine whether a particular named storage exists in this repository
1283
- #
1284
- # @param [String]
1285
- # storage_name name of the storage to test for
1286
- #
1287
- # @return [Boolean]
1288
- # true if the data-store +storage_name+ exists
1289
- #
1290
- # @api semipublic
1291
- def storage_exists?(storage_name)
1292
- adapter = self.adapter
1293
- if adapter.respond_to?(:storage_exists?)
1294
- adapter.storage_exists?(storage_name)
1295
- end
1296
- end
1297
-
1298
- # @api semipublic
1299
- def upgrade_model_storage(model)
1300
- adapter = self.adapter
1301
- if adapter.respond_to?(:upgrade_model_storage)
1302
- adapter.upgrade_model_storage(model)
1303
- end
1304
- end
1305
-
1306
- # @api semipublic
1307
- def create_model_storage(model)
1308
- adapter = self.adapter
1309
- if adapter.respond_to?(:create_model_storage)
1310
- adapter.create_model_storage(model)
1311
- end
1312
- end
1313
-
1314
- # @api semipublic
1315
- def destroy_model_storage(model)
1316
- adapter = self.adapter
1317
- if adapter.respond_to?(:destroy_model_storage)
1318
- adapter.destroy_model_storage(model)
1319
- end
1320
- end
1321
-
1322
- # Destructively automigrates the data-store to match the model.
1323
- # First migrates all models down and then up.
1324
- # REPEAT: THIS IS DESTRUCTIVE
1325
- #
1326
- # @api public
1327
- def auto_migrate!
1328
- DataMapper.auto_migrate!(name)
1329
- end
1330
-
1331
- # Safely migrates the data-store to match the model
1332
- # preserving data already in the data-store
1333
- #
1334
- # @api public
1335
- def auto_upgrade!
1336
- DataMapper.auto_upgrade!(name)
1337
- end
1338
- end # module Repository
1339
-
1340
- module Model
1341
- # @api private
1342
- def self.included(mod)
1343
- mod.descendants.each { |model| model.extend self }
1344
- end
1345
-
1346
- # @api semipublic
1347
- def storage_exists?(repository_name = default_repository_name)
1348
- repository(repository_name).storage_exists?(storage_name(repository_name))
1349
- end
1350
-
1351
- # Destructively automigrates the data-store to match the model
1352
- # REPEAT: THIS IS DESTRUCTIVE
1353
- #
1354
- # @param Symbol repository_name the repository to be migrated
1355
- #
1356
- # @api public
1357
- def auto_migrate!(repository_name = self.repository_name)
1358
- assert_valid
1359
- auto_migrate_down!(repository_name)
1360
- auto_migrate_up!(repository_name)
1361
- end
1362
-
1363
- # Safely migrates the data-store to match the model
1364
- # preserving data already in the data-store
1365
- #
1366
- # @param Symbol repository_name the repository to be migrated
1367
- #
1368
- # @api public
1369
- def auto_upgrade!(repository_name = self.repository_name)
1370
- assert_valid
1371
- base_model = self.base_model
1372
- if base_model == self
1373
- repository(repository_name).upgrade_model_storage(self)
1374
- else
1375
- base_model.auto_upgrade!(repository_name)
1376
- end
1377
- end
1378
-
1379
- # Destructively migrates the data-store down, which basically
1380
- # deletes all the models.
1381
- # REPEAT: THIS IS DESTRUCTIVE
1382
- #
1383
- # @param Symbol repository_name the repository to be migrated
1384
- #
1385
- # @api private
1386
- def auto_migrate_down!(repository_name = self.repository_name)
1387
- assert_valid
1388
- base_model = self.base_model
1389
- if base_model == self
1390
- repository(repository_name).destroy_model_storage(self)
1391
- else
1392
- base_model.auto_migrate_down!(repository_name)
1393
- end
1394
- end
1395
-
1396
- # Auto migrates the data-store to match the model
1397
- #
1398
- # @param Symbol repository_name the repository to be migrated
1399
- #
1400
- # @api private
1401
- def auto_migrate_up!(repository_name = self.repository_name)
1402
- assert_valid
1403
- base_model = self.base_model
1404
- if base_model == self
1405
- repository(repository_name).create_model_storage(self)
1406
- else
1407
- base_model.auto_migrate_up!(repository_name)
1408
- end
1409
- end
1410
- end # module Model
1411
- end
1412
-
1413
- module Adapters
1414
- extendable do
1415
-
1416
- # @api private
1417
- def const_added(const_name)
1418
- if DataMapper::Migrations.const_defined?(const_name)
1419
- adapter = const_get(const_name)
1420
- adapter.send(:include, DataMapper::Migrations.const_get(const_name))
1421
- end
1422
-
1423
- super
1424
- end
1425
- end
1426
- end # module Adapters
1427
- end # module DataMapper