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