ar-extensions 0.8.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +10 -0
- data/Rakefile +6 -1
- data/db/migrate/generic_schema.rb +26 -1
- data/db/migrate/mysql_schema.rb +1 -1
- data/db/migrate/version.rb +1 -1
- data/init.rb +18 -0
- data/lib/ar-extensions/adapters/abstract_adapter.rb +25 -2
- data/lib/ar-extensions/adapters/mysql.rb +2 -0
- data/lib/ar-extensions/create_and_update.rb +509 -0
- data/lib/ar-extensions/create_and_update/mysql.rb +7 -0
- data/lib/ar-extensions/csv.rb +32 -32
- data/lib/ar-extensions/delete.rb +143 -0
- data/lib/ar-extensions/delete/mysql.rb +3 -0
- data/lib/ar-extensions/extensions.rb +6 -1
- data/lib/ar-extensions/finder_options.rb +275 -0
- data/lib/ar-extensions/finder_options/mysql.rb +6 -0
- data/lib/ar-extensions/finders.rb +7 -1
- data/lib/ar-extensions/import/mysql.rb +8 -1
- data/lib/ar-extensions/insert_select.rb +178 -0
- data/lib/ar-extensions/insert_select/mysql.rb +7 -0
- data/lib/ar-extensions/union.rb +204 -0
- data/lib/ar-extensions/union/mysql.rb +6 -0
- data/lib/ar-extensions/util/sql_generation.rb +27 -0
- data/lib/ar-extensions/util/support_methods.rb +32 -0
- data/lib/ar-extensions/version.rb +1 -1
- metadata +15 -2
@@ -0,0 +1,6 @@
|
|
1
|
+
# Although the finder options actually override ActiveRecord::Base functionality instead of
|
2
|
+
# connector functionality, the methods are included here to keep the syntax of 0.8.0 intact
|
3
|
+
#
|
4
|
+
# To include finder options, use:
|
5
|
+
# require 'ar-extensions/finder_options/mysql.rb'
|
6
|
+
ActiveRecord::Base.send :include, ActiveRecord::Extensions::FinderOptions
|
@@ -1,3 +1,8 @@
|
|
1
|
+
require 'active_record/version'
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
|
1
6
|
module ActiveRecord::ConnectionAdapters::Quoting
|
2
7
|
|
3
8
|
alias :quote_before_arext :quote
|
@@ -10,6 +15,7 @@ module ActiveRecord::ConnectionAdapters::Quoting
|
|
10
15
|
end
|
11
16
|
end
|
12
17
|
|
18
|
+
unless ActiveRecord::VERSION::STRING < '2.0.2'
|
13
19
|
class ActiveRecord::Base
|
14
20
|
|
15
21
|
class << self
|
@@ -71,7 +77,6 @@ class ActiveRecord::Base
|
|
71
77
|
else
|
72
78
|
table_name = quoted_table_name
|
73
79
|
end
|
74
|
-
|
75
80
|
# ActiveRecord in 2.3.1 changed the method signature for
|
76
81
|
# the method attribute_condition
|
77
82
|
if ActiveRecord::VERSION::STRING < '2.3.1'
|
@@ -92,3 +97,4 @@ class ActiveRecord::Base
|
|
92
97
|
end
|
93
98
|
|
94
99
|
end
|
100
|
+
end
|
@@ -12,6 +12,8 @@ module ActiveRecord::Extensions::ConnectionAdapters::MysqlAdapter # :nodoc:
|
|
12
12
|
sql << sql_for_on_duplicate_key_update_as_array( table_name, arg )
|
13
13
|
elsif arg.is_a?( Hash )
|
14
14
|
sql << sql_for_on_duplicate_key_update_as_hash( table_name, arg )
|
15
|
+
elsif arg.is_a?( String )
|
16
|
+
sql << arg
|
15
17
|
else
|
16
18
|
raise ArgumentError.new( "Expected Array or Hash" )
|
17
19
|
end
|
@@ -34,7 +36,12 @@ module ActiveRecord::Extensions::ConnectionAdapters::MysqlAdapter # :nodoc:
|
|
34
36
|
"#{table_name}.#{qc1}=VALUES( #{qc2} )"
|
35
37
|
end
|
36
38
|
results.join( ',')
|
37
|
-
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#return true if the statement is a duplicate key record error
|
42
|
+
def duplicate_key_update_error?(exception)# :nodoc:
|
43
|
+
exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('Duplicate entry')
|
44
|
+
end
|
38
45
|
|
39
46
|
end
|
40
47
|
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# Insert records in bulk with a select statement
|
2
|
+
#
|
3
|
+
# == Parameters
|
4
|
+
# * +options+ - the options used for the finder sql (select)
|
5
|
+
#
|
6
|
+
# === Options
|
7
|
+
# Any valid finder options (options for <tt>ActiveRecord::Base.find(:all)</tt> )such as <tt>:joins</tt>, <tt>:conditions</tt>, <tt>:include</tt>, etc including:
|
8
|
+
# * <tt>:from</tt> - the symbol, class name or class used for the finder SQL (select)
|
9
|
+
# * <tt>:on_duplicate_key_update</tt> - an array of fields to update, or a custom string
|
10
|
+
# * <tt>:select</tt> - An array of fields to select or custom string. The SQL will be sanitized and ? replaced with values as with <tt>:conditions</tt>.
|
11
|
+
# * <tt>:ignore => true </tt> - will ignore any duplicates
|
12
|
+
# * <tt>:into</tt> - Specifies the columns for which data will be inserted. An array of fields to select or custom string.
|
13
|
+
#
|
14
|
+
# == Examples
|
15
|
+
# Create cart items for all books for shopping cart <tt>@cart+
|
16
|
+
# setting the +copies+ field to 1, the +updated_at+ field to Time.now and the +created_at+ field to the database function now()
|
17
|
+
# CartItem.insert_select(:from => :book,
|
18
|
+
# :select => ['books.id, ?, ?, ?, now()', @cart.to_param, 1, Time.now],
|
19
|
+
# :into => [:book_id, :shopping_cart_id, :copies, :updated_at, :created_at]})
|
20
|
+
#
|
21
|
+
# GENERATED SQL example (MySQL):
|
22
|
+
# INSERT INTO `cart_items` ( `book_id`, `shopping_cart_id`, `copies`, `updated_at`, `created_at` )
|
23
|
+
# SELECT books.id, '134', 1, '2009-03-02 18:28:25', now() FROM `books`
|
24
|
+
#
|
25
|
+
# A similar example that
|
26
|
+
# * uses the class +Book+ instead of symbol <tt>:book</tt>
|
27
|
+
# * a custom string (instead of an Array) for the <tt>:select</tt> of the +insert_options+
|
28
|
+
# * Updates the +updated_at+ field of all existing cart item. This assumes there is a unique composite index on the +book_id+ and +shopping_cart_id+ fields
|
29
|
+
#
|
30
|
+
# CartItem.insert_select(:from => Book,
|
31
|
+
# :select => ['books.id, ?, ?, ?, now()', @cart.to_param, 1, Time.now],
|
32
|
+
# :into => 'cart_items.book_id, shopping_cart_id, copies, updated_at, created_at',
|
33
|
+
# :on_duplicate_key_update => [:updated_at])
|
34
|
+
# GENERATED SQL example (MySQL):
|
35
|
+
# INSERT INTO `cart_items` ( cart_items.book_id, shopping_cart_id, copies, updated_at, created_at )
|
36
|
+
# SELECT books.id, '138', 1, '2009-03-02 18:32:34', now() FROM `books`
|
37
|
+
# ON DUPLICATE KEY UPDATE `cart_items`.`updated_at`=VALUES(`updated_at`)
|
38
|
+
#
|
39
|
+
#
|
40
|
+
# Similar example ignoring duplicates
|
41
|
+
# CartItem.insert_select(:from => :book,
|
42
|
+
# :select => ['books.id, ?, ?, ?, now()', @cart.to_param, 1, Time.now],
|
43
|
+
# :into => [:book_id, :shopping_cart_id, :copies, :updated_at, :created_at],
|
44
|
+
# :ignore => true)
|
45
|
+
#
|
46
|
+
# == Developers
|
47
|
+
# * Blythe Dunham http://blythedunham.com
|
48
|
+
#
|
49
|
+
# == Homepage
|
50
|
+
# * Project Site: http://www.continuousthinking.com/tags/arext
|
51
|
+
# * Rubyforge Project: http://rubyforge.org/projects/arext
|
52
|
+
# * Anonymous SVN: svn checkout svn://rubyforge.org/var/svn/arext
|
53
|
+
#
|
54
|
+
|
55
|
+
module ActiveRecord::Extensions::ConnectionAdapters; end
|
56
|
+
|
57
|
+
module ActiveRecord::Extensions::InsertSelectSupport #:nodoc:
|
58
|
+
def supports_insert_select? #:nodoc:
|
59
|
+
true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class ActiveRecord::Base
|
64
|
+
|
65
|
+
include ActiveRecord::Extensions::SqlGeneration
|
66
|
+
|
67
|
+
class << self
|
68
|
+
# Insert records in bulk with a select statement
|
69
|
+
#
|
70
|
+
# == Parameters
|
71
|
+
# * +options+ - the options used for the finder sql (select)
|
72
|
+
#
|
73
|
+
# === Options
|
74
|
+
# Any valid finder options (options for <tt>ActiveRecord::Base.find(:all)</tt> )such as <tt>:joins</tt>, <tt>:conditions</tt>, <tt>:include</tt>, etc including:
|
75
|
+
# * <tt>:from</tt> - the symbol, class name or class used for the finder SQL (select)
|
76
|
+
# * <tt>:on_duplicate_key_update</tt> - an array of fields to update, or a custom string
|
77
|
+
# * <tt>:select</tt> - An array of fields to select or custom string. The SQL will be sanitized and ? replaced with values as with <tt>:conditions</tt>.
|
78
|
+
# * <tt>:ignore => true </tt> - will ignore any duplicates
|
79
|
+
# * <tt>:into</tt> - Specifies the columns for which data will be inserted. An array of fields to select or custom string.
|
80
|
+
#
|
81
|
+
# == Examples
|
82
|
+
# Create cart items for all books for shopping cart <tt>@cart+
|
83
|
+
# setting the +copies+ field to 1, the +updated_at+ field to Time.now and the +created_at+ field to the database function now()
|
84
|
+
# CartItem.insert_select(:from => :book,
|
85
|
+
# :select => ['books.id, ?, ?, ?, now()', @cart.to_param, 1, Time.now],
|
86
|
+
# :into => [:book_id, :shopping_cart_id, :copies, :updated_at, :created_at]})
|
87
|
+
#
|
88
|
+
# GENERATED SQL example (MySQL):
|
89
|
+
# INSERT INTO `cart_items` ( `book_id`, `shopping_cart_id`, `copies`, `updated_at`, `created_at` )
|
90
|
+
# SELECT books.id, '134', 1, '2009-03-02 18:28:25', now() FROM `books`
|
91
|
+
#
|
92
|
+
# A similar example that
|
93
|
+
# * uses the class +Book+ instead of symbol <tt>:book</tt>
|
94
|
+
# * a custom string (instead of an Array) for the <tt>:select</tt> of the +insert_options+
|
95
|
+
# * Updates the +updated_at+ field of all existing cart item. This assumes there is a unique composite index on the +book_id+ and +shopping_cart_id+ fields
|
96
|
+
#
|
97
|
+
# CartItem.insert_select(:from => Book,
|
98
|
+
# :select => ['books.id, ?, ?, ?, now()', @cart.to_param, 1, Time.now],
|
99
|
+
# :into => 'cart_items.book_id, shopping_cart_id, copies, updated_at, created_at',
|
100
|
+
# :on_duplicate_key_update => [:updated_at])
|
101
|
+
# GENERATED SQL example (MySQL):
|
102
|
+
# INSERT INTO `cart_items` ( cart_items.book_id, shopping_cart_id, copies, updated_at, created_at )
|
103
|
+
# SELECT books.id, '138', 1, '2009-03-02 18:32:34', now() FROM `books`
|
104
|
+
# ON DUPLICATE KEY UPDATE `cart_items`.`updated_at`=VALUES(`updated_at`)
|
105
|
+
#
|
106
|
+
#
|
107
|
+
# Similar example ignoring duplicates
|
108
|
+
# CartItem.insert_select(:from => :book,
|
109
|
+
# :select => ['books.id, ?, ?, ?, now()', @cart.to_param, 1, Time.now],
|
110
|
+
# :into => [:book_id, :shopping_cart_id, :copies, :updated_at, :created_at],
|
111
|
+
# :ignore => true)
|
112
|
+
def insert_select(options={})
|
113
|
+
select_obj = options.delete(:from).to_s.classify.constantize
|
114
|
+
#TODO: add batch support for high volume inserts
|
115
|
+
#return insert_select_batch(select_obj, select_options, insert_options) if insert_options[:batch]
|
116
|
+
sql = construct_insert_select_sql(select_obj, options)
|
117
|
+
connection.insert(sql, "#{name} Insert Select #{select_obj}")
|
118
|
+
end
|
119
|
+
|
120
|
+
protected
|
121
|
+
|
122
|
+
def construct_insert_select_sql(select_obj, options)#:nodoc:
|
123
|
+
construct_ar_extension_sql(gather_insert_options(options), valid_insert_select_options) do |sql, into_op|
|
124
|
+
sql << " INTO #{quoted_table_name} "
|
125
|
+
sql << "( #{into_column_sql(options.delete(:into))} ) "
|
126
|
+
|
127
|
+
#sanitize the select sql based on the select object
|
128
|
+
sql << select_obj.send(:finder_sql_to_string, sanitize_select_options(options))
|
129
|
+
sql
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# return a list of the column names quoted accordingly
|
134
|
+
# nil => All columns except primary key (auto update)
|
135
|
+
# String => Exact String
|
136
|
+
# Array
|
137
|
+
# needs sanitation ["?, ?", 5, 'test'] => "5, 'test'" or [":date", {:date => Date.today}] => "12-30-2006"]
|
138
|
+
# list of strings or symbols returns quoted values [:start, :name] => `start`, `name` or ['abc'] => `start`
|
139
|
+
def select_column_sql(field_list=nil)#:nodoc:
|
140
|
+
if field_list.kind_of?(String)
|
141
|
+
field_list.dup
|
142
|
+
elsif ((field_list.kind_of?(Array) && field_list.first.is_a?(String)) &&
|
143
|
+
(field_list.last.is_a?(Hash) || field_list.first.include?('?')))
|
144
|
+
sanitize_sql(field_list)
|
145
|
+
else
|
146
|
+
field_list = field_list.blank? ? self.column_names - [self.primary_key] : [field_list].flatten
|
147
|
+
field_list.collect{|field| self.connection.quote_column_name(field.to_s) }.join(", ")
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
alias_method :into_column_sql, :select_column_sql
|
152
|
+
|
153
|
+
#sanitize the select options for insert select
|
154
|
+
def sanitize_select_options(options)#:nodoc:
|
155
|
+
o = options.dup
|
156
|
+
select = o.delete :select
|
157
|
+
o[:override_select] = select ? select_column_sql(select) : ' * '
|
158
|
+
o
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
def valid_insert_select_options#:nodoc:
|
163
|
+
@@valid_insert_select_options ||= [:command, :into_pre, :into_post,
|
164
|
+
:into_keywords, :ignore,
|
165
|
+
:on_duplicate_key_update]
|
166
|
+
end
|
167
|
+
|
168
|
+
#move all the insert options to a seperate map
|
169
|
+
def gather_insert_options(options)#:nodoc:
|
170
|
+
into_options = valid_insert_select_options.inject(:command => 'INSERT') do |map, o|
|
171
|
+
v = options.delete(o)
|
172
|
+
map[o] = v if v
|
173
|
+
map
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
#insert select functionality is dependent on finder options and import
|
2
|
+
require 'ar-extensions/finder_options/mysql'
|
3
|
+
require 'ar-extensions/import/mysql'
|
4
|
+
|
5
|
+
ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
|
6
|
+
include ActiveRecord::Extensions::InsertSelectSupport
|
7
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
module ActiveRecord::Extensions::Union#:nodoc:
|
2
|
+
module UnionSupport #:nodoc:
|
3
|
+
def supports_union? #:nodoc:
|
4
|
+
true
|
5
|
+
end
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class ActiveRecord::Base
|
10
|
+
supports_extension :union
|
11
|
+
|
12
|
+
extend ActiveRecord::Extensions::SqlGeneration
|
13
|
+
class << self
|
14
|
+
# Find a union of two or more queries
|
15
|
+
# === Args
|
16
|
+
# Each argument is a hash map of options sent to <tt>:find :all</tt>
|
17
|
+
# including <tt>:conditions</tt>, <tt>:join</tt>, <tt>:group</tt>,
|
18
|
+
# <tt>:having</tt>, and <tt>:limit</tt>
|
19
|
+
#
|
20
|
+
# In addition the following options are accepted
|
21
|
+
# * <tt>:pre_sql</tt> inserts SQL before the SELECT statement of this protion of the +union+
|
22
|
+
# * <tt>:post_sql</tt> appends additional SQL to the end of the statement
|
23
|
+
# * <tt>:override_select</tt> is used to override the <tt>SELECT</tt> clause of eager loaded associations
|
24
|
+
#
|
25
|
+
# == Examples
|
26
|
+
# Find the union of a San Fran zipcode with a Seattle zipcode
|
27
|
+
# union_args1 = {:conditions => ['zip_id = ?', 94010], :select => :phone_number_id}
|
28
|
+
# union_args2 = {:conditions => ['zip_id = ?', 98102], :select => :phone_number_id}
|
29
|
+
# Contact.find_union(union_args1, union_args2, ...)
|
30
|
+
#
|
31
|
+
# SQL> (SELECT phone_number_id FROM contacts WHERE zip_id = 94010) UNION
|
32
|
+
# (SELECT phone_number_id FROM contacts WHERE zip_id = 98102) UNION ...
|
33
|
+
#
|
34
|
+
# == Global Options
|
35
|
+
# To specify global options that apply to the entire union, specify a hash as the
|
36
|
+
# first parameter with a key <tt>:union_options</tt>. Valid options include
|
37
|
+
# <tt>:group</tt>, <tt>:having</tt>, <tt>:order</tt>, and <tt>:limit</tt>
|
38
|
+
#
|
39
|
+
#
|
40
|
+
# Example:
|
41
|
+
# Contact.find_union(:union_options => {:limit => 10, :order => 'created_on'},
|
42
|
+
# union_args1, union_args2, ...)
|
43
|
+
#
|
44
|
+
# SQL> ((select phone_number_id from contacts ...) UNION (select phone_number_id from contacts ...)) order by created_on limit 10
|
45
|
+
#
|
46
|
+
def find_union(*args)
|
47
|
+
supports_union!
|
48
|
+
find_by_sql(find_union_sql(*args))
|
49
|
+
end
|
50
|
+
|
51
|
+
# Count across a union of two or more queries
|
52
|
+
# === Args
|
53
|
+
# * +column_name+ - The column to count. Defaults to all ('*')
|
54
|
+
# * <tt>*args</tt> - Each additional argument is a hash map of options used by <tt>:find :all</tt>
|
55
|
+
# including <tt>:conditions</tt>, <tt>:join</tt>, <tt>:group</tt>,
|
56
|
+
# <tt>:having</tt>, and <tt>:limit</tt>
|
57
|
+
#
|
58
|
+
# In addition the following options are accepted
|
59
|
+
# * <tt>:pre_sql</tt> inserts SQL before the SELECT statement of this protion of the +union+
|
60
|
+
# * <tt>:post_sql</tt> appends additional SQL to the end of the statement
|
61
|
+
# * <tt>:override_select</tt> is used to override the <tt>SELECT</tt> clause of eager loaded associations
|
62
|
+
#
|
63
|
+
# Note that distinct is implied so a record that matches more than one
|
64
|
+
# portion of the union is counted only once.
|
65
|
+
#
|
66
|
+
# == Global Options
|
67
|
+
# To specify global options that apply to the entire union, specify a hash as the
|
68
|
+
# first parameter with a key <tt>:union_options</tt>. Valid options include
|
69
|
+
# <tt>:group</tt>, <tt>:having</tt>, <tt>:order</tt>, and <tt>:limit</tt>
|
70
|
+
#
|
71
|
+
# == Examples
|
72
|
+
# Count the number of people who live in Seattle and San Francisco
|
73
|
+
# Contact.count_union(:phone_number_id,
|
74
|
+
# {:conditions => ['zip_id = ?, 94010]'},
|
75
|
+
# {:conditions => ['zip_id = ?', 98102]})
|
76
|
+
# SQL> select count(*) from ((select phone_number_id from contacts ...) UNION (select phone_number_id from contacts ...)) as counter_tbl;
|
77
|
+
def count_union(column_name, *args)
|
78
|
+
supports_union!
|
79
|
+
count_val = calculate_union(:count, column_name, *args)
|
80
|
+
(args.length == 1 && args.first[:limit] && args.first[:limit].to_i < count_val) ? args.first[:limit].to_i : count_val
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
#do a union of specified calculation. Only for simple calculations
|
86
|
+
def calculate_union(operation, column_name, *args)#:nodoc:
|
87
|
+
union_options = remove_union_options(args)
|
88
|
+
|
89
|
+
|
90
|
+
if args.length == 1
|
91
|
+
column_name = '*' if column_name == :all
|
92
|
+
calculate(operation, column_name, args.first.update(union_options))
|
93
|
+
|
94
|
+
# For more than one map of options, count off the subquery of all the column_name fields unioned together
|
95
|
+
# For example, if column_name is phone_number_id the generated query is
|
96
|
+
# Contact.calculate_union(:count, :phone_number_id, args)
|
97
|
+
# SQL> select count(*) from
|
98
|
+
# ((select phone_number_id from contacts ...)
|
99
|
+
# UNION
|
100
|
+
# (select phone_number_id from contacts ...)) as counter_tbl
|
101
|
+
else
|
102
|
+
column_name = primary_key if column_name == :all
|
103
|
+
column = column_for column_name
|
104
|
+
column_name = "#{table_name}.#{column_name}" unless column_name.to_s.include?('.')
|
105
|
+
|
106
|
+
group_by = union_options.delete(:group)
|
107
|
+
having = union_options.delete(:having)
|
108
|
+
query_alias = union_options.delete(:query_alias)||"#{operation}_giraffe"
|
109
|
+
|
110
|
+
|
111
|
+
#aggregate_alias should be table_name_id
|
112
|
+
aggregate_alias = column_alias_for('', column_name)
|
113
|
+
#main alias is operation_table_name_id
|
114
|
+
main_aggregate_alias = column_alias_for(operation, column_name)
|
115
|
+
|
116
|
+
sql = "SELECT "
|
117
|
+
sql << (group_by ? "#{group_by}, #{operation}(#{aggregate_alias})" : "#{operation}(*)")
|
118
|
+
sql << " AS #{main_aggregate_alias}"
|
119
|
+
sql << " FROM ("
|
120
|
+
|
121
|
+
#by nature of the union the results will always be distinct, so remove distinct column here
|
122
|
+
sql << args.inject([]){|l, a|
|
123
|
+
calc = "(#{construct_calculation_sql_with_extension('', column_name, a)})"
|
124
|
+
#for group by we need to select the group by column also
|
125
|
+
calc.gsub!(" AS #{aggregate_alias}", " AS #{aggregate_alias}, #{group_by} ") if group_by
|
126
|
+
l << calc
|
127
|
+
}.join(" UNION ")
|
128
|
+
|
129
|
+
add_union_options!(sql, union_options)
|
130
|
+
|
131
|
+
sql << ") as #{query_alias}"
|
132
|
+
|
133
|
+
if group_by
|
134
|
+
#add groupings
|
135
|
+
sql << " GROUP BY #{group_by}"
|
136
|
+
sql << " HAVING #{having}" if having
|
137
|
+
|
138
|
+
calculated_data = connection.select_all(sql)
|
139
|
+
|
140
|
+
calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row|
|
141
|
+
key = type_cast_calculated_value(row[group_by], column_for(group_by.to_s))
|
142
|
+
value = row[main_aggregate_alias]
|
143
|
+
all << [key, type_cast_calculated_value(value, column_for(column), operation)]
|
144
|
+
end
|
145
|
+
|
146
|
+
else
|
147
|
+
count_by_sql(sql)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
#Add Global Union options
|
154
|
+
def add_union_options!(sql, options)#:nodoc:
|
155
|
+
sql << " GROUP BY #{options[:group]} " if options[:group]
|
156
|
+
|
157
|
+
if options[:order] || options[:limit]
|
158
|
+
scope = scope(:find)
|
159
|
+
add_order!(sql, options[:order], scope)
|
160
|
+
add_limit!(sql, options, scope)
|
161
|
+
end
|
162
|
+
sql
|
163
|
+
end
|
164
|
+
|
165
|
+
#Remove the global union options
|
166
|
+
def remove_union_options(args)#:nodoc:
|
167
|
+
args.first.is_a?(Hash) && args.first.has_key?(:union_options) ? (args.shift)[:union_options] : {}
|
168
|
+
end
|
169
|
+
|
170
|
+
def construct_calculation_sql_with_extension(operation, column_name, options)
|
171
|
+
construct_ar_extension_sql(options.merge(:command => '', :keywords => nil, :distinct => nil)) {|sql, o|
|
172
|
+
calc_sql = construct_calculation_sql(operation, column_name, options)
|
173
|
+
|
174
|
+
#this is really gross but prevents us from rewriting construct_calculation_sql
|
175
|
+
calc_sql.gsub!(/^SELECT\s/, "SELECT #{options[:keywords]} ") if options[:keywords]
|
176
|
+
|
177
|
+
sql << calc_sql
|
178
|
+
}
|
179
|
+
end
|
180
|
+
|
181
|
+
# Return the sql for union of the query options specified on the command line
|
182
|
+
# If the first parameter is a map containing :union_options, use these
|
183
|
+
def find_union_sql(*args)#:nodoc:
|
184
|
+
options = remove_union_options(args)
|
185
|
+
|
186
|
+
if args.length == 1
|
187
|
+
return finder_sql_to_string(args.first.update(options))
|
188
|
+
end
|
189
|
+
|
190
|
+
sql = args.inject([]) do |sql_list, union_args|
|
191
|
+
part = union_args.merge(:force_eager_load => true,
|
192
|
+
:override_select => union_args[:select]||"#{quoted_table_name}.*",
|
193
|
+
:select => nil)
|
194
|
+
sql_list << "(#{finder_sql_to_string(part)})"
|
195
|
+
sql_list
|
196
|
+
end.join(" UNION ")
|
197
|
+
|
198
|
+
|
199
|
+
add_union_options!(sql, options)
|
200
|
+
sql
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|