Skip to content

Commit d31ef5c

Browse files
skipkayhilbyroot
authored andcommitted
Ensure function aliases allow retrying queries
Previously, function aliases (ex. combining a `#group` and `#count`) would make queries non-retryable because `Function#as` would always wrap a given alias in a non-retryable `SqlLiteral`. This could be addressed by making `Function#as` behave similarly to `AliasPredication` (retry everything). However, `Function#as` can also just be removed so that `Function` uses `AliasPredication#as` instead. This behaves slightly differently because `AliasPredication#as` will now return the `Function` wrapped in an `Alias` (meaning the return value must be used). Only a single place in Active Record needed to be changed and all others already did the correct thing. This also resolves a long standing FIXME suggesting removing the custom `Function#as` in favor of using `Alias` nodes. The other half of the comment suggesting `Function` could inherit from `Unary` does not make sense because SQL functions could potentially take multiple parameters, so the existing superclass should not be changed.
1 parent e2f36ff commit d31ef5c

File tree

11 files changed

+22
-66
lines changed

11 files changed

+22
-66
lines changed

activerecord/lib/active_record/relation/calculations.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
545545
column = relation.aggregate_column(column_name)
546546
column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
547547
select_value = operation_over_aggregate_column(column, operation, distinct)
548-
select_value.as(model.adapter_class.quote_column_name(column_alias))
548+
select_value = select_value.as(model.adapter_class.quote_column_name(column_alias))
549549

550550
select_values = [select_value]
551551
select_values += self.select_values unless having_clause.empty?

activerecord/lib/arel/nodes.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@
4545
require "arel/nodes/nary"
4646

4747
# function
48-
# FIXME: Function + Alias can be rewritten as a Function and Alias node.
49-
# We should make Function a Unary node and deprecate the use of "aliaz"
5048
require "arel/nodes/function"
5149
require "arel/nodes/count"
5250
require "arel/nodes/extract"

activerecord/lib/arel/nodes/count.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
module Arel # :nodoc: all
44
module Nodes
55
class Count < Arel::Nodes::Function
6-
def initialize(expr, distinct = false, aliaz = nil)
7-
super(expr, aliaz)
6+
def initialize(expr, distinct = false)
7+
super(expr)
88
@distinct = distinct
99
end
1010
end

activerecord/lib/arel/nodes/function.rb

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,22 @@ module Nodes
55
class Function < Arel::Nodes::NodeExpression
66
include Arel::WindowPredications
77
include Arel::FilterPredications
8-
attr_accessor :expressions, :alias, :distinct
98

10-
def initialize(expr, aliaz = nil)
9+
attr_accessor :expressions, :distinct
10+
11+
def initialize(expr)
1112
super()
1213
@expressions = expr
13-
@alias = aliaz && SqlLiteral.new(aliaz)
1414
@distinct = false
1515
end
1616

17-
def as(aliaz)
18-
self.alias = SqlLiteral.new(aliaz)
19-
self
20-
end
21-
2217
def hash
23-
[@expressions, @alias, @distinct].hash
18+
[@expressions, @distinct].hash
2419
end
2520

2621
def eql?(other)
2722
self.class == other.class &&
2823
self.expressions == other.expressions &&
29-
self.alias == other.alias &&
3024
self.distinct == other.distinct
3125
end
3226
alias :== :eql?

activerecord/lib/arel/nodes/named_function.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ module Nodes
55
class NamedFunction < Arel::Nodes::Function
66
attr_accessor :name
77

8-
def initialize(name, expr, aliaz = nil)
9-
super(expr, aliaz)
8+
def initialize(name, expr)
9+
super(expr)
1010
@name = name
1111
end
1212

activerecord/lib/arel/visitors/dot.rb

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ def accept(object, collector)
3434
def visit_Arel_Nodes_Function(o)
3535
visit_edge o, "expressions"
3636
visit_edge o, "distinct"
37-
visit_edge o, "alias"
3837
end
3938

4039
def visit_Arel_Nodes_Unary(o)
@@ -108,14 +107,12 @@ def visit__no_edges(o)
108107

109108
def visit_Arel_Nodes_Extract(o)
110109
visit_edge o, "expressions"
111-
visit_edge o, "alias"
112110
end
113111

114112
def visit_Arel_Nodes_NamedFunction(o)
115113
visit_edge o, "name"
116114
visit_edge o, "expressions"
117115
visit_edge o, "distinct"
118-
visit_edge o, "alias"
119116
end
120117

121118
def visit_Arel_Nodes_InsertStatement(o)

activerecord/lib/arel/visitors/to_sql.rb

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,7 @@ def visit_Arel_Nodes_InsertStatement(o, collector)
7575

7676
def visit_Arel_Nodes_Exists(o, collector)
7777
collector << "EXISTS ("
78-
collector = visit(o.expressions, collector) << ")"
79-
if o.alias
80-
collector << " AS "
81-
visit o.alias, collector
82-
else
83-
collector
84-
end
78+
visit(o.expressions, collector) << ")"
8579
end
8680

8781
def visit_Arel_Nodes_Casted(o, collector)
@@ -388,13 +382,7 @@ def visit_Arel_Nodes_NamedFunction(o, collector)
388382
collector << o.name
389383
collector << "("
390384
collector << "DISTINCT " if o.distinct
391-
collector = inject_join(o.expressions, collector, ", ") << ")"
392-
if o.alias
393-
collector << " AS "
394-
visit o.alias, collector
395-
else
396-
collector
397-
end
385+
inject_join(o.expressions, collector, ", ") << ")"
398386
end
399387

400388
def visit_Arel_Nodes_Extract(o, collector)
@@ -998,13 +986,7 @@ def aggregate(name, o, collector)
998986
if o.distinct
999987
collector << "DISTINCT "
1000988
end
1001-
collector = inject_join(o.expressions, collector, ", ") << ")"
1002-
if o.alias
1003-
collector << " AS "
1004-
visit o.alias, collector
1005-
else
1006-
collector
1007-
end
989+
inject_join(o.expressions, collector, ", ") << ")"
1008990
end
1009991

1010992
def is_distinct_from(o, collector)

activerecord/test/cases/adapter_test.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -709,9 +709,10 @@ def teardown
709709
assert_predicate Post, :exists?
710710
a.books.to_a
711711
Author.select(:status).joins(:books).group(:status).to_a
712+
Author.group(:name).count
712713
end.select { |n| n.payload[:name] != "SCHEMA" }
713714

714-
assert_equal 7, notifications.length
715+
assert_equal 8, notifications.length
715716

716717
notifications.each do |n|
717718
assert n.payload[:allow_retry], "#{n.payload[:sql]} was not retryable"
@@ -729,9 +730,10 @@ def teardown
729730
assert_predicate Post, :exists?
730731
a.books.to_a
731732
Author.select(:status).joins(:books).group(:status).to_a
733+
Author.group(:name).count
732734
end.select { |n| n.payload[:name] != "SCHEMA" }
733735

734-
assert_equal 7, notifications.length
736+
assert_equal 8, notifications.length
735737

736738
notifications.each do |n|
737739
assert n.payload[:allow_retry], "#{n.payload[:sql]} was not retryable"

activerecord/test/cases/arel/nodes/homogeneous_in_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class TypedNode < Arel::Nodes::NamedFunction
3636
attr_reader :type_caster
3737

3838
def initialize(name, expr, type)
39-
super(name, expr, nil)
39+
super(name, expr)
4040
@type_caster = type
4141
end
4242
end

activerecord/test/cases/arel/nodes/named_function_test.rb

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,35 +11,18 @@ def test_construct
1111
assert_equal "zomg", function.expressions
1212
end
1313

14-
def test_function_alias
15-
function = NamedFunction.new "omg", "zomg"
16-
function = function.as("wth")
17-
assert_equal "omg", function.name
18-
assert_equal "zomg", function.expressions
19-
assert_kind_of SqlLiteral, function.alias
20-
assert_equal "wth", function.alias
21-
end
22-
23-
def test_construct_with_alias
24-
function = NamedFunction.new "omg", "zomg", "wth"
25-
assert_equal "omg", function.name
26-
assert_equal "zomg", function.expressions
27-
assert_kind_of SqlLiteral, function.alias
28-
assert_equal "wth", function.alias
29-
end
30-
3114
def test_equality_with_same_ivars
3215
array = [
33-
NamedFunction.new("omg", "zomg", "wth"),
34-
NamedFunction.new("omg", "zomg", "wth")
16+
NamedFunction.new("omg", "zomg"),
17+
NamedFunction.new("omg", "zomg")
3518
]
3619
assert_equal 1, array.uniq.size
3720
end
3821

3922
def test_inequality_with_different_ivars
4023
array = [
41-
NamedFunction.new("omg", "zomg", "wth"),
42-
NamedFunction.new("zomg", "zomg", "wth")
24+
NamedFunction.new("omg", "zomg"),
25+
NamedFunction.new("zomg", "zomg")
4326
]
4427
assert_equal 2, array.uniq.size
4528
end

0 commit comments

Comments
 (0)