ibm_db 2.5.17-universal-darwin-13

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