dm-core 0.10.2 → 1.0.0.rc1

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