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