active_record_extended 1.1.0 → 2.0.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.
- checksums.yaml +4 -4
- data/README.md +87 -15
- data/lib/active_record_extended.rb +2 -1
- data/lib/active_record_extended/active_record.rb +2 -9
- data/lib/active_record_extended/active_record/relation_patch.rb +21 -4
- data/lib/active_record_extended/arel.rb +2 -0
- data/lib/active_record_extended/arel/aggregate_function_name.rb +40 -0
- data/lib/active_record_extended/arel/nodes.rb +32 -41
- data/lib/active_record_extended/arel/predications.rb +4 -1
- data/lib/active_record_extended/arel/sql_literal.rb +16 -0
- data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +40 -1
- data/lib/active_record_extended/query_methods/any_of.rb +10 -8
- data/lib/active_record_extended/query_methods/either.rb +1 -1
- data/lib/active_record_extended/query_methods/inet.rb +7 -3
- data/lib/active_record_extended/query_methods/json.rb +156 -50
- data/lib/active_record_extended/query_methods/select.rb +118 -0
- data/lib/active_record_extended/query_methods/unionize.rb +14 -43
- data/lib/active_record_extended/query_methods/where_chain.rb +14 -6
- data/lib/active_record_extended/query_methods/window.rb +93 -0
- data/lib/active_record_extended/query_methods/with_cte.rb +102 -35
- data/lib/active_record_extended/utilities/order_by.rb +77 -0
- data/lib/active_record_extended/utilities/support.rb +178 -0
- data/lib/active_record_extended/version.rb +1 -1
- data/spec/query_methods/any_of_spec.rb +40 -40
- data/spec/query_methods/array_query_spec.rb +14 -14
- data/spec/query_methods/either_spec.rb +14 -14
- data/spec/query_methods/hash_query_spec.rb +11 -11
- data/spec/query_methods/inet_query_spec.rb +33 -31
- data/spec/query_methods/json_spec.rb +42 -27
- data/spec/query_methods/select_spec.rb +115 -0
- data/spec/query_methods/unionize_spec.rb +56 -56
- data/spec/query_methods/window_spec.rb +51 -0
- data/spec/query_methods/with_cte_spec.rb +22 -12
- data/spec/spec_helper.rb +1 -1
- data/spec/sql_inspections/any_of_sql_spec.rb +12 -12
- data/spec/sql_inspections/arel/aggregate_function_name_spec.rb +41 -0
- data/spec/sql_inspections/arel/array_spec.rb +7 -7
- data/spec/sql_inspections/arel/inet_spec.rb +7 -7
- data/spec/sql_inspections/contains_sql_queries_spec.rb +14 -14
- data/spec/sql_inspections/either_sql_spec.rb +11 -11
- data/spec/sql_inspections/json_sql_spec.rb +44 -8
- data/spec/sql_inspections/unionize_sql_spec.rb +27 -27
- data/spec/sql_inspections/window_sql_spec.rb +98 -0
- data/spec/sql_inspections/with_cte_sql_spec.rb +52 -23
- data/spec/support/models.rb +24 -4
- metadata +31 -20
- data/lib/active_record_extended/patch/5_0/predicate_builder_decorator.rb +0 -87
- data/lib/active_record_extended/utilities.rb +0 -141
@@ -3,7 +3,7 @@
|
|
3
3
|
require "spec_helper"
|
4
4
|
|
5
5
|
RSpec.describe "Inet Column Predicates" do
|
6
|
-
let(:arel_table) {
|
6
|
+
let(:arel_table) { User.arel_table }
|
7
7
|
|
8
8
|
describe "#inet_contained_within" do
|
9
9
|
it "converts Arel inet contained within statement" do
|
@@ -12,7 +12,7 @@ RSpec.describe "Inet Column Predicates" do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
it "works with count" do
|
15
|
-
expect(
|
15
|
+
expect(User.where(arel_table[:ip].inet_contained_within(IPAddr.new("127.0.0.1"))).count).to eq(0)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -23,7 +23,7 @@ RSpec.describe "Inet Column Predicates" do
|
|
23
23
|
end
|
24
24
|
|
25
25
|
it "works with count" do
|
26
|
-
expect(
|
26
|
+
expect(User.where(arel_table[:ip].inet_contained_within_or_equals(IPAddr.new("127.0.0.1"))).count).to eq(0)
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
@@ -34,7 +34,7 @@ RSpec.describe "Inet Column Predicates" do
|
|
34
34
|
end
|
35
35
|
|
36
36
|
it "works with count" do
|
37
|
-
expect(
|
37
|
+
expect(User.where(arel_table[:ip].inet_contains_or_equals(IPAddr.new("127.0.0.1"))).count).to eq(0)
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -45,7 +45,7 @@ RSpec.describe "Inet Column Predicates" do
|
|
45
45
|
end
|
46
46
|
|
47
47
|
it "works with count" do
|
48
|
-
expect(
|
48
|
+
expect(User.where(arel_table[:ip].inet_contains("127.0.0.1")).count).to eq(0)
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
@@ -59,8 +59,8 @@ RSpec.describe "Inet Column Predicates" do
|
|
59
59
|
end
|
60
60
|
|
61
61
|
it "works with count" do
|
62
|
-
expect(
|
63
|
-
expect(
|
62
|
+
expect(User.where(arel_table[:ip].inet_contains_or_is_contained_within("127.0.0.1")).count).to eq(0)
|
63
|
+
expect(User.where(arel_table[:ip].inet_contains_or_is_contained_within(IPAddr.new("127.0.0.1"))).count).to eq(0)
|
64
64
|
end
|
65
65
|
end
|
66
66
|
end
|
@@ -3,44 +3,44 @@
|
|
3
3
|
require "spec_helper"
|
4
4
|
|
5
5
|
RSpec.describe "Contains SQL Queries" do
|
6
|
-
let(:contains_array_regex) {
|
7
|
-
let(:contains_hstore_regex) {
|
8
|
-
let(:contains_jsonb_regex) {
|
9
|
-
let(:contained_in_array_regex) {
|
10
|
-
let(:contained_in_hstore_regex) {
|
11
|
-
let(:contained_in_jsonb_regex) {
|
12
|
-
let(:contains_equals_regex) {
|
13
|
-
let(:equality_regex) {
|
6
|
+
let(:contains_array_regex) { /"users"\."tag_ids" @> '\{1,2\}'/ }
|
7
|
+
let(:contains_hstore_regex) { /"users"\."data" @> '"nickname"=>"Dan"'/ }
|
8
|
+
let(:contains_jsonb_regex) { /"users"\."jsonb_data" @> '\{"nickname":"Dan"}'/ }
|
9
|
+
let(:contained_in_array_regex) { /"users"\."tag_ids" <@ '\{1,2\}'/ }
|
10
|
+
let(:contained_in_hstore_regex) { /"users"\."data" <@ '"nickname"=>"Dan"'/ }
|
11
|
+
let(:contained_in_jsonb_regex) { /"users"\."jsonb_data" <@ '\{"nickname":"Dan"}'/ }
|
12
|
+
let(:contains_equals_regex) { /"users"\."ip" >>= '127.0.0.1'/ }
|
13
|
+
let(:equality_regex) { /"users"\."tags" = '\{"?working"?\}'/ }
|
14
14
|
|
15
15
|
describe ".where.contains(:column => value)" do
|
16
16
|
it "generates the appropriate where clause for array columns" do
|
17
|
-
query =
|
17
|
+
query = User.where.contains(tag_ids: [1, 2]).to_sql
|
18
18
|
expect(query).to match_regex(contains_array_regex)
|
19
19
|
end
|
20
20
|
|
21
21
|
it "generates the appropriate where clause for hstore columns" do
|
22
|
-
query =
|
22
|
+
query = User.where.contains(data: { nickname: "Dan" }).to_sql
|
23
23
|
expect(query).to match_regex(contains_hstore_regex)
|
24
24
|
end
|
25
25
|
|
26
26
|
it "generates the appropriate where clause for jsonb columns" do
|
27
|
-
query =
|
27
|
+
query = User.where.contains(jsonb_data: { nickname: "Dan" }).to_sql
|
28
28
|
expect(query).to match_regex(contains_jsonb_regex)
|
29
29
|
end
|
30
30
|
|
31
31
|
it "generates the appropriate where clause for hstore columns on joins" do
|
32
|
-
query = Tag.joins(:
|
32
|
+
query = Tag.joins(:user).where.contains(users: { data: { nickname: "Dan" } }).to_sql
|
33
33
|
expect(query).to match_regex(contains_hstore_regex)
|
34
34
|
end
|
35
35
|
|
36
36
|
it "allows chaining" do
|
37
|
-
query =
|
37
|
+
query = User.where.contains(tag_ids: [1, 2]).where(tags: ["working"]).to_sql
|
38
38
|
expect(query).to match_regex(contains_array_regex)
|
39
39
|
expect(query).to match_regex(equality_regex)
|
40
40
|
end
|
41
41
|
|
42
42
|
it "generates the appropriate where clause for array columns on joins" do
|
43
|
-
query = Tag.joins(:
|
43
|
+
query = Tag.joins(:user).where.contains(users: { tag_ids: [1, 2] }).to_sql
|
44
44
|
expect(query).to match_regex(contains_array_regex)
|
45
45
|
end
|
46
46
|
end
|
@@ -3,14 +3,14 @@
|
|
3
3
|
require "spec_helper"
|
4
4
|
|
5
5
|
RSpec.describe "Either Methods SQL Queries" do
|
6
|
-
let(:contains_array_regex) {
|
7
|
-
let(:profile_l_outer_join) { /LEFT OUTER JOIN
|
8
|
-
let(:profile_r_outer_join) { /LEFT OUTER JOIN
|
6
|
+
let(:contains_array_regex) { /"users"\."tag_ids" @> '\{1,2\}'/ }
|
7
|
+
let(:profile_l_outer_join) { /LEFT OUTER JOIN "profile_ls" ON "profile_ls"."user_id" = "users"."id"/ }
|
8
|
+
let(:profile_r_outer_join) { /LEFT OUTER JOIN "profile_rs" ON "profile_rs"."user_id" = "users"."id"/ }
|
9
9
|
let(:where_join_case) do
|
10
|
-
"WHERE ((CASE WHEN profile_ls.
|
11
|
-
" THEN profile_rs.
|
12
|
-
" ELSE profile_ls.
|
13
|
-
"=
|
10
|
+
"WHERE ((CASE WHEN profile_ls.user_id IS NULL"\
|
11
|
+
" THEN profile_rs.user_id"\
|
12
|
+
" ELSE profile_ls.user_id END) "\
|
13
|
+
"= users.id)"
|
14
14
|
end
|
15
15
|
|
16
16
|
let(:order_case) do
|
@@ -22,20 +22,20 @@ RSpec.describe "Either Methods SQL Queries" do
|
|
22
22
|
|
23
23
|
describe ".either_join/2" do
|
24
24
|
it "Should contain outer joins on the provided relationships" do
|
25
|
-
query =
|
25
|
+
query = User.either_join(:profile_l, :profile_r).to_sql
|
26
26
|
expect(query).to match_regex(profile_l_outer_join)
|
27
27
|
expect(query).to match_regex(profile_r_outer_join)
|
28
28
|
end
|
29
29
|
|
30
30
|
it "Should contain a case statement that will conditionally alternative between tables" do
|
31
|
-
query =
|
31
|
+
query = User.either_join(:profile_l, :profile_r).to_sql
|
32
32
|
expect(query).to include(where_join_case)
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
36
|
describe ".either_order/2" do
|
37
|
-
let(:ascended_order) {
|
38
|
-
let(:descended_order) {
|
37
|
+
let(:ascended_order) { User.either_order(:asc, profile_l: :likes, profile_r: :dislikes).to_sql }
|
38
|
+
let(:descended_order) { User.either_order(:desc, profile_l: :likes, profile_r: :dislikes).to_sql }
|
39
39
|
|
40
40
|
it "Should contain outer joins on the provided relationships" do
|
41
41
|
expect(ascended_order).to match_regex(profile_l_outer_join)
|
@@ -4,39 +4,75 @@ require "spec_helper"
|
|
4
4
|
|
5
5
|
RSpec.describe "JSON Methods SQL Queries" do
|
6
6
|
let(:single_with) { /^WITH .all_others. AS(?!.*WITH \w?)/mi }
|
7
|
-
let(:override_with) { /^WITH .all_others. AS \(.+WHERE .
|
7
|
+
let(:override_with) { /^WITH .all_others. AS \(.+WHERE .users.\..id. = 10\)/mi }
|
8
8
|
|
9
9
|
describe ".select_row_to_json" do
|
10
10
|
context "when a subquery contains a CTE table" do
|
11
|
-
let(:cte_person) {
|
11
|
+
let(:cte_person) { User.with(all_others: User.where.not(id: 1)).where(id: 2) }
|
12
12
|
|
13
13
|
it "should push the CTE to the callee's level" do
|
14
|
-
query =
|
14
|
+
query = User.select_row_to_json(cte_person, as: :results).to_sql
|
15
15
|
expect(query).to match_regex(single_with)
|
16
16
|
end
|
17
17
|
|
18
18
|
it "should favor the parents CTE table if names collide" do
|
19
|
-
query =
|
19
|
+
query = User.with(all_others: User.where(id: 10))
|
20
20
|
query = query.select_row_to_json(cte_person, as: :results).to_sql
|
21
21
|
|
22
22
|
expect(query).to match_regex(single_with)
|
23
23
|
expect(query).to match_regex(override_with)
|
24
24
|
end
|
25
25
|
end
|
26
|
+
|
27
|
+
context "When adding cast_with: option" do
|
28
|
+
it "should wrap the row_to_json expression with to_jsonb" do
|
29
|
+
query = User.select_row_to_json(User.where(id: 10), cast_with: :to_jsonb, key: :convert_this, as: :results).to_sql
|
30
|
+
expect(query).to match_regex(/SELECT \(SELECT TO_JSONB\(ROW_TO_JSON\("convert_this"\)\) FROM \(.+\).+\) AS "results"/)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should cast object to an array" do
|
34
|
+
query = User.select_row_to_json(User.where(id: 10), cast_with: :array, key: :convert_this, as: :results).to_sql
|
35
|
+
expect(query).to match_regex(/SELECT \(ARRAY\(SELECT ROW_TO_JSON\("convert_this"\) FROM \(.+\).+\)\) AS "results"/)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should cast object to an aggregated array" do
|
39
|
+
query = User.select_row_to_json(User.where(id: 10), cast_with: :array_agg, key: :convert_this, as: :results).to_sql
|
40
|
+
expect(query).to match_regex(/SELECT \(ARRAY_AGG\(\(SELECT TO_JSONB\(ROW_TO_JSON\("convert_this"\)\) FROM \(.+\).+\)\)\) AS "results"/)
|
41
|
+
end
|
42
|
+
|
43
|
+
context "When multiple cast_with options are used" do
|
44
|
+
it "should cast query with to_jsonb and as an Array" do
|
45
|
+
query = User.select_row_to_json(User.where(id: 10), cast_with: [:to_jsonb, :array], key: :convert_this, as: :results).to_sql
|
46
|
+
expect(query).to match_regex(/SELECT \(ARRAY\(SELECT TO_JSONB\(ROW_TO_JSON\("convert_this"\)\) FROM \(.+\).+\)\) AS "results"/)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should cast query as a distinct Aggregated Array" do
|
50
|
+
query = User.select_row_to_json(User.where(id: 10), cast_with: [:array_agg, :distinct], key: :convert_this, as: :results).to_sql
|
51
|
+
expect(query).to match_regex(/SELECT \(ARRAY_AGG\(DISTINCT \(SELECT TO_JSONB\(ROW_TO_JSON\("convert_this"\)\) FROM \(.+\).+\)\)\) AS "results"/)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "when the subquery is a STI record type" do
|
57
|
+
it "should not append sti 'type IN(..)' where clauses to the nested query" do
|
58
|
+
query = User.select_row_to_json(AdminSti.where(id: 10), cast_with: :array, key: :convert_this, as: :results).to_sql
|
59
|
+
expect(query).to match_regex(/SELECT \(ARRAY\(SELECT ROW_TO_JSON\("convert_this"\) FROM \(.*\) convert_this\)\) AS .+/)
|
60
|
+
end
|
61
|
+
end
|
26
62
|
end
|
27
63
|
|
28
64
|
describe ".json_build_object" do
|
29
65
|
context "when a subquery contains a CTE table" do
|
30
|
-
let(:cte_person) {
|
66
|
+
let(:cte_person) { User.with(all_others: User.where.not(id: 1)).where(id: 2) }
|
31
67
|
|
32
68
|
it "should push the CTE to the callee's level" do
|
33
|
-
query =
|
69
|
+
query = User.json_build_object(:userss, cte_person).to_sql
|
34
70
|
expect(query).to match_regex(single_with)
|
35
71
|
end
|
36
72
|
|
37
73
|
it "should favor the parents CTE table if names collide" do
|
38
|
-
query =
|
39
|
-
query = query.json_build_object(:
|
74
|
+
query = User.with(all_others: User.where(id: 10))
|
75
|
+
query = query.json_build_object(:users, cte_person).to_sql
|
40
76
|
|
41
77
|
expect(query).to match_regex(single_with)
|
42
78
|
expect(query).to match_regex(override_with)
|
@@ -3,27 +3,27 @@
|
|
3
3
|
require "spec_helper"
|
4
4
|
|
5
5
|
RSpec.describe "Union SQL Queries" do
|
6
|
-
let(:
|
7
|
-
let(:
|
6
|
+
let(:user) { User.where(id: 1) }
|
7
|
+
let(:other_user) { User.where("id = 2") }
|
8
8
|
|
9
9
|
shared_examples_for "unions" do
|
10
|
-
it { is_expected.to eq("( (#{
|
10
|
+
it { is_expected.to eq("( (#{user.to_sql}) #{described_union} (#{other_user.to_sql}) )") }
|
11
11
|
end
|
12
12
|
|
13
13
|
shared_examples_for "piping nest CTE tables" do
|
14
|
-
let(:
|
14
|
+
let(:cte_user) { User.with(all_others: User.where.not(id: 1)).where(id: 2) }
|
15
15
|
let(:method) { raise "Required to override this method!" }
|
16
16
|
let(:single_with) { /^WITH .all_others. AS(?!.*WITH \w?)/mi }
|
17
|
-
let(:override_with) { /^WITH .all_others. AS \(.+WHERE .
|
17
|
+
let(:override_with) { /^WITH .all_others. AS \(.+WHERE .users.\..id. = 10\)/mi }
|
18
18
|
|
19
19
|
it "should push the CTE to the callee's level" do
|
20
|
-
query =
|
20
|
+
query = User.send(method.to_sym, cte_user, other_user).to_sql
|
21
21
|
expect(query).to match_regex(single_with)
|
22
22
|
end
|
23
23
|
|
24
24
|
it "should favor the parents CTE table if names collide" do
|
25
|
-
query =
|
26
|
-
query = query.send(method.to_sym,
|
25
|
+
query = User.with(all_others: User.where(id: 10))
|
26
|
+
query = query.send(method.to_sym, cte_user, other_user).to_sql
|
27
27
|
|
28
28
|
expect(query).to match_regex(single_with)
|
29
29
|
expect(query).to match_regex(override_with)
|
@@ -32,7 +32,7 @@ RSpec.describe "Union SQL Queries" do
|
|
32
32
|
|
33
33
|
describe ".union" do
|
34
34
|
let!(:described_union) { "UNION" }
|
35
|
-
subject(:described_method) {
|
35
|
+
subject(:described_method) { User.union(user, other_user).to_union_sql }
|
36
36
|
it_behaves_like "unions"
|
37
37
|
it_behaves_like "piping nest CTE tables" do
|
38
38
|
let!(:method) { :union }
|
@@ -41,7 +41,7 @@ RSpec.describe "Union SQL Queries" do
|
|
41
41
|
|
42
42
|
describe ".union.all" do
|
43
43
|
let!(:described_union) { "UNION ALL" }
|
44
|
-
subject(:described_method) {
|
44
|
+
subject(:described_method) { User.union.all(user, other_user).to_union_sql }
|
45
45
|
it_behaves_like "unions"
|
46
46
|
it_behaves_like "piping nest CTE tables" do
|
47
47
|
let!(:method) { :union_all }
|
@@ -50,7 +50,7 @@ RSpec.describe "Union SQL Queries" do
|
|
50
50
|
|
51
51
|
describe ".union.except" do
|
52
52
|
let!(:described_union) { "EXCEPT" }
|
53
|
-
subject(:described_method) {
|
53
|
+
subject(:described_method) { User.union.except(user, other_user).to_union_sql }
|
54
54
|
it_behaves_like "unions"
|
55
55
|
it_behaves_like "piping nest CTE tables" do
|
56
56
|
let!(:method) { :union_except }
|
@@ -59,7 +59,7 @@ RSpec.describe "Union SQL Queries" do
|
|
59
59
|
|
60
60
|
describe "union.intersect" do
|
61
61
|
let!(:described_union) { "INTERSECT" }
|
62
|
-
subject(:described_method) {
|
62
|
+
subject(:described_method) { User.union.intersect(user, other_user).to_union_sql }
|
63
63
|
it_behaves_like "unions"
|
64
64
|
it_behaves_like "piping nest CTE tables" do
|
65
65
|
let!(:method) { :union_intersect }
|
@@ -69,28 +69,28 @@ RSpec.describe "Union SQL Queries" do
|
|
69
69
|
describe "union.as" do
|
70
70
|
context "when a union.as has been called" do
|
71
71
|
subject(:described_method) do
|
72
|
-
|
72
|
+
User.select("happy_users.id").union(user, other_user).union.as(:happy_users).to_sql
|
73
73
|
end
|
74
74
|
|
75
|
-
it "should alias the union from clause to '
|
76
|
-
expect(described_method).to match_regex(/FROM \(+.+\) UNION \(.+\)+
|
77
|
-
expect(described_method).to match_regex(/^SELECT
|
75
|
+
it "should alias the union from clause to 'happy_users'" do
|
76
|
+
expect(described_method).to match_regex(/FROM \(+.+\) UNION \(.+\)+ happy_users$/)
|
77
|
+
expect(described_method).to match_regex(/^SELECT (happy_users\.id|"happy_users"\."id") FROM.+happy_users$/)
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
81
|
context "when user.as hasn't been called" do
|
82
|
-
subject(:described_method) {
|
82
|
+
subject(:described_method) { User.select(:id).union(user, other_user).to_sql }
|
83
83
|
|
84
84
|
it "should retain the actual class calling table name as the union alias" do
|
85
|
-
expect(described_method).to match_regex(/FROM \(+.+\) UNION \(.+\)+
|
86
|
-
expect(described_method).to match_regex(/^SELECT
|
85
|
+
expect(described_method).to match_regex(/FROM \(+.+\) UNION \(.+\)+ users$/)
|
86
|
+
expect(described_method).to match_regex(/^SELECT "users"\."id" FROM.+users$/)
|
87
87
|
end
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
91
91
|
describe "union.order" do
|
92
92
|
context "when rendering with .to_union_sql" do
|
93
|
-
subject(:described_method) {
|
93
|
+
subject(:described_method) { User.union(user, other_user).union.order(:id, name: :desc).to_union_sql }
|
94
94
|
|
95
95
|
it "Should append an 'ORDER BY' to the end of the union statements" do
|
96
96
|
expect(described_method).to match_regex(/^\(+.+\) UNION \(.+\) \) ORDER BY id, name DESC$/)
|
@@ -98,10 +98,10 @@ RSpec.describe "Union SQL Queries" do
|
|
98
98
|
end
|
99
99
|
|
100
100
|
context "when rendering with .to_sql" do
|
101
|
-
subject(:described_method) {
|
101
|
+
subject(:described_method) { User.union(user, other_user).union.order(:id, name: :desc).to_sql }
|
102
102
|
|
103
103
|
it "Should append an 'ORDER BY' to the end of the union statements" do
|
104
|
-
expect(described_method).to match_regex(/FROM \(+.+\) UNION \(.+\) \) ORDER BY id, name DESC\)
|
104
|
+
expect(described_method).to match_regex(/FROM \(+.+\) UNION \(.+\) \) ORDER BY id, name DESC\) users$/)
|
105
105
|
end
|
106
106
|
end
|
107
107
|
|
@@ -109,11 +109,11 @@ RSpec.describe "Union SQL Queries" do
|
|
109
109
|
let(:query_regex) { /(?<=\)\s(ORDER BY)) id/ }
|
110
110
|
|
111
111
|
it "should only append an order by to the very end of a union statements" do
|
112
|
-
query =
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
112
|
+
query = User.union.order(id: :asc, tags: :desc)
|
113
|
+
.union(user.order(id: :asc, tags: :desc))
|
114
|
+
.union(user.order(:id, :tags))
|
115
|
+
.union(other_user.order(id: :desc, tags: :desc))
|
116
|
+
.to_union_sql
|
117
117
|
|
118
118
|
index = query.index(query_regex)
|
119
119
|
expect(index).to be_truthy
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe "Active Record WINDOW Query inspection" do
|
6
|
+
describe "#define_window" do
|
7
|
+
context "when there is a single defined window" do
|
8
|
+
it "should only contain a single defined window statement at the bottom" do
|
9
|
+
query = Tag.define_window(:w_test).partition_by(:user_id).to_sql
|
10
|
+
expect(query).to eq('SELECT "tags".* FROM "tags" WINDOW w_test AS (PARTITION BY user_id)')
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should return a single defined window with a defined ORDER BY" do
|
14
|
+
query = Tag.define_window(:w_test).partition_by(:user_id, order_by: { tags: { user_id: :desc } }).to_sql
|
15
|
+
expect(query).to end_with("WINDOW w_test AS (PARTITION BY user_id ORDER BY tags.user_id DESC)")
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should place the window function below the WHERE and GROUP BY statements" do
|
19
|
+
query = Tag.define_window(:w_test).partition_by(:user_id).where(id: 1).group(:user_id).to_sql
|
20
|
+
expect(query).to eq('SELECT "tags".* FROM "tags" WHERE "tags"."id" = 1 GROUP BY "tags"."user_id" WINDOW w_test AS (PARTITION BY user_id)')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when there are multiple defined windows" do
|
25
|
+
it "should only contain a single defined window statement at the bottom" do
|
26
|
+
query =
|
27
|
+
Tag
|
28
|
+
.define_window(:test).partition_by(:user_id)
|
29
|
+
.define_window(:other_window).partition_by(:id)
|
30
|
+
.to_sql
|
31
|
+
|
32
|
+
expect(query).to end_with("WINDOW test AS (PARTITION BY user_id), other_window AS (PARTITION BY id)")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should contain each windows order by statements" do
|
36
|
+
query =
|
37
|
+
Tag
|
38
|
+
.define_window(:test).partition_by(:user_id, order_by: :id)
|
39
|
+
.define_window(:other_window).partition_by(:id, order_by: { tags: :user_id })
|
40
|
+
.to_sql
|
41
|
+
|
42
|
+
expect(query).to end_with("WINDOW test AS (PARTITION BY user_id ORDER BY id), other_window AS (PARTITION BY id ORDER BY tags.user_id ASC)")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#window_select" do
|
48
|
+
let(:query_base) { Tag.define_window(:w).partition_by(:user_id, order_by: :id) }
|
49
|
+
let(:expected_end) { "WINDOW w AS (PARTITION BY user_id ORDER BY id)" }
|
50
|
+
|
51
|
+
context "when using no argument window methods" do
|
52
|
+
[:row_to_number, :rank, :dense_rank, :percent_rank, :cume_dist].each do |window_function|
|
53
|
+
context "#{window_function.to_s.upcase}()" do
|
54
|
+
let(:expected_function) { "#{window_function.to_s.upcase}()" }
|
55
|
+
let(:query) do
|
56
|
+
query_base.select_window(window_function, over: :w, as: :window_response).to_sql
|
57
|
+
end
|
58
|
+
|
59
|
+
it "appends the function to the select query" do
|
60
|
+
expected_start = "SELECT (#{expected_function} OVER w) AS \"window_response\""
|
61
|
+
expect(query).to start_with(expected_start).and(end_with(expected_end))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "when using an argument based window method" do
|
68
|
+
let(:argument_list) { ["a", 1, :sauce] }
|
69
|
+
|
70
|
+
{ ntile: 1, lag: 2, lead: 3, first_value: 1, last_value: 1, nth_value: 2 }.each_pair do |window_function, arg_count|
|
71
|
+
context "#{window_function.to_s.upcase}/#{arg_count}" do
|
72
|
+
let(:arguments) { argument_list.first(arg_count) }
|
73
|
+
let(:expected_function) { "#{window_function.to_s.upcase}(#{arguments.join(', ')})" }
|
74
|
+
let(:query) do
|
75
|
+
query_base.select_window(window_function, *arguments, over: :w, as: :window_response).to_sql
|
76
|
+
end
|
77
|
+
|
78
|
+
it "appends the function to the select query" do
|
79
|
+
expected_start = "SELECT (#{expected_function} OVER w) AS \"window_response\""
|
80
|
+
expect(query).to start_with(expected_start).and(end_with(expected_end))
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "when not providing a partition by value" do
|
87
|
+
it "should construct a window function" do
|
88
|
+
query =
|
89
|
+
Tag
|
90
|
+
.define_window(:no_args).partition_by(order_by: { tag_number: :desc })
|
91
|
+
.select_window(:row_number, over: :no_args, as: :my_row)
|
92
|
+
.to_sql
|
93
|
+
|
94
|
+
expect(query).to eq("SELECT (ROW_NUMBER() OVER no_args) AS \"my_row\" FROM \"tags\" WINDOW no_args AS (ORDER BY tag_number DESC)")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|