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
data/deps.rip DELETED
@@ -1,2 +0,0 @@
1
- git://github.com/datamapper/extlib.git master
2
- git://github.com/sporkmonger/addressable.git addressable-2.1.0
@@ -1,712 +0,0 @@
1
- require 'data_objects'
2
-
3
- module DataMapper
4
- module Adapters
5
- # DataObjectsAdapter is the base class for all adapers for relational
6
- # databases. If you want to add support for a new RDBMS, it makes
7
- # sense to make your adapter class inherit from this class.
8
- #
9
- # By inheriting from DataObjectsAdapter, you get a copy of all the
10
- # standard sub-modules (Quoting, Coersion and Queries) in your own Adapter.
11
- # You can extend and overwrite these copies without affecting the originals.
12
- class DataObjectsAdapter < AbstractAdapter
13
- extend Chainable
14
- extend Deprecate
15
-
16
- deprecate :query, :select
17
-
18
- # Retrieve results using an SQL SELECT statement
19
- #
20
- # @param [String] statement
21
- # the SQL SELECT statement
22
- # @param [Array] *bind_values
23
- # optional bind values to merge into the statement
24
- #
25
- # @return [Array]
26
- # if fields > 1, return an Array of Struct objects
27
- # if fields == 1, return an Array of objects
28
- #
29
- # @api public
30
- def select(statement, *bind_values)
31
- with_connection do |connection|
32
- reader = connection.create_command(statement).execute_reader(*bind_values)
33
- fields = reader.fields
34
-
35
- begin
36
- if fields.size > 1
37
- select_fields(reader, fields)
38
- else
39
- select_field(reader)
40
- end
41
- ensure
42
- reader.close
43
- end
44
- end
45
- end
46
-
47
- # Execute non-SELECT SQL query
48
- #
49
- # @param [String] statement
50
- # the SQL statement
51
- # @param [Array] *bind_values
52
- # optional bind values to merge into the statement
53
- #
54
- # @return [DataObjects::Result]
55
- # result with number of affected rows, and insert id if any
56
- #
57
- # @api public
58
- def execute(statement, *bind_values)
59
- with_connection do |connection|
60
- command = connection.create_command(statement)
61
- command.execute_non_query(*bind_values)
62
- end
63
- end
64
-
65
- # For each model instance in resources, issues an SQL INSERT
66
- # (or equivalent) statement to create a new record in the data store for
67
- # the instance
68
- #
69
- # Note that this method does not update identity map. A plugin needs to use
70
- # adapter directly, it is up to plugin developer to keep identity map
71
- # up to date.
72
- #
73
- # @param [Enumerable(Resource)] resources
74
- # The list of resources (model instances) to create
75
- #
76
- # @return [Integer]
77
- # The number of records that were actually saved into the database
78
- #
79
- # @api semipublic
80
- def create(resources)
81
- name = self.name
82
-
83
- resources.each do |resource|
84
- model = resource.model
85
- serial = model.serial(name)
86
- attributes = resource.dirty_attributes
87
-
88
- properties = []
89
- bind_values = []
90
-
91
- # make the order of the properties consistent
92
- model.properties(name).each do |property|
93
- next unless attributes.key?(property)
94
-
95
- bind_value = attributes[property]
96
-
97
- # skip insering NULL for columns that are serial or without a default
98
- next if bind_value.nil? && (property.serial? || !property.default?)
99
-
100
- # if serial is being set explicitly, do not set it again
101
- if property.equal?(serial)
102
- serial = nil
103
- end
104
-
105
- properties << property
106
- bind_values << bind_value
107
- end
108
-
109
- statement = insert_statement(model, properties, serial)
110
- result = execute(statement, *bind_values)
111
-
112
- if result.affected_rows == 1 && serial
113
- serial.set!(resource, result.insert_id)
114
- end
115
- end
116
- end
117
-
118
- # Constructs and executes SELECT query, then instantiates
119
- # one or many object from result set.
120
- #
121
- # @param [Query] query
122
- # composition of the query to perform
123
- #
124
- # @return [Array]
125
- # result set of the query
126
- #
127
- # @api semipublic
128
- def read(query)
129
- fields = query.fields
130
- types = fields.map { |property| property.primitive }
131
-
132
- statement, bind_values = select_statement(query)
133
-
134
- records = []
135
-
136
- with_connection do |connection|
137
- command = connection.create_command(statement)
138
- command.set_types(types)
139
-
140
- reader = command.execute_reader(*bind_values)
141
-
142
- begin
143
- while reader.next!
144
- records << fields.zip(reader.values).to_hash
145
- end
146
- ensure
147
- reader.close
148
- end
149
- end
150
-
151
- records
152
- end
153
-
154
- # Constructs and executes UPDATE statement for given
155
- # attributes and a query
156
- #
157
- # @param [Hash(Property => Object)] attributes
158
- # hash of attribute values to set, keyed by Property
159
- # @param [Collection] collection
160
- # collection of records to be updated
161
- #
162
- # @return [Integer]
163
- # the number of records updated
164
- #
165
- # @api semipublic
166
- def update(attributes, collection)
167
- query = collection.query
168
-
169
- properties = []
170
- bind_values = []
171
-
172
- # make the order of the properties consistent
173
- query.model.properties(name).each do |property|
174
- next unless attributes.key?(property)
175
- properties << property
176
- bind_values << attributes[property]
177
- end
178
-
179
- statement, conditions_bind_values = update_statement(properties, query)
180
-
181
- bind_values.concat(conditions_bind_values)
182
-
183
- execute(statement, *bind_values).affected_rows
184
- end
185
-
186
- # Constructs and executes DELETE statement for given query
187
- #
188
- # @param [Collection] collection
189
- # collection of records to be deleted
190
- #
191
- # @return [Integer]
192
- # the number of records deleted
193
- #
194
- # @api semipublic
195
- def delete(collection)
196
- query = collection.query
197
- statement, bind_values = delete_statement(query)
198
- execute(statement, *bind_values).affected_rows
199
- end
200
-
201
- protected
202
-
203
- # @api private
204
- def normalized_uri
205
- @normalized_uri ||=
206
- begin
207
- query = @options.except(:adapter, :user, :password, :host, :port, :path, :fragment, :scheme, :query, :username, :database)
208
- query = nil if query.empty?
209
-
210
- DataObjects::URI.new(
211
- @options[:adapter],
212
- @options[:user] || @options[:username],
213
- @options[:password],
214
- @options[:host],
215
- @options[:port],
216
- @options[:path] || @options[:database],
217
- query,
218
- @options[:fragment]
219
- ).freeze
220
- end
221
- end
222
-
223
- chainable do
224
- protected
225
-
226
- # Instantiates new connection object
227
- #
228
- # @api semipublic
229
- def open_connection
230
- # DataObjects::Connection.new(uri) will give you back the right
231
- # driver based on the DataObjects::URI#scheme
232
- connection_stack = self.connection_stack
233
- connection = connection_stack.last || DataObjects::Connection.new(normalized_uri)
234
- connection_stack << connection
235
- connection
236
- end
237
-
238
- # Takes connection and closes it
239
- #
240
- # @api semipublic
241
- def close_connection(connection)
242
- connection_stack = self.connection_stack
243
- connection_stack.pop
244
- connection.close if connection_stack.empty?
245
- end
246
- end
247
-
248
- # @api private
249
- def connection_stack
250
- connection_stack_for = Thread.current[:dm_do_connection_stack] ||= {}
251
- connection_stack_for[self] ||= []
252
- end
253
-
254
- private
255
-
256
- # @api public
257
- def initialize(name, uri_or_options)
258
- super
259
-
260
- # Default the driver-specific logger to DataMapper's logger
261
- if driver_module = DataObjects.const_get(normalized_uri.scheme.capitalize)
262
- driver_module.logger = DataMapper.logger if driver_module.respond_to?(:logger=)
263
- end
264
- end
265
-
266
- # @api private
267
- def with_connection
268
- begin
269
- yield connection = open_connection
270
- rescue Exception => exception
271
- DataMapper.logger.error(exception.to_s)
272
- raise exception
273
- ensure
274
- close_connection(connection) if connection
275
- end
276
- end
277
-
278
- # @api private
279
- def select_fields(reader, fields)
280
- fields = fields.map { |field| Extlib::Inflection.underscore(field).to_sym }
281
- struct = Struct.new(*fields)
282
-
283
- results = []
284
-
285
- while reader.next!
286
- results << struct.new(*reader.values)
287
- end
288
-
289
- results
290
- end
291
-
292
- # @api private
293
- def select_field(reader)
294
- results = []
295
-
296
- while reader.next!
297
- results << reader.values.at(0)
298
- end
299
-
300
- results
301
- end
302
-
303
- # This module is just for organization. The methods are included into the
304
- # Adapter below.
305
- module SQL #:nodoc:
306
- IDENTIFIER_MAX_LENGTH = 128
307
-
308
- # @api semipublic
309
- def property_to_column_name(property, qualify, table_name = nil)
310
- column_name = quote_name(property.field)
311
-
312
- if qualify
313
- table_name ||= quote_name(property.model.storage_name(name))
314
- "#{table_name}.#{column_name}"
315
- else
316
- column_name
317
- end
318
- end
319
-
320
- private
321
-
322
- # Adapters requiring a RETURNING syntax for INSERT statements
323
- # should overwrite this to return true.
324
- #
325
- # @api private
326
- def supports_returning?
327
- false
328
- end
329
-
330
- # Adapters that do not support the DEFAULT VALUES syntax for
331
- # INSERT statements should overwrite this to return false.
332
- #
333
- # @api private
334
- def supports_default_values?
335
- true
336
- end
337
-
338
- # Constructs SELECT statement for given query,
339
- #
340
- # @return [String] SELECT statement as a string
341
- #
342
- # @api private
343
- def select_statement(query)
344
- qualify = query.links.any?
345
- fields = query.fields
346
- order_by = query.order
347
- group_by = if query.unique?
348
- fields.select { |property| property.kind_of?(Property) }
349
- end
350
-
351
- conditions_statement, bind_values = conditions_statement(query.conditions, qualify)
352
-
353
- statement = "SELECT #{columns_statement(fields, qualify)}"
354
- statement << " FROM #{quote_name(query.model.storage_name(name))}"
355
- statement << join_statement(query, qualify) if qualify
356
- statement << " WHERE #{conditions_statement}" unless conditions_statement.blank?
357
- statement << " GROUP BY #{columns_statement(group_by, qualify)}" if group_by && group_by.any?
358
- statement << " ORDER BY #{order_statement(order_by, qualify)}" if order_by && order_by.any?
359
-
360
- add_limit_offset!(statement, query.limit, query.offset, bind_values)
361
-
362
- return statement, bind_values
363
- end
364
-
365
- # default construction of LIMIT and OFFSET
366
- # overriden by some adapters (currently Oracle and SQL Server)
367
- def add_limit_offset!(statement, limit, offset, bind_values)
368
- if limit
369
- statement << ' LIMIT ?'
370
- bind_values << limit
371
- end
372
-
373
- if limit && offset > 0
374
- statement << ' OFFSET ?'
375
- bind_values << offset
376
- end
377
- end
378
-
379
- # Constructs INSERT statement for given query,
380
- #
381
- # @return [String] INSERT statement as a string
382
- #
383
- # @api private
384
- def insert_statement(model, properties, serial)
385
- statement = "INSERT INTO #{quote_name(model.storage_name(name))} "
386
-
387
- if supports_default_values? && properties.empty?
388
- statement << default_values_clause
389
- else
390
- statement << <<-SQL.compress_lines
391
- (#{properties.map { |property| quote_name(property.field) }.join(', ')})
392
- VALUES
393
- (#{(['?'] * properties.size).join(', ')})
394
- SQL
395
- end
396
-
397
- if supports_returning? && serial
398
- statement << returning_clause(serial)
399
- end
400
-
401
- statement
402
- end
403
-
404
- # by default PostgreSQL syntax
405
- # overrided in Oracle adapter
406
- def default_values_clause
407
- 'DEFAULT VALUES'
408
- end
409
-
410
- # by default PostgreSQL syntax
411
- # overrided in Oracle adapter
412
- def returning_clause(serial)
413
- " RETURNING #{quote_name(serial.field)}"
414
- end
415
-
416
- # Constructs UPDATE statement for given query,
417
- #
418
- # @return [String] UPDATE statement as a string
419
- #
420
- # @api private
421
- def update_statement(properties, query)
422
- model = query.model
423
- name = self.name
424
-
425
- # TODO: DRY this up with delete_statement
426
- conditions_statement, bind_values = if query.limit || query.links.any?
427
- subquery(query, model.key(name), false)
428
- else
429
- conditions_statement(query.conditions)
430
- end
431
-
432
- statement = "UPDATE #{quote_name(model.storage_name(name))}"
433
- statement << " SET #{properties.map { |property| "#{quote_name(property.field)} = ?" }.join(', ')}"
434
- statement << " WHERE #{conditions_statement}" unless conditions_statement.blank?
435
-
436
- return statement, bind_values
437
- end
438
-
439
- # Constructs DELETE statement for given query,
440
- #
441
- # @return [String] DELETE statement as a string
442
- #
443
- # @api private
444
- def delete_statement(query)
445
- model = query.model
446
- name = self.name
447
-
448
- # TODO: DRY this up with update_statement
449
- conditions_statement, bind_values = if query.limit || query.links.any?
450
- subquery(query, model.key(name), false)
451
- else
452
- conditions_statement(query.conditions)
453
- end
454
-
455
- statement = "DELETE FROM #{quote_name(model.storage_name(name))}"
456
- statement << " WHERE #{conditions_statement}" unless conditions_statement.blank?
457
-
458
- return statement, bind_values
459
- end
460
-
461
- # Constructs comma separated list of fields
462
- #
463
- # @return [String]
464
- # list of fields as a string
465
- #
466
- # @api private
467
- def columns_statement(properties, qualify, qualifier = nil)
468
- properties.map { |property| property_to_column_name(property, qualify, qualifier) }.join(', ')
469
- end
470
-
471
- # Constructs joins clause
472
- #
473
- # @return [String]
474
- # joins clause
475
- #
476
- # @api private
477
- def join_statement(query, qualify)
478
- statement = ''
479
-
480
- query.links.reverse_each do |relationship|
481
- statement << " INNER JOIN #{quote_name(relationship.source_model.storage_name(name))} ON "
482
- statement << relationship.target_key.zip(relationship.source_key).map do |target_property, source_property|
483
- "#{property_to_column_name(target_property, qualify)} = #{property_to_column_name(source_property, qualify)}"
484
- end.join(' AND ')
485
- end
486
-
487
- statement
488
- end
489
-
490
- # Constructs where clause
491
- #
492
- # @return [String]
493
- # where clause
494
- #
495
- # @api private
496
- def conditions_statement(conditions, qualify = false)
497
- case conditions
498
- when Query::Conditions::NotOperation then negate_operation(conditions.operand, qualify)
499
- when Query::Conditions::AbstractOperation then operation_statement(conditions, qualify)
500
- when Query::Conditions::AbstractComparison then comparison_statement(conditions, qualify)
501
- when Array
502
- statement, bind_values = conditions # handle raw conditions
503
- [ "(#{statement})", bind_values ].compact
504
- end
505
- end
506
-
507
- # @api private
508
- def supports_subquery?(*)
509
- true
510
- end
511
-
512
- # @api private
513
- def subquery(query, subject, qualify)
514
- source_key, target_key = subquery_keys(subject)
515
-
516
- if query.repository.name == name && supports_subquery?(query, source_key, target_key, qualify)
517
- subquery_statement(query, source_key, target_key, qualify)
518
- else
519
- subquery_execute(query, source_key, target_key, qualify)
520
- end
521
- end
522
-
523
- # @api private
524
- def subquery_statement(query, source_key, target_key, qualify)
525
- query = subquery_query(query, source_key)
526
- select_statement, bind_values = select_statement(query)
527
-
528
- statement = if target_key.size == 1
529
- property_to_column_name(target_key.first, qualify)
530
- else
531
- "(#{target_key.map { |property| property_to_column_name(property, qualify) }.join(', ')})"
532
- end
533
-
534
- statement << " IN (#{select_statement})"
535
-
536
- return statement, bind_values
537
- end
538
-
539
- # @api private
540
- def subquery_execute(query, source_key, target_key, qualify)
541
- query = subquery_query(query, source_key)
542
- sources = query.model.all(query)
543
- conditions = Query.target_conditions(sources, source_key, target_key)
544
-
545
- if conditions.valid?
546
- conditions_statement(conditions, qualify)
547
- else
548
- [ '1 = 0', [] ]
549
- end
550
- end
551
-
552
- # @api private
553
- def subquery_keys(subject)
554
- case subject
555
- when Associations::Relationship
556
- relationship = subject.inverse
557
- [ relationship.source_key, relationship.target_key ]
558
- when PropertySet
559
- [ subject, subject ]
560
- end
561
- end
562
-
563
- # @api private
564
- def subquery_query(query, source_key)
565
- # force unique to be false because PostgreSQL has a problem with
566
- # subselects that contain a GROUP BY with different columns
567
- # than the outer-most query
568
- query = query.merge(:fields => source_key, :unique => false)
569
- query.update(:order => nil) unless query.limit
570
- query
571
- end
572
-
573
- # Constructs order clause
574
- #
575
- # @return [String]
576
- # order clause
577
- #
578
- # @api private
579
- def order_statement(order, qualify, qualifier = nil)
580
- statements = order.map do |direction|
581
- statement = property_to_column_name(direction.target, qualify, qualifier)
582
- statement << ' DESC' if direction.operator == :desc
583
- statement
584
- end
585
-
586
- statements.join(', ')
587
- end
588
-
589
- # @api private
590
- def negate_operation(operand, qualify)
591
- statement, bind_values = conditions_statement(operand, qualify)
592
- statement = "NOT(#{statement})" unless statement.nil?
593
- [ statement, bind_values ]
594
- end
595
-
596
- # @api private
597
- def operation_statement(operation, qualify)
598
- statements = []
599
- bind_values = []
600
-
601
- operation.each do |operand|
602
- statement, values = conditions_statement(operand, qualify)
603
- next unless statement && values
604
- statements << statement
605
- bind_values.concat(values)
606
- end
607
-
608
- statement = statements.join(" #{operation.slug.to_s.upcase} ")
609
-
610
- if statements.size > 1
611
- statement = "(#{statement})"
612
- end
613
-
614
- return statement, bind_values
615
- end
616
-
617
- # Constructs comparison clause
618
- #
619
- # @return [String]
620
- # comparison clause
621
- #
622
- # @api private
623
- def comparison_statement(comparison, qualify)
624
- subject = comparison.subject
625
- value = comparison.value
626
-
627
- # TODO: move exclusive Range handling into another method, and
628
- # update conditions_statement to use it
629
-
630
- # break exclusive Range queries up into two comparisons ANDed together
631
- if value.kind_of?(Range) && value.exclude_end?
632
- operation = Query::Conditions::Operation.new(:and,
633
- Query::Conditions::Comparison.new(:gte, subject, value.first),
634
- Query::Conditions::Comparison.new(:lt, subject, value.last)
635
- )
636
-
637
- statement, bind_values = conditions_statement(operation, qualify)
638
-
639
- return "(#{statement})", bind_values
640
- elsif comparison.relationship?
641
- if value.respond_to?(:query) && value.respond_to?(:loaded?) && !value.loaded?
642
- return subquery(value.query, subject, qualify)
643
- else
644
- return conditions_statement(comparison.foreign_key_mapping, qualify)
645
- end
646
- elsif comparison.slug == :in && value.empty?
647
- return [] # match everything
648
- end
649
-
650
- operator = comparison_operator(comparison)
651
- column_name = property_to_column_name(subject, qualify)
652
-
653
- # if operator return value contains ? then it means that it is function call
654
- # and it contains placeholder (%s) for property name as well (used in Oracle adapter for regexp operator)
655
- if operator.include?('?')
656
- return operator % column_name, [ value ]
657
- else
658
- return "#{column_name} #{operator} #{value.nil? ? 'NULL' : '?'}", [ value ].compact
659
- end
660
- end
661
-
662
- def comparison_operator(comparison)
663
- subject = comparison.subject
664
- value = comparison.value
665
-
666
- case comparison.slug
667
- when :eql then equality_operator(subject, value)
668
- when :in then include_operator(subject, value)
669
- when :regexp then regexp_operator(value)
670
- when :like then like_operator(value)
671
- when :gt then '>'
672
- when :lt then '<'
673
- when :gte then '>='
674
- when :lte then '<='
675
- end
676
- end
677
-
678
- # @api private
679
- def equality_operator(property, operand)
680
- operand.nil? ? 'IS' : '='
681
- end
682
-
683
- # @api private
684
- def include_operator(property, operand)
685
- case operand
686
- when Array then 'IN'
687
- when Range then 'BETWEEN'
688
- end
689
- end
690
-
691
- # @api private
692
- def regexp_operator(operand)
693
- '~'
694
- end
695
-
696
- # @api private
697
- def like_operator(operand)
698
- 'LIKE'
699
- end
700
-
701
- # @api private
702
- def quote_name(name)
703
- "\"#{name[0, self.class::IDENTIFIER_MAX_LENGTH].gsub('"', '""')}\""
704
- end
705
- end #module SQL
706
-
707
- include SQL
708
- end # class DataObjectsAdapter
709
-
710
- const_added(:DataObjectsAdapter)
711
- end # module Adapters
712
- end # module DataMapper