Skip to content

Commit a738295

Browse files
committed
Arel: Add support for FILTER clause (SQL:2003)
Currently supported by PostgreSQL 9.4+ and SQLite 3.30+ See: - http://modern-sql.com/feature/filter - https://www.postgresql.org/docs/9.4/static/sql-expressions.html#SYNTAX-AGGREGATES - https://sqlite.org/lang_aggfunc.html#aggfilter Example: Model.all.pluck( Arel.star.count.as('records_total').to_sql, Arel.star.count.filter(Model.arel_table[:some_column].not_eq(nil)).as('records_filtered').to_sql, )
1 parent c0911e9 commit a738295

File tree

8 files changed

+86
-0
lines changed

8 files changed

+86
-0
lines changed

activerecord/CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
* Arel: Add support for FILTER clause (SQL:2003)
2+
3+
Currently supported by PostgreSQL 9.4+ and SQLite 3.30+
4+
5+
Example usage:
6+
7+
Model.all.pluck(
8+
Arel.star.count.as('records_total').to_sql,
9+
Arel.star.count.filter(Model.arel_table[:some_column].not_eq(nil)).as('records_filtered').to_sql,
10+
)
11+
12+
Example result:
13+
14+
SELECT
15+
COUNT(*) AS records_total,
16+
COUNT(*) FILTER (WHERE "some_column" IS NOT NULL) AS records_filtered
17+
FROM models
18+
19+
*Andrey Novikov*
20+
121
* Two change tracking methods are added for `belongs_to` associations.
222

323
The `association_changed?` method (assuming an association named `:association`) returns true

activerecord/lib/arel.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
require "arel/expressions"
99
require "arel/predications"
10+
require "arel/filter_predications"
1011
require "arel/window_predications"
1112
require "arel/math"
1213
require "arel/alias_predication"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# frozen_string_literal: true
2+
3+
module Arel
4+
module FilterPredications
5+
def filter(expr)
6+
Nodes::Filter.new(self, expr)
7+
end
8+
end
9+
end

activerecord/lib/arel/nodes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
# binary
2929
require "arel/nodes/binary"
3030
require "arel/nodes/equality"
31+
require "arel/nodes/filter"
3132
require "arel/nodes/in"
3233
require "arel/nodes/join_source"
3334
require "arel/nodes/delete_statement"

activerecord/lib/arel/nodes/filter.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# frozen_string_literal: true
2+
3+
module Arel
4+
module Nodes
5+
class Filter < Binary
6+
include Arel::WindowPredications
7+
include Arel::AliasPredication
8+
end
9+
end
10+
end

activerecord/lib/arel/nodes/function.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module Arel # :nodoc: all
44
module Nodes
55
class Function < Arel::Nodes::NodeExpression
66
include Arel::WindowPredications
7+
include Arel::FilterPredications
78
attr_accessor :expressions, :alias, :distinct
89

910
def initialize(expr, aliaz = nil)

activerecord/lib/arel/visitors/to_sql.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,13 @@ def visit_Arel_Nodes_Window(o, collector)
245245
collector << ")"
246246
end
247247

248+
def visit_Arel_Nodes_Filter(o, collector)
249+
visit o.left, collector
250+
collector << " FILTER (WHERE "
251+
visit o.right, collector
252+
collector << ")"
253+
end
254+
248255
def visit_Arel_Nodes_Rows(o, collector)
249256
if o.expr
250257
collector << "ROWS "
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "../helper"
4+
5+
module Arel
6+
module Nodes
7+
class ::FilterTest < Arel::Spec
8+
describe "Filter" do
9+
it "should add filter to expression" do
10+
table = Arel::Table.new :users
11+
_(table[:id].count.filter(table[:income].gteq(40_000)).to_sql).must_be_like %{
12+
COUNT("users"."id") FILTER (WHERE "users"."income" >= 40000)
13+
}
14+
end
15+
16+
describe "as" do
17+
it "should alias the expression" do
18+
table = Arel::Table.new :users
19+
_(table[:id].count.filter(table[:income].gteq(40_000)).as("rich_users_count").to_sql).must_be_like %{
20+
COUNT("users"."id") FILTER (WHERE "users"."income" >= 40000) AS rich_users_count
21+
}
22+
end
23+
end
24+
25+
describe "over" do
26+
it "should reference the window definition by name" do
27+
table = Arel::Table.new :users
28+
window = Arel::Nodes::Window.new.partition(table[:year])
29+
_(table[:id].count.filter(table[:income].gteq(40_000)).over(window).to_sql).must_be_like %{
30+
COUNT("users"."id") FILTER (WHERE "users"."income" >= 40000) OVER (PARTITION BY "users"."year")
31+
}
32+
end
33+
end
34+
end
35+
end
36+
end
37+
end

0 commit comments

Comments
 (0)