ibm_db 2.5.17-universal-darwin-13

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 (49) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +221 -0
  3. data/LICENSE +18 -0
  4. data/MANIFEST +14 -0
  5. data/ParameterizedQueries README +39 -0
  6. data/README +225 -0
  7. data/ext/Makefile.nt32 +181 -0
  8. data/ext/Makefile.nt32.191 +212 -0
  9. data/ext/extconf.rb +127 -0
  10. data/ext/ibm_db.c +11719 -0
  11. data/ext/ruby_ibm_db.h +240 -0
  12. data/ext/ruby_ibm_db_cli.c +845 -0
  13. data/ext/ruby_ibm_db_cli.h +489 -0
  14. data/init.rb +42 -0
  15. data/lib/IBM_DB.rb +16 -0
  16. data/lib/active_record/connection_adapters/ibm_db_adapter.rb +3031 -0
  17. data/lib/active_record/connection_adapters/ibm_db_pstmt.rb +1965 -0
  18. data/lib/active_record/connection_adapters/ibmdb_adapter.rb +2 -0
  19. data/lib/active_record/vendor/db2-i5-zOS.yaml +328 -0
  20. data/lib/linux/rb18x/ibm_db.bundle +0 -0
  21. data/lib/linux/rb19x/ibm_db.bundle +0 -0
  22. data/lib/linux/rb20x/ibm_db.bundle +0 -0
  23. data/lib/linux/rb21x/ibm_db.bundle +0 -0
  24. data/test/cases/adapter_test.rb +207 -0
  25. data/test/cases/associations/belongs_to_associations_test.rb +711 -0
  26. data/test/cases/associations/cascaded_eager_loading_test.rb +181 -0
  27. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +851 -0
  28. data/test/cases/associations/join_model_test.rb +743 -0
  29. data/test/cases/attribute_methods_test.rb +822 -0
  30. data/test/cases/base_test.rb +2133 -0
  31. data/test/cases/calculations_test.rb +482 -0
  32. data/test/cases/migration_test.rb +2408 -0
  33. data/test/cases/persistence_test.rb +642 -0
  34. data/test/cases/query_cache_test.rb +257 -0
  35. data/test/cases/relations_test.rb +1182 -0
  36. data/test/cases/schema_dumper_test.rb +256 -0
  37. data/test/cases/transaction_callbacks_test.rb +300 -0
  38. data/test/cases/validations/uniqueness_validation_test.rb +299 -0
  39. data/test/cases/xml_serialization_test.rb +408 -0
  40. data/test/config.yml +154 -0
  41. data/test/connections/native_ibm_db/connection.rb +44 -0
  42. data/test/ibm_db_test.rb +25 -0
  43. data/test/models/warehouse_thing.rb +5 -0
  44. data/test/schema/i5/ibm_db_specific_schema.rb +137 -0
  45. data/test/schema/ids/ibm_db_specific_schema.rb +140 -0
  46. data/test/schema/luw/ibm_db_specific_schema.rb +137 -0
  47. data/test/schema/schema.rb +751 -0
  48. data/test/schema/zOS/ibm_db_specific_schema.rb +208 -0
  49. metadata +109 -0
@@ -0,0 +1,1965 @@
1
+ # +-----------------------------------------------------------------------+
2
+ # | |
3
+ # | Copyright (c) 2004-2009 David Heinemeier Hansson |
4
+ # | Copyright (c) 2010 IBM Corporation (modifications) |
5
+ # | |
6
+ # | Permission is hereby granted, free of charge, to any person obtaining |
7
+ # | a copy of this software and associated documentation files (the |
8
+ # | "Software"), to deal in the Software without restriction, including |
9
+ # | without limitation the rights to use, copy, modify, merge, publish, |
10
+ # | distribute, sublicense, and/or sell copies of the Software, and to |
11
+ # | permit persons to whom the Software is furnished to do so, subject to |
12
+ # | the following conditions: |
13
+ # |
14
+ # | The above copyright notice and this permission notice shall be |
15
+ # | included in all copies or substantial portions of the Software. |
16
+ # | |
17
+ # | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
18
+ # | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
19
+ # | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.|
20
+ # | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR |
21
+ # | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
22
+ # | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
23
+ # | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
24
+ # | |
25
+ # +-----------------------------------------------------------------------+
26
+
27
+ module ActiveRecord
28
+ class Base
29
+
30
+ def quote_value(value, column = nil) #:nodoc:
31
+ connection.quote_value_for_pstmt(value,column)
32
+ end
33
+
34
+ # Deletes the record in the database and freezes this instance to reflect that no changes should
35
+ # be made (since they can't be persisted).
36
+ def destroy_without_lock
37
+ unless new_record?
38
+ # Prepare the sql for deleting a row
39
+ pstmt = connection.prepare(
40
+ "DELETE FROM #{self.class.quoted_table_name} " +
41
+ "WHERE #{connection.quote_column_name(self.class.primary_key)} = ?",
42
+ "#{self.class.name} Destroy"
43
+ )
44
+ # Execute the prepared Statement
45
+ connection.prepared_delete(pstmt, [connection.quote_value_for_pstmt(quoted_id)])
46
+ end
47
+
48
+ @destroyed = true
49
+ freeze
50
+ end
51
+
52
+ def update_without_lock(attribute_names = @attributes.keys)
53
+ quoted_attributes = attributes_with_quotes(false, false, attribute_names)
54
+ return 0 if quoted_attributes.empty?
55
+
56
+ columns_values_hash = quoted_comma_pair_list(connection, quoted_attributes)
57
+ pstmt = connection.prepare(
58
+ "UPDATE #{self.class.quoted_table_name} " +
59
+ "SET #{columns_values_hash["sqlSegment"]} " +
60
+ "WHERE #{connection.quote_column_name(self.class.primary_key)} = ?",
61
+ "#{self.class.name} Update"
62
+ )
63
+ columns_values_hash["paramArray"] << connection.quote_value_for_pstmt(id)
64
+ connection.prepared_update(pstmt, columns_values_hash["paramArray"] )
65
+ end
66
+
67
+ def attributes_with_quotes(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
68
+ quoted = {}
69
+ connection = self.class.connection
70
+ attribute_names.each do |name|
71
+ if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
72
+ value = read_attribute(name)
73
+
74
+ # We need explicit to_yaml because quote() does not properly convert Time/Date fields to YAML.
75
+ if value && self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time))
76
+ value = value.to_yaml
77
+ end
78
+
79
+ quoted[name] = connection.quote_value_for_pstmt(value)
80
+ end
81
+ end
82
+
83
+ include_readonly_attributes ? quoted : remove_readonly_attributes(quoted)
84
+ end
85
+
86
+ def comma_pair_list(hash)
87
+ return_hash = {}
88
+ return_hash["paramArray"] = []
89
+
90
+ return_hash["sqlSegment"] = hash.inject([]) { |list, pair|
91
+ return_hash["paramArray"] << pair.last
92
+ list << "#{pair.first} = ? "
93
+ }.join(", ")
94
+ return_hash
95
+ end
96
+
97
+ def update_without_dirty(attribute_names = @attributes.keys) #:nodoc:
98
+ return update_without_lock(attribute_names) unless locking_enabled?
99
+ return 0 if attribute_names.empty?
100
+
101
+ lock_col = self.class.locking_column
102
+ previous_value = send(lock_col).to_i
103
+ send(lock_col + '=', previous_value + 1)
104
+
105
+ attribute_names += [lock_col]
106
+ attribute_names.uniq!
107
+
108
+ columns_values_hash = quoted_comma_pair_list(connection, attributes_with_quotes(false, false, attribute_names))
109
+ begin
110
+ pstmt = connection.prepare(<<-end_sql, "#{self.class.name} Update with optimistic locking")
111
+ UPDATE #{self.class.quoted_table_name}
112
+ SET #{columns_values_hash["sqlSegment"]}
113
+ WHERE #{self.class.primary_key} = ?
114
+ AND #{self.class.quoted_locking_column} = ?
115
+ end_sql
116
+
117
+ columns_values_hash["paramArray"] << connection.quote_value_for_pstmt(id)
118
+ columns_values_hash["paramArray"] << connection.quote_value_for_pstmt(previous_value)
119
+
120
+ affected_rows = connection.prepared_update(pstmt, columns_values_hash["paramArray"])
121
+ unless affected_rows == 1
122
+ raise ActiveRecord::StaleObjectError, "Attempted to update a stale object"
123
+ end
124
+
125
+ affected_rows
126
+
127
+ # If something went wrong, revert the version.
128
+ rescue Exception
129
+ send(lock_col + '=', previous_value)
130
+ raise
131
+ end
132
+ end
133
+
134
+ def destroy_without_callbacks #:nodoc:
135
+ return destroy_without_lock unless locking_enabled?
136
+
137
+ paramArray = []
138
+
139
+ unless new_record?
140
+ lock_col = self.class.locking_column
141
+ previous_value = send(lock_col).to_i
142
+
143
+ pstmt = connection.prepare(
144
+ "DELETE FROM #{self.class.quoted_table_name} " +
145
+ "WHERE #{connection.quote_column_name(self.class.primary_key)} = ? " +
146
+ "AND #{self.class.quoted_locking_column} = ?",
147
+ "#{self.class.name} Destroy"
148
+ )
149
+
150
+ paramArray << connection.quote_value_for_pstmt(quoted_id)
151
+ paramArray << connection.quote_value_for_pstmt(previous_value)
152
+
153
+ affected_rows = connection.prepared_delete(pstmt, paramArray)
154
+
155
+ unless affected_rows == 1
156
+ raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object"
157
+ end
158
+ end
159
+
160
+ freeze
161
+ end
162
+
163
+ def create_without_timestamps
164
+ if self.id.nil? && connection.prefetch_primary_key?(self.class.table_name)
165
+ self.id = connection.next_sequence_value(self.class.sequence_name)
166
+ end
167
+
168
+ quoted_attributes = attributes_with_quotes
169
+
170
+ statement = if quoted_attributes.empty?
171
+ connection.empty_insert_statement(self.class.table_name)
172
+ else
173
+ param_marker_sql_segment = quoted_attributes.values.map{|value| '?'}.join(', ')
174
+ "INSERT INTO #{self.class.quoted_table_name} " +
175
+ "(#{quoted_column_names.join(', ')}) " +
176
+ "VALUES(#{param_marker_sql_segment})"
177
+ end
178
+
179
+ pstmt = connection.prepare(statement, "#{self.class.name} Create")
180
+ self.id = connection.prepared_insert(pstmt, quoted_attributes.values, self.id)
181
+
182
+ @new_record = false
183
+ id
184
+ end
185
+
186
+ private :update_without_lock, :attributes_with_quotes, :comma_pair_list
187
+ private :update_without_dirty, :destroy_without_callbacks
188
+ private :create_without_timestamps
189
+
190
+ class << self
191
+
192
+ def validates_uniqueness_of(*attr_names)
193
+ configuration = { :case_sensitive => true }
194
+ configuration.update(attr_names.extract_options!)
195
+
196
+ validates_each(attr_names,configuration) do |record, attr_name, value|
197
+ # The check for an existing value should be run from a class that
198
+ # isn't abstract. This means working down from the current class
199
+ # (self), to the first non-abstract class. Since classes don't know
200
+ # their subclasses, we have to build the hierarchy between self and
201
+ # the record's class.
202
+ class_hierarchy = [record.class]
203
+ while class_hierarchy.first != self
204
+ class_hierarchy.insert(0, class_hierarchy.first.superclass)
205
+ end
206
+
207
+ # Now we can work our way down the tree to the first non-abstract
208
+ # class (which has a database table to query from).
209
+ finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? }
210
+
211
+ column = finder_class.columns_hash[attr_name.to_s]
212
+
213
+ if value.nil?
214
+ comparison_operator = "IS NULL"
215
+ elsif column.text?
216
+ comparison_operator = "#{connection.case_sensitive_equality_operator} ?"
217
+ value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s
218
+ else
219
+ comparison_operator = "= ?"
220
+ end
221
+
222
+ sql_attribute = "#{record.class.quoted_table_name}.#{connection.quote_column_name(attr_name)}"
223
+
224
+ if value.nil? || (configuration[:case_sensitive] || !column.text?)
225
+ condition_sql = "#{sql_attribute} #{comparison_operator}"
226
+ condition_params = [value] if(!value.nil?) #Add the value only if not nil, because in case of nil comparison op is IS NULL
227
+ else
228
+ condition_sql = "LOWER(#{sql_attribute}) #{comparison_operator}"
229
+ condition_params = [value.mb_chars.downcase]
230
+ end
231
+
232
+ if scope = configuration[:scope]
233
+ Array(scope).map do |scope_item|
234
+ scope_value = record.send(scope_item)
235
+ condition_sql << " AND " << attribute_condition("#{record.class.quoted_table_name}.#{scope_item}", scope_value)
236
+ condition_params << scope_value
237
+ end
238
+ end
239
+
240
+ unless record.new_record?
241
+ condition_sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?"
242
+ condition_params << record.send(:id)
243
+ end
244
+
245
+ finder_class.with_exclusive_scope do
246
+ if finder_class.exists?([condition_sql, *condition_params])
247
+ record.errors.add(attr_name, :taken, :default => configuration[:message], :value => value)
248
+ end
249
+ end
250
+ end
251
+ end
252
+
253
+ def find_one(id, options)
254
+ param_array = [quote_value(id,columns_hash[primary_key])]
255
+ if options[:conditions]
256
+ sql_param_hash = sanitize_sql(options[:conditions])
257
+ conditions = " AND (#{sql_param_hash["sqlSegment"]})"
258
+ param_array = param_array + sql_param_hash["paramArray"] unless sql_param_hash["paramArray"].nil?
259
+ end
260
+
261
+ options.update :conditions => ["#{quoted_table_name}.#{connection.quote_column_name(primary_key)} = ?#{conditions}"] + param_array
262
+
263
+ # Use find_every(options).first since the primary key condition
264
+ # already ensures we have a single record. Using find_initial adds
265
+ # a superfluous :limit => 1.
266
+ if result = find_every(options).first
267
+ result
268
+ else
269
+ raise RecordNotFound, "Couldn't find #{name} with ID=#{id}#{conditions} with parameters #{param_array.last(param_array.size-1)}"
270
+ end
271
+ end
272
+
273
+ def find_some(ids, options)
274
+ param_array = []
275
+ ids_array = ids.map { |id| quote_value(id,columns_hash[primary_key]) }
276
+ if options[:conditions]
277
+ sql_param_hash = sanitize_sql(options[:conditions])
278
+ conditions = " AND (#{sql_param_hash["sqlSegment"]})"
279
+ param_array = param_array + sql_param_hash["paramArray"] unless sql_param_hash["paramArray"].nil?
280
+ end
281
+
282
+ options.update :conditions => ["#{quoted_table_name}.#{connection.quote_column_name(primary_key)} IN (?)#{conditions}"] + [ids_array] + param_array
283
+
284
+ result = find_every(options)
285
+
286
+ # Determine expected size from limit and offset, not just ids.size.
287
+ expected_size =
288
+ if options[:limit] && ids.size > options[:limit]
289
+ options[:limit]
290
+ else
291
+ ids.size
292
+ end
293
+
294
+ # 11 ids with limit 3, offset 9 should give 2 results.
295
+ if options[:offset] && (ids.size - options[:offset] < expected_size)
296
+ expected_size = ids.size - options[:offset]
297
+ end
298
+
299
+ if result.size == expected_size
300
+ result
301
+ else
302
+ raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids.join(', ')})#{conditions} with parameter(s) #{param_array.join(', ')} (found #{result.size} results, but was looking for #{expected_size})"
303
+ end
304
+ end
305
+
306
+ def merge_joins(*joins)
307
+ sql_param_hash = {}
308
+ param_array = []
309
+ if joins.any?{|j| j.is_a?(String) || array_of_strings?(j) || (j.is_a?(Hash) && j.has_key?("pstmt_hook"))}
310
+ joins_array = []
311
+ joins_compare_array = []
312
+
313
+ joins.each do |join|
314
+ get_join_associations = true
315
+ if join.is_a?(String)
316
+ unless joins_compare_array.include?(join)
317
+ joins_array << join
318
+ joins_compare_array << join
319
+ end
320
+ get_join_associations = false
321
+ elsif (join.is_a?(Hash) && join.has_key?("pstmt_hook"))
322
+ if(join["pstmt_hook"]["sqlSegment"].is_a?(Array))
323
+ compare_string = join["pstmt_hook"]["sqlSegment"].join(" ") + join["pstmt_hook"]["paramArray"].join(" ")
324
+ else
325
+ compare_string = join["pstmt_hook"]["sqlSegment"] + join["pstmt_hook"]["paramArray"].join(" ")
326
+ end
327
+ unless joins_compare_array.include?(compare_string)
328
+ param_array = param_array + join["pstmt_hook"]["paramArray"] unless join["pstmt_hook"]["paramArray"].nil?
329
+ joins_array << join["pstmt_hook"]["sqlSegment"]
330
+ joins_compare_array << compare_string
331
+ end
332
+ get_join_associations = false
333
+ end
334
+ unless array_of_strings?(join)
335
+ if get_join_associations
336
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, join, nil)
337
+ join_dependency.join_associations.each do |assoc|
338
+ sql_param_hash = assoc.association_join
339
+ compare_string = nil
340
+ compare_string = sql_param_hash["sqlSegment"] + sql_param_hash["paramArray"].join(" ") unless sql_param_hash.nil?
341
+ unless compare_string.nil? || joins_array.include?(compare_string)
342
+ param_array = param_array + sql_param_hash["paramArray"] unless sql_param_hash["paramArray"].nil?
343
+ joins_array << sql_param_hash["sqlSegment"]
344
+ joins_compare_array << compare_string
345
+ end
346
+ end
347
+ end
348
+ else
349
+ if get_join_associations
350
+ joins_array = joins_array + join.flatten.map{|j| j.strip }.uniq
351
+ end
352
+ end
353
+ end
354
+ sql_param_hash["sqlSegment"] = joins_array.flatten.map{|j| j.strip }.uniq
355
+ sql_param_hash["paramArray"] = param_array
356
+ {"pstmt_hook" => sql_param_hash}
357
+ else
358
+ sql_param_hash["sqlSegment"] = joins.collect{|j| safe_to_array(j)}.flatten.uniq
359
+ sql_param_hash["paramArray"] = param_array
360
+ {"pstmt_hook" => sql_param_hash}
361
+ end
362
+ end
363
+
364
+ private :find_one, :find_some, :merge_joins
365
+
366
+ def find_by_sql(sql)
367
+ sql_param_hash = sanitize_sql(sql)
368
+ connection.prepared_select(sql_param_hash, "#{name} Load").collect! { |record| instantiate(record) }
369
+ end
370
+
371
+ # Interpret Array and Hash as conditions and anything else as an id.
372
+ def expand_id_conditions(id_or_conditions)
373
+ case id_or_conditions
374
+ when Array, Hash then id_or_conditions
375
+ else {primary_key => id_or_conditions}
376
+ end
377
+ end
378
+
379
+ def construct_finder_sql(options)
380
+ param_array = []
381
+ scope = scope(:find)
382
+ sql = "SELECT #{options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))} "
383
+ sql << "FROM #{options[:from] || (scope && scope[:from]) || quoted_table_name} "
384
+
385
+ param_array = add_joins!(sql, options[:joins], scope)
386
+
387
+ param_array = param_array + add_conditions!(sql, options[:conditions], scope)
388
+
389
+ param_array = param_array + add_group!(sql, options[:group], options[:having], scope)
390
+
391
+ add_order!(sql, options[:order], scope)
392
+
393
+ temp_options = options.dup # Ensure that the necessary parameters are received in the duplicate, so that the original hash is intact
394
+ temp_options[:paramArray] = [] # To receive the values for limit and offset.
395
+ add_limit!(sql, temp_options, scope)
396
+
397
+ param_array = param_array + temp_options[:paramArray]
398
+
399
+ add_lock!(sql, options, scope)
400
+
401
+ [sql] + param_array
402
+ end
403
+
404
+ def add_group!(sql, group, having, scope = :auto)
405
+ param_array = []
406
+ if group
407
+ sql << " GROUP BY #{group}"
408
+ if having
409
+ sql_param_hash = sanitize_sql_for_conditions(having)
410
+ sql << " HAVING #{sql_param_hash["sqlSegment"]}"
411
+ param_array = param_array + sql_param_hash["paramArray"] unless sql_param_hash["paramArray"].nil?
412
+ end
413
+ else
414
+ scope = scope(:find) if :auto == scope
415
+ if scope && (scoped_group = scope[:group])
416
+ sql << " GROUP BY #{scoped_group}"
417
+ if scope[:having]
418
+ sql_param_hash = sanitize_sql_for_conditions(scope[:having])
419
+ sql << " HAVING #{sql_param_hash["sqlSegment"]}"
420
+ param_array = param_array + sql_param_hash["paramArray"] unless sql_param_hash["paramArray"].nil?
421
+ end
422
+ end
423
+ end
424
+ param_array
425
+ end
426
+
427
+ # The optional scope argument is for the current <tt>:find</tt> scope.
428
+ def add_joins!(sql, joins, scope = :auto)
429
+ param_array = []
430
+ scope = scope(:find) if :auto == scope
431
+
432
+ if joins.is_a?(Hash) && joins.has_key?("pstmt_hook")
433
+ param_array = joins["pstmt_hook"]["paramArray"]
434
+ joins = joins["pstmt_hook"]["sqlSegment"]
435
+ end
436
+
437
+ merged_joins = if scope && scope[:joins] && joins
438
+ join_merge_hash = merge_joins(scope[:joins], joins)
439
+ param_array = param_array + join_merge_hash["pstmt_hook"]["paramArray"]
440
+ join_merge_hash["pstmt_hook"]["sqlSegment"]
441
+ else
442
+ if(scope && scope[:joins].is_a?(Hash) && scope[:joins].has_key?("pstmt_hook"))
443
+ param_array = scope[:joins]["pstmt_hook"]["paramArray"]
444
+ (joins || scope[:joins]["pstmt_hook"]["sqlSegment"])
445
+ else
446
+ (joins || scope && scope[:joins])
447
+ end
448
+ end
449
+
450
+ case merged_joins
451
+ when Symbol, Hash, Array
452
+ if array_of_strings?(merged_joins)
453
+ sql << merged_joins.join(' ') + " "
454
+ else
455
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
456
+ sql << " #{join_dependency.join_associations.collect { |assoc|
457
+ sql_param_hash = assoc.association_join
458
+ param_array = param_array + sql_param_hash["paramArray"] unless sql_param_hash["paramArray"].nil?
459
+ sql_param_hash["sqlSegment"]
460
+ }.join} "
461
+ end
462
+ when String
463
+ sql << " #{merged_joins} "
464
+ end
465
+ param_array
466
+ end
467
+ private :construct_finder_sql, :expand_id_conditions, :add_joins!, :add_group!
468
+
469
+ def with_scope(method_scoping = {}, action = :merge, &block)
470
+ method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
471
+
472
+ # Dup first and second level of hash (method and params).
473
+ method_scoping = method_scoping.inject({}) do |hash, (method, params)|
474
+ hash[method] = (params == true) ? params : params.dup
475
+ hash
476
+ end
477
+
478
+ method_scoping.assert_valid_keys([ :find, :create ])
479
+
480
+ if f = method_scoping[:find]
481
+ f.assert_valid_keys(VALID_FIND_OPTIONS)
482
+ set_readonly_option! f
483
+ end
484
+
485
+ # Merge scopings
486
+ if [:merge, :reverse_merge].include?(action) && current_scoped_methods
487
+ method_scoping = current_scoped_methods.inject(method_scoping) do |hash, (method, params)|
488
+ case hash[method]
489
+ when Hash
490
+ if method == :find
491
+ (hash[method].keys + params.keys).uniq.each do |key|
492
+ merge = hash[method][key] && params[key] # merge if both scopes have the same key
493
+ if key == :conditions && merge
494
+ if params[key].is_a?(Hash) && hash[method][key].is_a?(Hash)
495
+ sql_param_hash = merge_conditions(hash[method][key].deep_merge(params[key]))
496
+ hash[method][key] = [sql_param_hash["sqlSegment"]] + sql_param_hash["paramArray"]
497
+ else
498
+ sql_param_hash = merge_conditions(params[key], hash[method][key])
499
+ hash[method][key] = [sql_param_hash["sqlSegment"]] + sql_param_hash["paramArray"]
500
+ end
501
+ elsif key == :include && merge
502
+ hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
503
+ elsif key == :joins && merge
504
+ hash[method][key] = merge_joins(params[key], hash[method][key])
505
+ else
506
+ hash[method][key] = hash[method][key] || params[key]
507
+ end
508
+ end
509
+ else
510
+ if action == :reverse_merge
511
+ hash[method] = hash[method].merge(params)
512
+ else
513
+ hash[method] = params.merge(hash[method])
514
+ end
515
+ end
516
+ else
517
+ hash[method] = params
518
+ end
519
+ hash
520
+ end
521
+ end
522
+
523
+ self.scoped_methods << method_scoping
524
+ begin
525
+ yield
526
+ ensure
527
+ self.scoped_methods.pop
528
+ end
529
+ end
530
+ protected :with_scope
531
+
532
+ def count_by_sql(sql)
533
+ sql_param_hash = sanitize_conditions(sql)
534
+ result = connection.prepared_select(sql_param_hash, "#{name} Count").first
535
+ #result will be of type Hash.
536
+ if result
537
+ return result.values.first.to_i #Retrieve the first value from hash
538
+ else
539
+ return 0
540
+ end
541
+ end
542
+
543
+ def quote_value(value, column = nil) #:nodoc:
544
+ connection.quote_value_for_pstmt(value,column)
545
+ end
546
+
547
+ def update_all(updates, conditions = nil, options = {})
548
+ sql_values_hash = sanitize_sql_for_assignment(updates)
549
+ param_array = sql_values_hash["paramArray"]
550
+
551
+ sql = "UPDATE #{quoted_table_name} SET #{sql_values_hash["sqlSegment"]} "
552
+
553
+ scope = scope(:find)
554
+
555
+ select_sql = ""
556
+ temp_param_array = add_conditions!(select_sql, conditions, scope)
557
+
558
+ if !param_array.nil? && !param_array.empty?
559
+ param_array += temp_param_array
560
+ else
561
+ param_array = temp_param_array
562
+ end
563
+
564
+ if options.has_key?(:limit) || (scope && scope[:limit])
565
+ # Only take order from scope if limit is also provided by scope, this
566
+ # is useful for updating a has_many association with a limit.
567
+ add_order!(select_sql, options[:order], scope)
568
+
569
+ temp_options = options.dup # Ensure that the necessary parameters are received in the duplicate, so that the original hash is intact
570
+ temp_options[:paramArray] = [] # To receive the values for limit and offset.
571
+ add_limit!(select_sql, temp_options, scope)
572
+ param_array = param_array + temp_options[:paramArray]
573
+
574
+ sql.concat(connection.limited_update_conditions(select_sql, quoted_table_name, connection.quote_column_name(primary_key)))
575
+ else
576
+ add_order!(select_sql, options[:order], nil)
577
+ sql.concat(select_sql)
578
+ end
579
+
580
+ pstmt = connection.prepare(sql, "#{name} Update")
581
+ connection.prepared_update(pstmt, param_array)
582
+ end
583
+
584
+ def update_counters_without_lock(id, counters)
585
+ updates = counters.inject([]) { |list, (counter_name, increment)|
586
+ sign = increment < 0 ? "-" : "+"
587
+ list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}"
588
+ }.join(", ")
589
+
590
+ if id.is_a?(Array)
591
+ ids_list = id.map {|i|
592
+ connection.quote_value_for_pstmt(i)
593
+ }
594
+ condition = ["#{connection.quote_column_name(primary_key)} IN (?)", ids_list]
595
+ else
596
+ param_value = connection.quote_value_for_pstmt(id)
597
+ condition = ["#{connection.quote_column_name(primary_key)} = ?", param_value]
598
+ end
599
+
600
+ update_all(updates, condition)
601
+ end
602
+
603
+ def delete_all(conditions = nil)
604
+ sql = "DELETE FROM #{quoted_table_name} "
605
+ param_array = add_conditions!(sql, conditions, scope(:find))
606
+ # Prepare the sql for deleting the rows
607
+ pstmt = connection.prepare(sql, "#{name} Delete all")
608
+ # Execute the prepared Statement
609
+ connection.prepared_delete(pstmt, param_array)
610
+ end
611
+
612
+ # Merges conditions so that the result is a valid +condition+
613
+ def merge_conditions(*conditions)
614
+ segments = []
615
+ return_hash = {}
616
+ return_hash["paramArray"] = []
617
+ conditions.each do |condition|
618
+ unless condition.blank?
619
+ sql_param_hash = sanitize_sql(condition)
620
+ unless sql_param_hash["sqlSegment"].blank?
621
+ segments << sql_param_hash["sqlSegment"]
622
+ if !sql_param_hash["paramArray"].nil? && !sql_param_hash["paramArray"].empty?
623
+ return_hash["paramArray"] = return_hash["paramArray"] +
624
+ sql_param_hash["paramArray"]
625
+ end
626
+ end
627
+ end
628
+ end
629
+
630
+ return_hash["sqlSegment"] = "(#{segments.join(') AND (')})" unless segments.empty?
631
+ return_hash
632
+ end
633
+
634
+ # Adds a sanitized version of +conditions+ to the +sql+ string. Note that the passed-in +sql+ string is changed.
635
+ # The optional scope argument is for the current <tt>:find</tt> scope.
636
+ def add_conditions!(sql, conditions, scope = :auto)
637
+ scope = scope(:find) if :auto == scope
638
+ conditions = [conditions]
639
+ conditions << scope[:conditions] if scope
640
+ conditions << type_condition if finder_needs_type_condition?
641
+ merged_conditions = merge_conditions(*conditions)
642
+ sql << "WHERE #{merged_conditions["sqlSegment"]} " unless merged_conditions["sqlSegment"].blank?
643
+ merged_conditions["paramArray"]
644
+ end
645
+
646
+ def type_condition(table_alias=nil)
647
+ param_array = []
648
+ quoted_table_alias = self.connection.quote_table_name(table_alias || table_name)
649
+ quoted_inheritance_column = connection.quote_column_name(inheritance_column)
650
+ param_array << self.connection.quote_value_for_pstmt(sti_name)
651
+ type_condition = subclasses.inject("#{quoted_table_alias}.#{quoted_inheritance_column} = ? ") do |condition, subclass|
652
+ param_array << self.connection.quote_value_for_pstmt(subclass.sti_name)
653
+ condition << "OR #{quoted_table_alias}.#{quoted_inheritance_column} = ? "
654
+ end
655
+
656
+ [" (#{type_condition}) "] + param_array
657
+ end
658
+
659
+ def attribute_condition(quoted_column_name, argument)
660
+ case argument
661
+ when nil then "#{quoted_column_name} IS NULL"
662
+ when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope then "#{quoted_column_name} IN (?)"
663
+ when Range then if argument.exclude_end?
664
+ "#{quoted_column_name} >= ? AND #{quoted_column_name} < ?"
665
+ else
666
+ "#{quoted_column_name} BETWEEN ? AND ?"
667
+ end
668
+ else "#{quoted_column_name} = ?"
669
+ end
670
+ end
671
+
672
+ private :add_conditions!, :type_condition, :attribute_condition
673
+
674
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
675
+ # { :status => nil, :group_id => 1 }
676
+ # # => "status = NULL , group_id = 1"
677
+ def sanitize_sql_hash_for_assignment(attrs)
678
+ return_hash = {}
679
+ return_hash["paramArray"] = []
680
+
681
+ return_hash["sqlSegment"] = attrs.map do |attr, value|
682
+ return_hash["paramArray"] += quote_bound_value(value)
683
+ "#{connection.quote_column_name(attr)} = ?"
684
+ end.join(', ')
685
+ return_hash
686
+ end
687
+
688
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
689
+ # them into a valid SQL fragment for a SET clause.
690
+ # { :name => nil, :group_id => 4 } returns "name = NULL , group_id='4'"
691
+ def sanitize_sql_for_assignment(assignments)
692
+ return_hash = {}
693
+ case assignments
694
+ when Array; sanitize_sql_array(assignments)
695
+ when Hash; sanitize_sql_hash_for_assignment(assignments)
696
+ else
697
+ return_hash["sqlSegment"] = assignments
698
+ return_hash["paramArray"] = nil
699
+ return_hash
700
+ end
701
+ end
702
+
703
+ def sanitize_sql_for_conditions(condition, table_name = quoted_table_name)
704
+ return nil if condition.blank?
705
+
706
+ return_hash = {}
707
+
708
+ case condition
709
+ when Array; sanitize_sql_array(condition)
710
+ when Hash; sanitize_sql_hash_for_conditions(condition, table_name)
711
+ else
712
+ return_hash["sqlSegment"] = condition
713
+ return_hash["paramArray"] = nil
714
+ return_hash
715
+ end
716
+ end
717
+ alias_method :sanitize_sql, :sanitize_sql_for_conditions
718
+ alias_method :sanitize_conditions, :sanitize_sql_for_conditions
719
+
720
+ # Accepts an array of conditions. The array has each value
721
+ # sanitized and interpolated into the SQL statement.
722
+ def sanitize_sql_array(ary)
723
+ statement, *values = ary
724
+ return_hash = {}
725
+
726
+ if values.first.is_a?(Hash) and statement =~ /:\w+/
727
+ replace_named_bind_variables(statement, values.first)
728
+ elsif statement && statement.include?('?')
729
+ replace_bind_variables(statement, values)
730
+ else
731
+ if !values.nil? && values.size > 0
732
+ return_hash["sqlSegment"] = statement % values.collect { |value| connection.quote_string(value.to_s) }
733
+ else
734
+ return_hash["sqlSegment"] = statement
735
+ end
736
+ return_hash["paramArray"] = []
737
+ return_hash
738
+ end
739
+ end
740
+
741
+ def sanitize_sql_hash_for_conditions(attrs, table_name = quoted_table_name)
742
+ attrs = expand_hash_conditions_for_aggregates(attrs)
743
+ temp_table_name = table_name
744
+
745
+ param_array = []
746
+
747
+ conditions = attrs.map do |attr, value|
748
+ unless value.is_a?(Hash)
749
+ attr = attr.to_s
750
+
751
+ # Extract table name from qualified attribute names.
752
+ if attr.include?('.')
753
+ table_name, attr = attr.split('.', 2)
754
+ table_name = connection.quote_table_name(table_name)
755
+ else
756
+ table_name = temp_table_name
757
+ end
758
+
759
+ param_array << value unless value.nil?
760
+ attribute_condition("#{table_name}.#{connection.quote_column_name(attr)}", value)
761
+ else
762
+ sql_param_hash = sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s))
763
+ param_array = param_array + sql_param_hash["paramArray"] unless sql_param_hash["paramArray"].empty?
764
+ sql_param_hash["sqlSegment"]
765
+ end
766
+ end.join(' AND ')
767
+
768
+ replace_bind_variables(conditions, expand_range_bind_variables(param_array))
769
+ end
770
+ alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
771
+
772
+ # Check delete_all method, which passes a ? and array of params, as an example.
773
+ # This method replace_bind_variables replaces those ? with a string of the values.
774
+ # For Eg:- if said Wood.delete([1234]), delete all sends the condition as ["id in (?)", [1,2,3,4]]
775
+ # This method sends the condition part back as string, "id in (1,2,3,4)" originally
776
+ # Now this method is modified to send out a hash containing the parameter array and the sql to be prepared
777
+ def replace_bind_variables(statement, values)
778
+ raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
779
+ bound = values.dup
780
+ return_hash = {}
781
+ return_hash["paramArray"] = []
782
+ return_hash["sqlSegment"] = ''
783
+
784
+ return_hash["sqlSegment"] =
785
+ statement.gsub('?') {
786
+ str_seg = ''
787
+ param_array = quote_bound_value(bound.shift)
788
+ if param_array && param_array.size > 1
789
+ for index in 0...param_array.size-1
790
+ str_seg << '?,'
791
+ end
792
+ end
793
+ str_seg << '?'
794
+ return_hash["paramArray"] = return_hash["paramArray"] + param_array unless param_array.nil?
795
+ str_seg
796
+ }
797
+ return_hash
798
+ end
799
+
800
+ # Replaces the named parameters with '?' and pass a hash containing the sql's condition clause segment and the parameters array
801
+ def replace_named_bind_variables(statement, bind_vars) #:nodoc:
802
+ return_hash = {}
803
+ return_hash["paramArray"] = []
804
+ return_hash["sqlSegment"] = ''
805
+
806
+ return_hash["sqlSegment"] =
807
+ statement.gsub(/(:?):([a-zA-Z]\w*)/) {
808
+
809
+ if $1 == ':' # skip postgresql casts
810
+ $& # return the whole match
811
+ elsif bind_vars.include?(match = $2.to_sym)
812
+ str_seg = ''
813
+ param_array = quote_bound_value(bind_vars[match])
814
+ if param_array.size > 1
815
+ for index in 0...param_array.size-1
816
+ str_seg << '?,'
817
+ end
818
+ end
819
+ str_seg << '?'
820
+ return_hash["paramArray"] = return_hash["paramArray"] + param_array
821
+ str_seg
822
+ else
823
+ raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
824
+ end
825
+ }
826
+ return_hash
827
+ end
828
+
829
+ # Returns an array of parameter values, with the values respectively quoted if of type date time or is nil
830
+ def quote_bound_value(value) #:nodoc:
831
+ if value.respond_to?(:map) && !value.acts_like?(:string)
832
+ if (value.respond_to?(:empty?) && value.empty?) || value.nil?
833
+ [nil]
834
+ else
835
+ value.map { |v|
836
+ connection.quote_value_for_pstmt(v)
837
+ }
838
+ end
839
+ else
840
+ [connection.quote_value_for_pstmt(value)]
841
+ end
842
+ end
843
+ protected :replace_bind_variables, :quote_bound_value,:replace_named_bind_variables
844
+ protected :sanitize_sql_array, :sanitize_sql_for_conditions, :sanitize_sql_hash_for_conditions
845
+ end #End of class << self
846
+ end #End of class Base
847
+
848
+ module Calculations
849
+ #Visit back. This is still not complete. Visit back after checking method construct_scope
850
+ module ClassMethods
851
+ def construct_calculation_sql(operation, column_name, options) #:nodoc:
852
+ return_hash = {}
853
+ return_hash["paramArray"] = []
854
+ parameter_array = []
855
+
856
+ operation = operation.to_s.downcase
857
+ options = options.symbolize_keys
858
+
859
+ scope = scope(:find)
860
+ merged_includes = merge_includes(scope ? scope[:include] : [], options[:include])
861
+ aggregate_alias = column_alias_for(operation, column_name)
862
+ column_name = "#{connection.quote_table_name(table_name)}.#{column_name}" if column_names.include?(column_name.to_s)
863
+
864
+ if operation == 'count'
865
+ if merged_includes.any?
866
+ options[:distinct] = true
867
+ column_name = options[:select] || [connection.quote_table_name(table_name), primary_key] * '.'
868
+ end
869
+
870
+ if options[:distinct]
871
+ use_workaround = !connection.supports_count_distinct?
872
+ end
873
+ end
874
+
875
+ if options[:distinct] && column_name.to_s !~ /\s*DISTINCT\s+/i
876
+ distinct = 'DISTINCT '
877
+ end
878
+ sql = "SELECT #{operation}(#{distinct}#{column_name}) AS #{aggregate_alias}"
879
+
880
+ # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
881
+ sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround
882
+
883
+ sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
884
+ if options[:from]
885
+ sql << " FROM #{options[:from]} "
886
+ else
887
+ sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround
888
+ sql << " FROM #{connection.quote_table_name(table_name)} "
889
+ end
890
+
891
+ joins = ""
892
+ param_array = add_joins!(joins, options[:joins], scope)
893
+
894
+ if merged_includes.any?
895
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, joins)
896
+ sql << join_dependency.join_associations.collect{|join|
897
+ sql_param_hash = join.association_join
898
+ parameter_array = parameter_array + sql_param_hash["paramArray"] unless sql_param_hash["paramArray"].nil?
899
+ sql_param_hash["sqlSegment"]
900
+ }.join
901
+ end
902
+
903
+ unless joins.blank?
904
+ sql << joins
905
+ parameter_array = parameter_array + param_array unless param_array.nil?
906
+ end
907
+
908
+ parameter_array = parameter_array + add_conditions!(sql, options[:conditions], scope)
909
+ parameter_array = parameter_array + add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
910
+
911
+ if options[:group]
912
+ group_key = connection.adapter_name == 'FrontBase' ? :group_alias : :group_field
913
+ sql << " GROUP BY #{options[group_key]} "
914
+ end
915
+
916
+ if options[:group] && options[:having]
917
+ having = sanitize_sql_for_conditions(options[:having])
918
+
919
+ sql << " HAVING #{having["sqlSegment"]} "
920
+ parameter_array = parameter_array + having["paramArray"] unless having["paramArray"].nil?
921
+ end
922
+
923
+ sql << " ORDER BY #{options[:order]} " if options[:order]
924
+
925
+ temp_options = options.dup # Ensure that the necessary parameters are received in the duplicate, so that the original hash is intact
926
+ temp_options[:paramArray] = [] # To receive the values for limit and offset.
927
+ add_limit!(sql, temp_options, scope)
928
+ parameter_array = parameter_array + temp_options[:paramArray]
929
+
930
+ sql << ") #{aggregate_alias}_subquery" if use_workaround
931
+
932
+ return_hash["sqlSegment"] = sql
933
+ return_hash["paramArray"] = parameter_array
934
+ return_hash
935
+ end
936
+
937
+ def execute_simple_calculation(operation, column_name, column, options) #:nodoc:
938
+ result = connection.prepared_select(construct_calculation_sql(operation, column_name, options))
939
+ value = result.first.values.first if result # If result set conatins a row then pick the first value of the first row
940
+ type_cast_calculated_value(value, column, operation)
941
+ end
942
+
943
+ def execute_grouped_calculation(operation, column_name, column, options) #:nodoc:
944
+ group_attr = options[:group].to_s
945
+ association = reflect_on_association(group_attr.to_sym)
946
+ associated = association && association.macro == :belongs_to # only count belongs_to associations
947
+ group_field = associated ? association.primary_key_name : group_attr
948
+ group_alias = column_alias_for(group_field)
949
+ group_column = column_for group_field
950
+ sql = construct_calculation_sql(operation, column_name, options.merge(:group_field => group_field, :group_alias => group_alias))
951
+ calculated_data = connection.prepared_select(sql)
952
+ aggregate_alias = column_alias_for(operation, column_name)
953
+
954
+ if association
955
+ key_ids = calculated_data.collect { |row| row[group_alias] }
956
+ key_records = association.klass.base_class.find(key_ids)
957
+ key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) }
958
+ end
959
+
960
+ calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row|
961
+ key = type_cast_calculated_value(row[group_alias], group_column)
962
+ key = key_records[key] if associated
963
+ value = row[aggregate_alias]
964
+ all[key] = type_cast_calculated_value(value, column, operation)
965
+ all
966
+ end
967
+ end
968
+
969
+ protected :construct_calculation_sql, :execute_simple_calculation, :execute_grouped_calculation
970
+ end # End of module classMethods
971
+ end #End of module calculations
972
+
973
+ class Migrator
974
+
975
+ class << self
976
+ def get_all_versions
977
+ sql = "SELECT version FROM #{schema_migrations_table_name}"
978
+ # Check method prepared_select_values signature to know the reason for the hash in the argument below
979
+ Base.connection.prepared_select_values({"sqlSegment" => sql, "paramArray" => nil}).map(&:to_i).sort
980
+ end
981
+ end
982
+
983
+ def record_version_state_after_migrating(version)
984
+ sm_table = self.class.schema_migrations_table_name
985
+
986
+ paramArray = []
987
+ @migrated_versions ||= []
988
+ if down?
989
+ @migrated_versions.delete(version.to_i)
990
+ pstmt = Base.connection.prepare("DELETE FROM #{sm_table} WHERE version = ? ")
991
+ paramArray << Base.connection.quote_value_for_pstmt(version)
992
+ Base.connection.prepared_update(pstmt, paramArray)
993
+ else
994
+ @migrated_versions.push(version.to_i).sort!
995
+ pstmt = Base.connection.prepare("INSERT INTO #{sm_table} (version) VALUES (?)")
996
+ paramArray << Base.connection.quote_value_for_pstmt(version)
997
+ Base.connection.execute_prepared_stmt(pstmt, paramArray)
998
+ end
999
+ end
1000
+ private :record_version_state_after_migrating
1001
+ end
1002
+
1003
+ module AssociationPreload
1004
+ module ClassMethods
1005
+
1006
+ def preload_belongs_to_association(records, reflection, preload_options={})
1007
+ return if records.first.send("loaded_#{reflection.name}?")
1008
+ options = reflection.options
1009
+ primary_key_name = reflection.primary_key_name
1010
+
1011
+ if options[:polymorphic]
1012
+ polymorph_type = options[:foreign_type]
1013
+ klasses_and_ids = {}
1014
+
1015
+ # Construct a mapping from klass to a list of ids to load and a mapping of those ids back to their parent_records
1016
+ records.each do |record|
1017
+ if klass = record.send(polymorph_type)
1018
+ klass_id = record.send(primary_key_name)
1019
+ if klass_id
1020
+ id_map = klasses_and_ids[klass] ||= {}
1021
+ id_list_for_klass_id = (id_map[klass_id.to_s] ||= [])
1022
+ id_list_for_klass_id << record
1023
+ end
1024
+ end
1025
+ end
1026
+ klasses_and_ids = klasses_and_ids.to_a
1027
+ else
1028
+ id_map = {}
1029
+ records.each do |record|
1030
+ key = record.send(primary_key_name)
1031
+ if key
1032
+ mapped_records = (id_map[key.to_s] ||= [])
1033
+ mapped_records << record
1034
+ end
1035
+ end
1036
+ klasses_and_ids = [[reflection.klass.name, id_map]]
1037
+ end
1038
+
1039
+ klasses_and_ids.each do |klass_and_id|
1040
+ klass_name, id_map = *klass_and_id
1041
+ next if id_map.empty?
1042
+ klass = klass_name.constantize
1043
+
1044
+ table_name = klass.quoted_table_name
1045
+ primary_key = klass.primary_key
1046
+ column_type = klass.columns.detect{|c| c.name == primary_key}.type
1047
+ ids = id_map.keys.map do |id|
1048
+ if column_type == :integer
1049
+ id.to_i
1050
+ elsif column_type == :float
1051
+ id.to_f
1052
+ else
1053
+ id
1054
+ end
1055
+ end
1056
+ conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} #{in_or_equals_for_ids(ids)}"
1057
+
1058
+ sql_segment, *parameterArray = append_conditions(reflection, preload_options)
1059
+ conditions << sql_segment
1060
+ parameterArray = [] if parameterArray.nil?
1061
+
1062
+ associated_records = klass.with_exclusive_scope do
1063
+ klass.find(:all, :conditions => [conditions, ids] + parameterArray,
1064
+ :include => options[:include],
1065
+ :select => options[:select],
1066
+ :joins => options[:joins],
1067
+ :order => options[:order])
1068
+ end
1069
+ set_association_single_records(id_map, reflection.name, associated_records, primary_key)
1070
+ end
1071
+ end
1072
+
1073
+ def preload_has_and_belongs_to_many_association(records, reflection, preload_options={})
1074
+ table_name = reflection.klass.quoted_table_name
1075
+ id_to_record_map, ids = construct_id_map(records)
1076
+ records.each {|record| record.send(reflection.name).loaded}
1077
+ options = reflection.options
1078
+
1079
+ conditions = "t0.#{reflection.primary_key_name} #{in_or_equals_for_ids(ids)}"
1080
+ sql_segment, *paramterArray = append_conditions(reflection, preload_options)
1081
+ conditions << sql_segment
1082
+ parameterArray = [] if parameterArray.nil?
1083
+
1084
+ associated_records = reflection.klass.with_exclusive_scope do
1085
+ reflection.klass.find(:all, :conditions => [conditions, ids] + parameterArray,
1086
+ :include => options[:include],
1087
+ :joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}",
1088
+ :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id",
1089
+ :order => options[:order])
1090
+ end
1091
+ set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'the_parent_record_id')
1092
+ end
1093
+
1094
+ def find_associated_records(ids, reflection, preload_options)
1095
+ options = reflection.options
1096
+ table_name = reflection.klass.quoted_table_name
1097
+
1098
+ if interface = reflection.options[:as]
1099
+ conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} #{in_or_equals_for_ids(ids)} and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.sti_name}'"
1100
+ else
1101
+ foreign_key = reflection.primary_key_name
1102
+ conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} #{in_or_equals_for_ids(ids)}"
1103
+ end
1104
+
1105
+ sql_segment, *parameterArray = append_conditions(reflection, preload_options)
1106
+ conditions << sql_segment
1107
+ parameterArray = [] if parameterArray.nil?
1108
+
1109
+ reflection.klass.with_exclusive_scope do
1110
+ reflection.klass.find(:all,
1111
+ :select => (preload_options[:select] || options[:select] || "#{table_name}.*"),
1112
+ :include => preload_options[:include] || options[:include],
1113
+ :conditions => [conditions, ids] + parameterArray,
1114
+ :joins => options[:joins],
1115
+ :group => preload_options[:group] || options[:group],
1116
+ :order => preload_options[:order] || options[:order])
1117
+ end
1118
+ end
1119
+
1120
+ def append_conditions(reflection, preload_options)
1121
+ param_array = []
1122
+ sql = ""
1123
+ if sql_param_hash = reflection.sanitized_conditions
1124
+ sql << " AND (#{interpolate_sql_for_preload(sql_param_hash["sqlSegment"])})"
1125
+ param_array = param_array + sql_param_hash["paramArray"] unless sql_param_hash["paramArray"].nil?
1126
+ end
1127
+ if preload_options[:conditions]
1128
+ sql_param_hash = sanitize_sql preload_options[:conditions]
1129
+ sql << " AND (#{sql_param_hash["sqlSegment"]})"
1130
+ param_array = param_array + sql_param_hash["paramArray"] unless sql_param_hash["paramArray"].nil?
1131
+ end
1132
+ [sql] + param_array
1133
+ end
1134
+
1135
+ private :append_conditions, :find_associated_records, :preload_belongs_to_association
1136
+ private :preload_has_and_belongs_to_many_association
1137
+ end # End of module ClassMethods
1138
+ end # End of module AssociationPreload
1139
+
1140
+ module Associations
1141
+
1142
+ module ClassMethods
1143
+ def joined_tables(options)
1144
+ scope = scope(:find)
1145
+
1146
+ if options[:joins].is_a?(Hash) && options[:joins].has_key?("pstmt_hook")
1147
+ param_array = options[:joins]["pstmt_hook"]["paramArray"]
1148
+ joins = options[:joins]["pstmt_hook"]["sqlSegment"]
1149
+ else
1150
+ joins = options[:joins]
1151
+ end
1152
+
1153
+ merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins)["pstmt_hook"]["sqlSegment"] : (joins || scope && scope[:joins])
1154
+ [table_name] + case merged_joins
1155
+ when Symbol, Hash, Array
1156
+ if array_of_strings?(merged_joins)
1157
+ tables_in_string(merged_joins.join(' '))
1158
+ else
1159
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
1160
+ join_dependency.join_associations.collect {|join_association| [join_association.aliased_join_table_name, join_association.aliased_table_name]}.flatten.compact
1161
+ end
1162
+ else
1163
+ tables_in_string(merged_joins)
1164
+ end
1165
+ end
1166
+ private :joined_tables
1167
+ end
1168
+
1169
+ class HasOneAssociation < BelongsToAssociation
1170
+ def find_target
1171
+ @reflection.klass.find(:first,
1172
+ :conditions => [@finder_sql] + @finder_sql_param_array,
1173
+ :select => @reflection.options[:select],
1174
+ :order => @reflection.options[:order],
1175
+ :include => @reflection.options[:include],
1176
+ :readonly => @reflection.options[:readonly]
1177
+ )
1178
+ end
1179
+
1180
+ def construct_sql
1181
+ @finder_sql_param_array = []
1182
+ case
1183
+ when @reflection.options[:as]
1184
+ @finder_sql =
1185
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = ? AND " +
1186
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = ?"
1187
+ @finder_sql_param_array << owner_quoted_id
1188
+ @finder_sql_param_array << @owner.class.quote_value(@owner.class.base_class.name.to_s)
1189
+ else
1190
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = ?"
1191
+ @finder_sql_param_array << owner_quoted_id
1192
+ end
1193
+ if conditions
1194
+ condition, *parameters = conditions
1195
+ @finder_sql << " AND (#{condition})"
1196
+ @finder_sql_param_array = @finder_sql_param_array + parameters unless parameters.nil?
1197
+ end
1198
+ end
1199
+ end # End of class HasOneAssociation < BelongsToAssociation
1200
+
1201
+ class HasManyThroughAssociation < HasManyAssociation
1202
+ # Build SQL conditions from attributes, qualified by table name.
1203
+ def construct_conditions
1204
+ param_array = []
1205
+ table_name = @reflection.through_reflection.quoted_table_name
1206
+ conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
1207
+ param_array = param_array + [value]
1208
+ "#{table_name}.#{attr} = ?"
1209
+ end
1210
+
1211
+ if sql_param_hash = sql_conditions
1212
+ conditions << sql_param_hash["sqlSegment"] unless sql_param_hash["sqlSegment"].blank?
1213
+ param_array = param_array + sql_param_hash["paramArray"] unless sql_param_hash["paramArray"].nil?
1214
+ end
1215
+ ["(" + conditions.join(') AND (') + ")"] + param_array
1216
+ end
1217
+
1218
+ def construct_joins(custom_joins = nil)
1219
+ polymorphic_join = nil
1220
+ parameterArray = []
1221
+
1222
+ if @reflection.source_reflection.macro == :belongs_to
1223
+ reflection_primary_key = @reflection.klass.primary_key
1224
+ source_primary_key = @reflection.source_reflection.primary_key_name
1225
+ if @reflection.options[:source_type]
1226
+ polymorphic_join = "AND %s.%s = ?" % [
1227
+ @reflection.through_reflection.quoted_table_name, "#{@reflection.source_reflection.options[:foreign_type]}"
1228
+ ]
1229
+ parameterArray = [@owner.class.quote_value(@reflection.options[:source_type])]
1230
+ end
1231
+ else
1232
+ reflection_primary_key = @reflection.source_reflection.primary_key_name
1233
+ source_primary_key = @reflection.through_reflection.klass.primary_key
1234
+ if @reflection.source_reflection.options[:as]
1235
+ polymorphic_join = "AND %s.%s = ?" % [
1236
+ @reflection.quoted_table_name, "#{@reflection.source_reflection.options[:as]}_type"
1237
+ ]
1238
+ parameterArray = [@owner.class.quote_value(@reflection.through_reflection.klass.name)]
1239
+ end
1240
+ end
1241
+
1242
+ sql_param_hash = {"sqlSegment" =>
1243
+ "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [
1244
+ @reflection.through_reflection.quoted_table_name,
1245
+ @reflection.quoted_table_name, reflection_primary_key,
1246
+ @reflection.through_reflection.quoted_table_name, source_primary_key,
1247
+ polymorphic_join
1248
+ ]
1249
+ }
1250
+ sql_param_hash["paramArray"] = parameterArray
1251
+ {"pstmt_hook" => sql_param_hash}
1252
+ end
1253
+
1254
+ def construct_sql
1255
+ case
1256
+ when @reflection.options[:finder_sql]
1257
+ # Not found a test case yet. Also the below 2 lines are a bit ambiguous, because finder_sql is been re assigned
1258
+ @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
1259
+
1260
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = ?"
1261
+ @finder_sql_param_array = [owner_quoted_id]
1262
+ if conditions
1263
+ condition, *parameters = conditions
1264
+ @finder_sql << " AND (#{condition})"
1265
+ @finder_sql_param_array = @finder_sql_param_array + parameters unless parameters.nil?
1266
+ end
1267
+ else
1268
+ @finder_sql, *@finder_sql_param_array = construct_conditions
1269
+ end
1270
+
1271
+ if @reflection.options[:counter_sql]
1272
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
1273
+ elsif @reflection.options[:finder_sql]
1274
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
1275
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
1276
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
1277
+ else
1278
+ @counter_sql = @finder_sql
1279
+ @counter_sql_param_array = @finder_sql_param_array
1280
+ end
1281
+ end
1282
+
1283
+ def build_conditions
1284
+ sql_param_hash = {}
1285
+ param_array = []
1286
+ association_conditions = @reflection.options[:conditions]
1287
+ through_conditions = build_through_conditions
1288
+ source_conditions = @reflection.source_reflection.options[:conditions]
1289
+ uses_sti = !@reflection.through_reflection.klass.descends_from_active_record?
1290
+
1291
+ if association_conditions || through_conditions || source_conditions || uses_sti
1292
+ all = []
1293
+
1294
+ [association_conditions, source_conditions].each do |conditions|
1295
+ if conditions
1296
+ returned_hash = sanitize_sql(conditions)
1297
+ all << interpolate_sql(returned_hash["sqlSegment"])
1298
+ param_array = param_array + returned_hash["paramArray"] unless returned_hash["paramArray"].nil?
1299
+ end
1300
+ end
1301
+
1302
+ if !through_conditions["sqlSegment"].blank?
1303
+ all << through_conditions["sqlSegment"]
1304
+ param_array = param_array + through_conditions["paramArray"] unless through_conditions["paramArray"].nil?
1305
+ end
1306
+
1307
+ if uses_sti
1308
+ sqlsegment, *parameterArray = build_sti_condition
1309
+ all << sqlsegment
1310
+ param_array = param_array + parameterArray
1311
+ end
1312
+
1313
+ sql_param_hash["sqlSegment"] = all.map { |sql| "(#{sql})" } * ' AND '
1314
+ sql_param_hash["paramArray"] = param_array
1315
+ sql_param_hash
1316
+ end
1317
+ end
1318
+
1319
+ def build_through_conditions
1320
+ sql_param_hash = {}
1321
+ conditions = @reflection.through_reflection.options[:conditions]
1322
+ if conditions.is_a?(Hash)
1323
+ sql_param_hash = sanitize_sql(conditions)
1324
+ sql_param_hash["sqlSegment"] = interpolate_sql(sql_param_hash["sqlSegment"]).gsub(
1325
+ @reflection.quoted_table_name,
1326
+ @reflection.through_reflection.quoted_table_name)
1327
+ elsif conditions
1328
+ sql_param_hash = sanitize_sql(conditions)
1329
+ sql_param_hash["sqlSegment"] = interpolate_sql(sql_param_hash["sqlSegment"])
1330
+ end
1331
+ sql_param_hash
1332
+ end
1333
+
1334
+ protected :construct_sql, :construct_scope, :construct_conditions, :construct_joins
1335
+ protected :build_through_conditions, :build_conditions
1336
+ end # End of class HasManyThroughAssociation < HasManyAssociation
1337
+
1338
+ class HasManyAssociation < AssociationCollection
1339
+ def count_records
1340
+ count = if has_cached_counter?
1341
+ @owner.send(:read_attribute, cached_counter_attribute_name)
1342
+ elsif @reflection.options[:counter_sql]
1343
+ @reflection.klass.count_by_sql(@counter_sql)
1344
+ else
1345
+ @reflection.klass.count(:conditions => [@counter_sql] + @counter_sql_param_array , :include => @reflection.options[:include])
1346
+ end
1347
+
1348
+ # If there's nothing in the database and @target has no new records
1349
+ # we are certain the current target is an empty array. This is a
1350
+ # documented side-effect of the method that may avoid an extra SELECT.
1351
+ @target ||= [] and loaded if count == 0
1352
+
1353
+ if @reflection.options[:limit]
1354
+ count = [ @reflection.options[:limit], count ].min
1355
+ end
1356
+
1357
+ return count
1358
+ end
1359
+
1360
+ def construct_sql
1361
+ @finder_sql_param_array = []
1362
+ case
1363
+ when @reflection.options[:finder_sql]
1364
+ @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
1365
+
1366
+ when @reflection.options[:as]
1367
+ @finder_sql =
1368
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = ? AND " +
1369
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = ?"
1370
+ @finder_sql_param_array << owner_quoted_id
1371
+ @finder_sql_param_array << @owner.class.quote_value(@owner.class.base_class.name.to_s)
1372
+ if conditions
1373
+ condition, *parameters = conditions
1374
+ @finder_sql << " AND (#{condition})"
1375
+ @finder_sql_param_array = @finder_sql_param_array + parameters unless parameters.nil?
1376
+ end
1377
+
1378
+ else
1379
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = ?"
1380
+ @finder_sql_param_array << [owner_quoted_id]
1381
+ if conditions
1382
+ condition, *parameters = conditions
1383
+ @finder_sql << " AND (#{condition})"
1384
+ @finder_sql_param_array = @finder_sql_param_array + parameters unless parameters.nil?
1385
+ end
1386
+ end
1387
+
1388
+ if @reflection.options[:counter_sql]
1389
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
1390
+ elsif @reflection.options[:finder_sql]
1391
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
1392
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
1393
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
1394
+ else
1395
+ @counter_sql = @finder_sql
1396
+ @counter_sql_param_array = @finder_sql_param_array
1397
+ end
1398
+ end
1399
+
1400
+ # Deletes the records according to the <tt>:dependent</tt> option.
1401
+ def delete_records(records)
1402
+ case @reflection.options[:dependent]
1403
+ when :destroy
1404
+ records.each { |r| r.destroy }
1405
+ when :delete_all
1406
+ @reflection.klass.delete(records.map { |record| record.id })
1407
+ else
1408
+ ids = quoted_record_ids(records)
1409
+ @reflection.klass.update_all(
1410
+ ["#{@reflection.primary_key_name} = ?", nil],
1411
+ ["#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (?)",ids]
1412
+ )
1413
+ @owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter?
1414
+ end
1415
+ end
1416
+
1417
+ def construct_scope
1418
+ create_scoping = {}
1419
+ set_belongs_to_association_for(create_scoping)
1420
+ {
1421
+ :find => { :conditions => [@finder_sql] + @finder_sql_param_array, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit], :include => @reflection.options[:include]},
1422
+ :create => create_scoping
1423
+ }
1424
+ end
1425
+
1426
+ protected :construct_sql, :delete_records, :construct_scope, :count_records
1427
+ end # End of class HasManyAssociation
1428
+
1429
+ class AssociationCollection < AssociationProxy
1430
+ def find(*args)
1431
+ options = args.extract_options!
1432
+
1433
+ param_array = []
1434
+ # If using a custom finder_sql, scan the entire collection.
1435
+ if @reflection.options[:finder_sql]
1436
+ expects_array = args.first.kind_of?(Array)
1437
+ ids = args.flatten.compact.uniq.map { |arg| arg.to_i }
1438
+
1439
+ if ids.size == 1
1440
+ id = ids.first
1441
+ record = load_target.detect { |r| id == r.id }
1442
+ expects_array ? [ record ] : record
1443
+ else
1444
+ load_target.select { |r| ids.include?(r.id) }
1445
+ end
1446
+ else
1447
+ conditions = "#{@finder_sql}"
1448
+ param_array = param_array + @finder_sql_param_array unless @finder_sql_param_array.nil?
1449
+ if sanitized_conditions = sanitize_sql(options[:conditions])
1450
+ conditions << " AND (#{sanitized_conditions["sqlSegment"]})"
1451
+ unless sanitized_conditions["paramArray"].nil?
1452
+ param_array = param_array + sanitized_conditions["paramArray"]
1453
+ end
1454
+ end
1455
+
1456
+ if param_array.nil?
1457
+ options[:conditions] = conditions
1458
+ else
1459
+ options[:conditions] = [conditions] + param_array
1460
+ end
1461
+
1462
+ if options[:order] && @reflection.options[:order]
1463
+ options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
1464
+ elsif @reflection.options[:order]
1465
+ options[:order] = @reflection.options[:order]
1466
+ end
1467
+
1468
+ # Build options specific to association
1469
+ construct_find_options!(options)
1470
+
1471
+ merge_options_from_reflection!(options)
1472
+
1473
+ # Pass through args exactly as we received them.
1474
+ args << options
1475
+ @reflection.klass.find(*args)
1476
+ end
1477
+ end
1478
+ end # End of class AssociationCollection
1479
+
1480
+ module ClassMethods
1481
+ def select_all_rows(options, join_dependency)
1482
+ connection.prepared_select(
1483
+ construct_finder_sql_with_included_associations(options, join_dependency),
1484
+ "#{name} Load Including Associations"
1485
+ )
1486
+ end
1487
+
1488
+ def construct_finder_sql_with_included_associations(options, join_dependency)
1489
+ param_array = []
1490
+ scope = scope(:find)
1491
+ sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
1492
+ sql << join_dependency.join_associations.collect{|join|
1493
+ sql_param_hash = join.association_join
1494
+ param_array = param_array + sql_param_hash["paramArray"] unless sql_param_hash["paramArray"].nil?
1495
+ sql_param_hash["sqlSegment"]
1496
+ }.join
1497
+
1498
+ param_array = param_array + add_joins!(sql, options[:joins], scope)
1499
+ param_array = param_array + add_conditions!(sql, options[:conditions], scope)
1500
+ param_array = param_array + add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
1501
+
1502
+ param_array = param_array + add_group!(sql, options[:group], options[:having], scope)
1503
+ add_order!(sql, options[:order], scope)
1504
+
1505
+ temp_options = options.dup # Ensure that the necessary parameters are received in the duplicate, so that the original hash is intact
1506
+ temp_options[:paramArray] = [] # To receive the values for limit and offset.
1507
+ add_limit!(sql, temp_options, scope) if using_limitable_reflections?(join_dependency.reflections)
1508
+ param_array = param_array + temp_options[:paramArray]
1509
+
1510
+ add_lock!(sql, options, scope)
1511
+
1512
+ return sanitize_sql([sql] + param_array)
1513
+ end
1514
+
1515
+ def add_limited_ids_condition!(sql, options, join_dependency)
1516
+ unless (id_list = select_limited_ids_list(options, join_dependency)).empty?
1517
+ sql_segment = id_list.map{ '?'}.join(', ')
1518
+ sql << "#{condition_word(sql)} #{connection.quote_table_name table_name}.#{primary_key} IN (#{sql_segment}) "
1519
+ id_list
1520
+ else
1521
+ throw :invalid_query
1522
+ end
1523
+ end
1524
+
1525
+ def select_limited_ids_list(options, join_dependency)
1526
+ pk = columns_hash[primary_key]
1527
+ connection.prepared_select(
1528
+ construct_finder_sql_for_association_limiting(options, join_dependency),
1529
+ "#{name} Load IDs For Limited Eager Loading"
1530
+ ).collect { |row| connection.quote_value_for_pstmt(row[primary_key], pk) }
1531
+ end
1532
+
1533
+ def construct_finder_sql_for_association_limiting(options, join_dependency)
1534
+ scope = scope(:find)
1535
+
1536
+ parameter_array = []
1537
+
1538
+ # Only join tables referenced in order or conditions since this is particularly slow on the pre-query.
1539
+ tables_from_conditions = conditions_tables(options)
1540
+ tables_from_order = order_tables(options)
1541
+ all_tables = tables_from_conditions + tables_from_order
1542
+ distinct_join_associations = all_tables.uniq.map{|table|
1543
+ join_dependency.joins_for_table_name(table)
1544
+ }.flatten.compact.uniq
1545
+
1546
+ order = options[:order]
1547
+ if scoped_order = (scope && scope[:order])
1548
+ order = order ? "#{order}, #{scoped_order}" : scoped_order
1549
+ end
1550
+
1551
+ is_distinct = !options[:joins].blank? || include_eager_conditions?(options, tables_from_conditions) || include_eager_order?(options, tables_from_order)
1552
+ sql = "SELECT "
1553
+ if is_distinct
1554
+ sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", order)
1555
+ else
1556
+ sql << primary_key
1557
+ end
1558
+ sql << " FROM #{connection.quote_table_name table_name} "
1559
+
1560
+ if is_distinct
1561
+ sql << distinct_join_associations.collect { |assoc|
1562
+ sql_param_hash = assoc.association_join
1563
+ parameter_array = parameter_array + sql_param_hash["paramArray"]
1564
+ sql_param_hash["sqlSegment"]
1565
+ }.join
1566
+ parameter_array = parameter_array + add_joins!(sql, options[:joins], scope)
1567
+ end
1568
+
1569
+ parameter_array = parameter_array + add_conditions!(sql, options[:conditions], scope)
1570
+ parameter_array = parameter_array + add_group!(sql, options[:group], options[:having], scope)
1571
+
1572
+ if order && is_distinct
1573
+ connection.add_order_by_for_association_limiting!(sql, :order => order)
1574
+ else
1575
+ add_order!(sql, options[:order], scope)
1576
+ end
1577
+
1578
+ temp_options = options.dup # Ensure that the necessary parameters are received in the duplicate, so that the original hash is intact
1579
+ temp_options[:paramArray] = [] # To receive the values for limit and offset.
1580
+ add_limit!(sql, temp_options, scope)
1581
+ parameter_array = parameter_array + temp_options[:paramArray]
1582
+
1583
+ return sanitize_sql([sql] + parameter_array)
1584
+ end
1585
+
1586
+ def configure_dependency_for_has_many(reflection, extra_conditions = nil)
1587
+ if reflection.options.include?(:dependent)
1588
+ # Add polymorphic type if the :as option is present
1589
+ dependent_conditions = []
1590
+ param_array = []
1591
+ #record.quoted_id is to be passed. But the evaluation is deffered, hence this is to be passed in module_eval in case delete_all below
1592
+ dependent_conditions << "#{reflection.primary_key_name} = ?" # The value is passed in below in respective cases
1593
+ if reflection.options[:as]
1594
+ dependent_conditions << "#{reflection.options[:as]}_type = ?"
1595
+ param_array << base_class.name
1596
+ end
1597
+ if reflection.options[:conditions]
1598
+ sql_param_hash = sanitize_sql(reflection.options[:conditions], reflection.quoted_table_name)
1599
+ dependent_conditions << sql_param_hash["sqlSegment"]
1600
+ param_array = param_array + sql_param_hash["paramArray"] if !sql_param_hash["paramArray"].nil?
1601
+ end
1602
+ dependent_conditions << extra_conditions if extra_conditions
1603
+ dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
1604
+ dependent_conditions = dependent_conditions.gsub('@', '\@')
1605
+ case reflection.options[:dependent]
1606
+ when :destroy
1607
+ method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
1608
+ define_method(method_name) do
1609
+ send(reflection.name).each { |o| o.destroy }
1610
+ end
1611
+ before_destroy method_name
1612
+ when :delete_all
1613
+ module_eval %Q{
1614
+ before_destroy do |record|
1615
+ delete_all_has_many_dependencies(record,
1616
+ "#{reflection.name}",
1617
+ #{reflection.class_name},
1618
+ [dependent_conditions] + ["\#{record.#{reflection.name}.send(:owner_quoted_id)}"] + param_array)
1619
+ end
1620
+ }
1621
+ when :nullify
1622
+ module_eval %Q{
1623
+ before_destroy do |record|
1624
+ nullify_has_many_dependencies(record,
1625
+ "#{reflection.name}",
1626
+ #{reflection.class_name},
1627
+ "#{reflection.primary_key_name}",
1628
+ [dependent_conditions] + ["\#{record.#{reflection.name}.send(:owner_quoted_id)}"] + param_array)
1629
+ end
1630
+ }
1631
+ else
1632
+ raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, or :nullify (#{reflection.options[:dependent].inspect})"
1633
+ end
1634
+ end
1635
+ end
1636
+ private :select_all_rows, :construct_finder_sql_with_included_associations
1637
+ private :configure_dependency_for_has_many
1638
+ private :construct_finder_sql_for_association_limiting, :select_limited_ids_list, :add_limited_ids_condition!
1639
+
1640
+ class JoinDependency
1641
+ class JoinAssociation < JoinBase
1642
+ def association_join
1643
+ return_hash = {} # A Hash to conatin the parameterised sql and the parameters
1644
+ parameter_array = []
1645
+
1646
+ connection = reflection.active_record.connection
1647
+ join = case reflection.macro
1648
+ when :has_and_belongs_to_many
1649
+ " #{join_type} %s ON %s.%s = %s.%s " % [
1650
+ table_alias_for(options[:join_table], aliased_join_table_name),
1651
+ connection.quote_table_name(aliased_join_table_name),
1652
+ options[:foreign_key] || reflection.active_record.to_s.foreign_key,
1653
+ connection.quote_table_name(parent.aliased_table_name),
1654
+ reflection.active_record.primary_key] +
1655
+ " #{join_type} %s ON %s.%s = %s.%s " % [
1656
+ table_name_and_alias,
1657
+ connection.quote_table_name(aliased_table_name),
1658
+ klass.primary_key,
1659
+ connection.quote_table_name(aliased_join_table_name),
1660
+ options[:association_foreign_key] || klass.to_s.foreign_key
1661
+ ]
1662
+ when :has_many, :has_one
1663
+ case
1664
+ when reflection.options[:through]
1665
+ through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
1666
+
1667
+ jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
1668
+ first_key = second_key = as_extra = nil
1669
+
1670
+ if through_reflection.options[:as] # has_many :through against a polymorphic join
1671
+ jt_foreign_key = through_reflection.options[:as].to_s + '_id'
1672
+ jt_as_extra = " AND %s.%s = ?" % [
1673
+ connection.quote_table_name(aliased_join_table_name),
1674
+ connection.quote_column_name(through_reflection.options[:as].to_s + '_type')
1675
+ ]
1676
+ parameter_array = parameter_array + [klass.quote_value(parent.active_record.base_class.name)]
1677
+ else
1678
+ jt_foreign_key = through_reflection.primary_key_name
1679
+ end
1680
+
1681
+ case source_reflection.macro
1682
+ when :has_many
1683
+ if source_reflection.options[:as]
1684
+ first_key = "#{source_reflection.options[:as]}_id"
1685
+ second_key = options[:foreign_key] || primary_key
1686
+ parameter_array = parameter_array + [klass.quote_value(source_reflection.active_record.base_class.name)]
1687
+ as_extra = " AND %s.%s = ?" % [
1688
+ connection.quote_table_name(aliased_table_name),
1689
+ connection.quote_column_name("#{source_reflection.options[:as]}_type")
1690
+ ]
1691
+ else
1692
+ first_key = through_reflection.klass.base_class.to_s.foreign_key
1693
+ second_key = options[:foreign_key] || primary_key
1694
+ end
1695
+
1696
+ unless through_reflection.klass.descends_from_active_record?
1697
+ parameter_array = parameter_array + [through_reflection.klass.quote_value(through_reflection.klass.sti_name)]
1698
+ jt_sti_extra = " AND %s.%s = ?" % [
1699
+ connection.quote_table_name(aliased_join_table_name),
1700
+ connection.quote_column_name(through_reflection.active_record.inheritance_column)
1701
+ ]
1702
+ end
1703
+ when :belongs_to
1704
+ first_key = primary_key
1705
+ if reflection.options[:source_type]
1706
+ second_key = source_reflection.association_foreign_key
1707
+ parameter_array = parameter_array + [klass.quote_value(reflection.options[:source_type])]
1708
+ jt_source_extra = " AND %s.%s = ?" % [
1709
+ connection.quote_table_name(aliased_join_table_name),
1710
+ connection.quote_column_name(reflection.source_reflection.options[:foreign_type])
1711
+ ]
1712
+ else
1713
+ second_key = source_reflection.primary_key_name
1714
+ end
1715
+ end
1716
+
1717
+ " #{join_type} %s ON (%s.%s = %s.%s%s%s%s) " % [
1718
+ table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
1719
+ connection.quote_table_name(parent.aliased_table_name),
1720
+ connection.quote_column_name(parent.primary_key),
1721
+ connection.quote_table_name(aliased_join_table_name),
1722
+ connection.quote_column_name(jt_foreign_key),
1723
+ jt_as_extra, jt_source_extra, jt_sti_extra
1724
+ ] +
1725
+ " #{join_type} %s ON (%s.%s = %s.%s%s) " % [
1726
+ table_name_and_alias,
1727
+ connection.quote_table_name(aliased_table_name),
1728
+ connection.quote_column_name(first_key),
1729
+ connection.quote_table_name(aliased_join_table_name),
1730
+ connection.quote_column_name(second_key),
1731
+ as_extra
1732
+ ]
1733
+
1734
+ when reflection.options[:as] && [:has_many, :has_one].include?(reflection.macro)
1735
+ parameter_array = parameter_array + [klass.quote_value(parent.active_record.base_class.name)]
1736
+ " #{join_type} %s ON %s.%s = %s.%s AND %s.%s = ?" % [
1737
+ table_name_and_alias,
1738
+ connection.quote_table_name(aliased_table_name),
1739
+ "#{reflection.options[:as]}_id",
1740
+ connection.quote_table_name(parent.aliased_table_name),
1741
+ parent.primary_key,
1742
+ connection.quote_table_name(aliased_table_name),
1743
+ "#{reflection.options[:as]}_type"
1744
+ ]
1745
+ else
1746
+ foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
1747
+ " #{join_type} %s ON %s.%s = %s.%s " % [
1748
+ table_name_and_alias,
1749
+ aliased_table_name,
1750
+ foreign_key,
1751
+ parent.aliased_table_name,
1752
+ reflection.options[:primary_key] || parent.primary_key
1753
+ ]
1754
+ end
1755
+ when :belongs_to
1756
+ " #{join_type} %s ON %s.%s = %s.%s " % [
1757
+ table_name_and_alias,
1758
+ connection.quote_table_name(aliased_table_name),
1759
+ reflection.klass.primary_key,
1760
+ connection.quote_table_name(parent.aliased_table_name),
1761
+ options[:foreign_key] || reflection.primary_key_name
1762
+ ]
1763
+ else
1764
+ ""
1765
+ end || ''
1766
+
1767
+ unless klass.descends_from_active_record?
1768
+ sql_segment, *param_array = klass.send(:type_condition, aliased_table_name)
1769
+ join << %(AND %s) % [sql_segment]
1770
+ parameter_array = parameter_array + param_array unless param_array.nil?
1771
+ end
1772
+
1773
+ [through_reflection, reflection].each do |ref|
1774
+ if ref && ref.options[:conditions]
1775
+ sql_param_hash = sanitize_sql(ref.options[:conditions], aliased_table_name)
1776
+ parameter_array = parameter_array + sql_param_hash["paramArray"] unless sql_param_hash["paramArray"].nil?
1777
+ join << "AND #{interpolate_sql(sql_param_hash["sqlSegment"])} "
1778
+ end
1779
+ end
1780
+
1781
+ return_hash["sqlSegment"] = join
1782
+ return_hash["paramArray"] = parameter_array
1783
+ return_hash
1784
+ end
1785
+
1786
+ end # End of class JoinAssociation
1787
+ end # End of class JoinDependency
1788
+ end # End of module ClassMethods
1789
+
1790
+ class AssociationProxy
1791
+ def quoted_record_ids(records)
1792
+ records.map { |record| record.quoted_id }
1793
+ end
1794
+
1795
+ def conditions
1796
+ if @reflection.sanitized_conditions
1797
+ sql_param_hash = @reflection.sanitized_conditions
1798
+ temp_condition = [interpolate_sql(sql_param_hash["sqlSegment"])]
1799
+ temp_condition = temp_condition + sql_param_hash["paramArray"] unless sql_param_hash["paramArray"].nil?
1800
+ @conditions ||= temp_condition
1801
+ end
1802
+ end
1803
+ alias :sql_conditions :conditions
1804
+
1805
+ protected :quoted_record_ids
1806
+ end
1807
+
1808
+ class HasAndBelongsToManyAssociation < AssociationCollection
1809
+
1810
+ def insert_record(record, force = true, validate = true)
1811
+ if record.new_record?
1812
+ if force
1813
+ record.save!
1814
+ else
1815
+ return false unless record.save(validate)
1816
+ end
1817
+ end
1818
+
1819
+ if @reflection.options[:insert_sql]
1820
+ @owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
1821
+ else
1822
+ attributes = columns.inject({}) do |attrs, column|
1823
+ case column.name.to_s
1824
+ when @reflection.primary_key_name.to_s
1825
+ attrs[column.name] = owner_quoted_id
1826
+ when @reflection.association_foreign_key.to_s
1827
+ attrs[column.name] = record.quoted_id
1828
+ else
1829
+ if record.has_attribute?(column.name)
1830
+ value = @owner.send(:quote_value, record[column.name], column)
1831
+ attrs[column.name] = value unless value.nil?
1832
+ end
1833
+ end
1834
+ attrs
1835
+ end
1836
+
1837
+ param_marker_sql_segment = attributes.values.collect{|value| '?'}.join(', ')
1838
+ sql =
1839
+ "INSERT INTO #{@owner.connection.quote_table_name @reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
1840
+ "VALUES (#{param_marker_sql_segment})"
1841
+
1842
+ pstmt = @owner.connection.prepare(sql)
1843
+ @owner.connection.prepared_insert(pstmt, attributes.values )
1844
+ end
1845
+
1846
+ return true
1847
+ end
1848
+
1849
+ def delete_records(records)
1850
+ paramArray = []
1851
+ if sql = @reflection.options[:delete_sql]
1852
+ records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
1853
+ else
1854
+ ids = quoted_record_ids(records)
1855
+
1856
+ paramArray = [owner_quoted_id] + ids
1857
+ param_marker_sql_segment = ids.collect{|id| '?'}.join(', ')
1858
+
1859
+ sql = "DELETE FROM #{@owner.connection.quote_table_name @reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = ? AND #{@reflection.association_foreign_key} IN (#{param_marker_sql_segment})"
1860
+ pstmt = @owner.connection.prepare(sql)
1861
+ @owner.connection.prepared_delete(pstmt,paramArray)
1862
+ end
1863
+ end
1864
+
1865
+ def construct_sql
1866
+ @finder_sql_param_array = []
1867
+ if @reflection.options[:finder_sql]
1868
+ @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
1869
+ else
1870
+ @finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = ? "
1871
+ @finder_sql_param_array << owner_quoted_id
1872
+ if conditions
1873
+ condition, *parameters = conditions
1874
+ @finder_sql << " AND (#{condition})"
1875
+ @finder_sql_param_array = @finder_sql_param_array + parameters unless parameters.nil?
1876
+ end
1877
+ end
1878
+
1879
+ @join_sql = "INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON #{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
1880
+
1881
+ if @reflection.options[:counter_sql]
1882
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
1883
+ elsif @reflection.options[:finder_sql]
1884
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
1885
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
1886
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
1887
+ else
1888
+ @counter_sql = @finder_sql
1889
+ end
1890
+ end
1891
+
1892
+ def construct_scope
1893
+ { :find => { :conditions => [@finder_sql] + @finder_sql_param_array,
1894
+ :joins => @join_sql,
1895
+ :readonly => false,
1896
+ :order => @reflection.options[:order],
1897
+ :include => @reflection.options[:include],
1898
+ :limit => @reflection.options[:limit] } }
1899
+ end
1900
+
1901
+ protected :insert_record, :delete_records, :construct_sql, :construct_scope
1902
+ end #end of class HasAndBelongsToManyAssociation
1903
+
1904
+ end #end of module Associations
1905
+
1906
+ module ConnectionAdapters
1907
+
1908
+ module QueryCache
1909
+ class << self
1910
+ def included(base)
1911
+ base.class_eval do
1912
+ alias_method_chain :prepared_select, :query_cache
1913
+ end
1914
+ end
1915
+ end
1916
+
1917
+ def prepared_select_with_query_cache(sql_param_hash, name = nil)
1918
+ if @query_cache_enabled
1919
+ cache_sql("#{sql_param_hash["sqlSegment"]} #{sql_param_hash["paramArray"]}") { prepared_select_without_query_cache(sql_param_hash, name) }
1920
+ else
1921
+ prepared_select_without_query_cache(sql_param_hash, name)
1922
+ end
1923
+ end
1924
+ end
1925
+
1926
+ class IBM_DBAdapter < AbstractAdapter
1927
+ include QueryCache
1928
+ end
1929
+
1930
+ module SchemaStatements
1931
+ def assume_migrated_upto_version(version)
1932
+ version = version.to_i
1933
+ sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
1934
+
1935
+ migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i)
1936
+ versions = Dir['db/migrate/[0-9]*_*.rb'].map do |filename|
1937
+ filename.split('/').last.split('_').first.to_i
1938
+ end
1939
+
1940
+ unless migrated.include?(version)
1941
+ pstmt = prepare("INSERT INTO #{sm_table} (version) VALUES (?)")
1942
+ execute_prepared_stmt(pstmt, [version])
1943
+ end
1944
+
1945
+ inserted = Set.new
1946
+ (versions - migrated).each do |v|
1947
+ if inserted.include?(v)
1948
+ raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict."
1949
+ elsif v < version
1950
+ pstmt = prepare("INSERT INTO #{sm_table} (version) VALUES (?)")
1951
+ execute_prepared_stmt(pstmt, [v])
1952
+ inserted << v
1953
+ end
1954
+ end
1955
+ end
1956
+ end # End of module SchemaStatements
1957
+ end # End of ConnectionAdapters
1958
+ end #End of module Activerecord
1959
+
1960
+ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
1961
+ def delete_existing_fixtures
1962
+ pstmt = @connection.prepare "DELETE FROM #{@connection.quote_table_name(table_name)}", 'Fixture Delete'
1963
+ @connection.prepared_delete(pstmt, nil)
1964
+ end
1965
+ end