ibm_db 1.5.0-mswin32 → 2.0.0-mswin32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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