ibm_db 1.5.0 → 2.0.0

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