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