active_record_extended 2.0.3 → 3.1.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 +140 -77
- data/lib/active_record_extended/arel/nodes.rb +1 -1
- data/lib/active_record_extended/arel/{sql_literal.rb → sql_literal_patch.rb} +2 -2
- data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +16 -12
- data/lib/active_record_extended/arel.rb +1 -1
- data/lib/active_record_extended/patch/array_handler_patch.rb +22 -0
- data/lib/active_record_extended/patch/relation_patch.rb +82 -0
- data/lib/active_record_extended/patch/where_clause_patch.rb +13 -0
- data/lib/active_record_extended/query_methods/any_of.rb +1 -1
- data/lib/active_record_extended/query_methods/either.rb +5 -7
- data/lib/active_record_extended/query_methods/{select.rb → foster_select.rb} +4 -4
- data/lib/active_record_extended/query_methods/json.rb +2 -2
- data/lib/active_record_extended/query_methods/unionize.rb +5 -5
- data/lib/active_record_extended/query_methods/where_chain.rb +96 -90
- data/lib/active_record_extended/query_methods/window.rb +3 -3
- data/lib/active_record_extended/query_methods/with_cte.rb +61 -4
- data/lib/active_record_extended/utilities/order_by.rb +1 -1
- data/lib/active_record_extended/utilities/support.rb +1 -1
- data/lib/active_record_extended/version.rb +1 -1
- data/lib/active_record_extended.rb +55 -4
- metadata +20 -83
- data/lib/active_record_extended/active_record/relation_patch.rb +0 -50
- data/lib/active_record_extended/active_record.rb +0 -25
- data/lib/active_record_extended/patch/5_1/where_clause.rb +0 -11
- data/lib/active_record_extended/patch/5_2/where_clause.rb +0 -11
- data/lib/active_record_extended/predicate_builder/array_handler_decorator.rb +0 -20
- data/spec/active_record_extended_spec.rb +0 -7
- data/spec/query_methods/any_of_spec.rb +0 -131
- data/spec/query_methods/array_query_spec.rb +0 -64
- data/spec/query_methods/either_spec.rb +0 -70
- data/spec/query_methods/hash_query_spec.rb +0 -45
- data/spec/query_methods/inet_query_spec.rb +0 -112
- data/spec/query_methods/json_spec.rb +0 -157
- data/spec/query_methods/select_spec.rb +0 -115
- data/spec/query_methods/unionize_spec.rb +0 -165
- data/spec/query_methods/window_spec.rb +0 -51
- data/spec/query_methods/with_cte_spec.rb +0 -50
- data/spec/spec_helper.rb +0 -28
- data/spec/sql_inspections/any_of_sql_spec.rb +0 -41
- data/spec/sql_inspections/arel/aggregate_function_name_spec.rb +0 -41
- data/spec/sql_inspections/arel/array_spec.rb +0 -63
- data/spec/sql_inspections/arel/inet_spec.rb +0 -66
- data/spec/sql_inspections/contains_sql_queries_spec.rb +0 -47
- data/spec/sql_inspections/either_sql_spec.rb +0 -71
- data/spec/sql_inspections/json_sql_spec.rb +0 -82
- data/spec/sql_inspections/unionize_sql_spec.rb +0 -124
- data/spec/sql_inspections/window_sql_spec.rb +0 -98
- data/spec/sql_inspections/with_cte_sql_spec.rb +0 -95
- data/spec/support/database_cleaner.rb +0 -15
- data/spec/support/models.rb +0 -80
@@ -1,41 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "spec_helper"
|
4
|
-
|
5
|
-
RSpec.describe "Any / None of SQL Queries" do
|
6
|
-
let(:equal_query) { '"users"."personal_id" = 1' }
|
7
|
-
let(:or_query) { 'OR "users"."personal_id" = 2' }
|
8
|
-
let(:equal_or) { "#{equal_query} #{or_query}" }
|
9
|
-
let(:join_query) { /INNER JOIN "tags" ON "tags"."user_id" = "users"."id/ }
|
10
|
-
|
11
|
-
describe "where.any_of/1" do
|
12
|
-
it "should group different column arguments into nested or conditions" do
|
13
|
-
query = User.where.any_of({ personal_id: 1 }, { id: 2 }, { personal_id: 2 }).to_sql
|
14
|
-
expect(query).to match_regex(/WHERE \(\(.+ = 1 OR .+ = 2\) OR .+ = 2\)/)
|
15
|
-
end
|
16
|
-
|
17
|
-
it "Should assign where clause predicates for standard queries" do
|
18
|
-
query = User.where.any_of({ personal_id: 1 }, { personal_id: 2 }).to_sql
|
19
|
-
expect(query).to include(equal_or)
|
20
|
-
|
21
|
-
personal_one = User.where(personal_id: 1)
|
22
|
-
personal_two = User.where(personal_id: 2)
|
23
|
-
query = User.where.any_of(personal_one, personal_two).to_sql
|
24
|
-
expect(query).to include(equal_or)
|
25
|
-
end
|
26
|
-
|
27
|
-
it "Joining queries should be added to the select statement" do
|
28
|
-
user_two_tag = User.where(personal_id: 1).joins(:hm_tags)
|
29
|
-
query = User.where.any_of(user_two_tag).to_sql
|
30
|
-
expect(query).to match_regex(join_query)
|
31
|
-
expect(query).to include(equal_query)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
describe "where.none_of/1" do
|
36
|
-
it "Should surround the query in a WHERE NOT clause" do
|
37
|
-
query = User.where.none_of({ personal_id: 1 }, { id: 2 }, { personal_id: 2 }).to_sql
|
38
|
-
expect(query).to match_regex(/WHERE.+NOT \(\(.+ = 1 OR .+ = 2\) OR .+ = 2\)/)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "spec_helper"
|
4
|
-
|
5
|
-
RSpec.describe Arel::Nodes::AggregateFunctionName do
|
6
|
-
describe "Custom Aggregate function" do
|
7
|
-
it "constructs an aggregate function based on a given name" do
|
8
|
-
query = described_class.new("MY_CUSTOM_AGG", [Arel.sql("id == me")])
|
9
|
-
expect(query.to_sql).to eq("MY_CUSTOM_AGG(id == me)")
|
10
|
-
end
|
11
|
-
|
12
|
-
it "can append multiple expressions" do
|
13
|
-
query = described_class.new("MY_CUSTOM_AGG", [Arel.sql("id == me"), Arel.sql("id == you")])
|
14
|
-
expect(query.to_sql).to eq("MY_CUSTOM_AGG(id == me, id == you)")
|
15
|
-
end
|
16
|
-
|
17
|
-
it "can append a distinct clause inside the aggregate" do
|
18
|
-
query = described_class.new("MY_CUSTOM_AGG", [Arel.sql("id == me")], true)
|
19
|
-
expect(query.to_sql).to eq("MY_CUSTOM_AGG(DISTINCT id == me)")
|
20
|
-
end
|
21
|
-
|
22
|
-
it "can append an order by clause when providing a ordering expression" do
|
23
|
-
order_expr = Arel.sql("id").desc
|
24
|
-
query = described_class.new("MY_CUSTOM_AGG", [Arel.sql("id == me")], true).order_by([order_expr])
|
25
|
-
expect(query.to_sql).to eq("MY_CUSTOM_AGG(DISTINCT id == me ORDER BY id DESC)")
|
26
|
-
end
|
27
|
-
|
28
|
-
it "can append multiple ordering clauses" do
|
29
|
-
expr = Arel.sql("id").desc
|
30
|
-
other_expr = Arel.sql("name").asc
|
31
|
-
query = described_class.new("MY_CUSTOM_AGG", [Arel.sql("id == me")], true).order_by([expr, other_expr])
|
32
|
-
expect(query.to_sql).to eq("MY_CUSTOM_AGG(DISTINCT id == me ORDER BY id DESC, name ASC)")
|
33
|
-
end
|
34
|
-
|
35
|
-
it "can be aliased" do
|
36
|
-
alias_as = Arel.sql("new_name")
|
37
|
-
query = described_class.new("MY_CUSTOM_AGG", [Arel.sql("id == me")], true).as(alias_as)
|
38
|
-
expect(query.to_sql).to eq("MY_CUSTOM_AGG(DISTINCT id == me) AS new_name")
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
@@ -1,63 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "spec_helper"
|
4
|
-
|
5
|
-
RSpec.describe "Array Column Predicates" do
|
6
|
-
let(:arel_table) { User.arel_table }
|
7
|
-
|
8
|
-
describe "Array Overlap" do
|
9
|
-
it "converts Arel overlap statement" do
|
10
|
-
query = arel_table.where(arel_table[:tags].overlap(["tag", "tag 2"])).to_sql
|
11
|
-
expect(query).to match_regex(/&& '\{"?tag"?,"tag 2"\}'/)
|
12
|
-
end
|
13
|
-
|
14
|
-
it "converts Arel overlap statement" do
|
15
|
-
query = arel_table.where(arel_table[:tag_ids].overlap([1, 2])).to_sql
|
16
|
-
expect(query).to match_regex(/&& '\{1,2\}'/)
|
17
|
-
end
|
18
|
-
|
19
|
-
it "works with count (and other predicates)" do
|
20
|
-
expect(User.where(arel_table[:tag_ids].overlap([1, 2])).count).to eq 0
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
describe "Array Contains" do
|
25
|
-
it "converts Arel contains statement and escapes strings" do
|
26
|
-
query = arel_table.where(arel_table[:tags].contains(["tag", "tag 2"])).to_sql
|
27
|
-
expect(query).to match_regex(/@> '\{"?tag"?,"tag 2"\}'/)
|
28
|
-
end
|
29
|
-
|
30
|
-
it "converts Arel contains statement with numbers" do
|
31
|
-
query = arel_table.where(arel_table[:tag_ids].contains([1, 2])).to_sql
|
32
|
-
expect(query).to match_regex(/@> '\{1,2\}'/)
|
33
|
-
end
|
34
|
-
|
35
|
-
it "works with count (and other predicates)" do
|
36
|
-
expect(User.where(arel_table[:tag_ids].contains([1, 2])).count).to eq 0
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
describe "Any Array Element" do
|
41
|
-
it "creates any predicates that contain a string value" do
|
42
|
-
query = arel_table.where(arel_table[:tags].any("tag")).to_sql
|
43
|
-
expect(query).to match_regex(/'tag' = ANY\("users"\."tags"\)/)
|
44
|
-
end
|
45
|
-
|
46
|
-
it "creates any predicates that contain a integer value" do
|
47
|
-
query = arel_table.where(arel_table[:tags].any(2)).to_sql
|
48
|
-
expect(query).to match_regex(/2 = ANY\("users"\."tags"\)/)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
describe "All Array Elements" do
|
53
|
-
it "create all predicates that contain a string value" do
|
54
|
-
query = arel_table.where(arel_table[:tags].all("tag")).to_sql
|
55
|
-
expect(query).to match_regex(/'tag' = ALL\("users"\."tags"\)/)
|
56
|
-
end
|
57
|
-
|
58
|
-
it "create all predicates that contain a interger value" do
|
59
|
-
query = arel_table.where(arel_table[:tags].all(2)).to_sql
|
60
|
-
expect(query).to match_regex(/2 = ALL\("users"\."tags"\)/)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
@@ -1,66 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "spec_helper"
|
4
|
-
|
5
|
-
RSpec.describe "Inet Column Predicates" do
|
6
|
-
let(:arel_table) { User.arel_table }
|
7
|
-
|
8
|
-
describe "#inet_contained_within" do
|
9
|
-
it "converts Arel inet contained within statement" do
|
10
|
-
query = arel_table.where(arel_table[:ip].inet_contained_within(IPAddr.new("127.0.0.1"))).to_sql
|
11
|
-
expect(query).to match_regex(%r{<< '127\.0\.0\.1/32'})
|
12
|
-
end
|
13
|
-
|
14
|
-
it "works with count" do
|
15
|
-
expect(User.where(arel_table[:ip].inet_contained_within(IPAddr.new("127.0.0.1"))).count).to eq(0)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
describe "#inet_contained_within_or_equals" do
|
20
|
-
it "converts Arel inet contained within statement" do
|
21
|
-
query = arel_table.where(arel_table[:ip].inet_contained_within_or_equals(IPAddr.new("127.0.0.1"))).to_sql
|
22
|
-
expect(query).to match_regex(%r{<<= '127\.0\.0\.1/32'})
|
23
|
-
end
|
24
|
-
|
25
|
-
it "works with count" do
|
26
|
-
expect(User.where(arel_table[:ip].inet_contained_within_or_equals(IPAddr.new("127.0.0.1"))).count).to eq(0)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
describe "#inet_contains_or_equals" do
|
31
|
-
it "converts Arel inet contained within statement" do
|
32
|
-
query = arel_table.where(arel_table[:ip].inet_contains_or_equals(IPAddr.new("127.0.0.1"))).to_sql
|
33
|
-
expect(query).to match_regex(%r{>>= '127\.0\.0\.1/32'})
|
34
|
-
end
|
35
|
-
|
36
|
-
it "works with count" do
|
37
|
-
expect(User.where(arel_table[:ip].inet_contains_or_equals(IPAddr.new("127.0.0.1"))).count).to eq(0)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
describe "#inet_contains" do
|
42
|
-
it "converts Arel inet contained within statement" do
|
43
|
-
query = arel_table.where(arel_table[:ip].inet_contains("127.0.0.1")).to_sql
|
44
|
-
expect(query).to match_regex(/>> '127\.0\.0\.1'/)
|
45
|
-
end
|
46
|
-
|
47
|
-
it "works with count" do
|
48
|
-
expect(User.where(arel_table[:ip].inet_contains("127.0.0.1")).count).to eq(0)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
describe "#inet_contains_or_is_contained_within" do
|
53
|
-
it "converts Arel inet contained within statement" do
|
54
|
-
query = arel_table.where(arel_table[:ip].inet_contains_or_is_contained_within("127.0.0.1")).to_sql
|
55
|
-
expect(query).to match_regex(/&& '127\.0\.0\.1'/)
|
56
|
-
|
57
|
-
query = arel_table.where(arel_table[:ip].inet_contains_or_is_contained_within(IPAddr.new("127.0.0.1"))).to_sql
|
58
|
-
expect(query).to match_regex(%r{&& '127\.0\.0\.1/32'})
|
59
|
-
end
|
60
|
-
|
61
|
-
it "works with count" do
|
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
|
-
end
|
65
|
-
end
|
66
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "spec_helper"
|
4
|
-
|
5
|
-
RSpec.describe "Contains SQL Queries" do
|
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
|
-
|
15
|
-
describe ".where.contains(:column => value)" do
|
16
|
-
it "generates the appropriate where clause for array columns" do
|
17
|
-
query = User.where.contains(tag_ids: [1, 2]).to_sql
|
18
|
-
expect(query).to match_regex(contains_array_regex)
|
19
|
-
end
|
20
|
-
|
21
|
-
it "generates the appropriate where clause for hstore columns" do
|
22
|
-
query = User.where.contains(data: { nickname: "Dan" }).to_sql
|
23
|
-
expect(query).to match_regex(contains_hstore_regex)
|
24
|
-
end
|
25
|
-
|
26
|
-
it "generates the appropriate where clause for jsonb columns" do
|
27
|
-
query = User.where.contains(jsonb_data: { nickname: "Dan" }).to_sql
|
28
|
-
expect(query).to match_regex(contains_jsonb_regex)
|
29
|
-
end
|
30
|
-
|
31
|
-
it "generates the appropriate where clause for hstore columns on joins" do
|
32
|
-
query = Tag.joins(:user).where.contains(users: { data: { nickname: "Dan" } }).to_sql
|
33
|
-
expect(query).to match_regex(contains_hstore_regex)
|
34
|
-
end
|
35
|
-
|
36
|
-
it "allows chaining" do
|
37
|
-
query = User.where.contains(tag_ids: [1, 2]).where(tags: ["working"]).to_sql
|
38
|
-
expect(query).to match_regex(contains_array_regex)
|
39
|
-
expect(query).to match_regex(equality_regex)
|
40
|
-
end
|
41
|
-
|
42
|
-
it "generates the appropriate where clause for array columns on joins" do
|
43
|
-
query = Tag.joins(:user).where.contains(users: { tag_ids: [1, 2] }).to_sql
|
44
|
-
expect(query).to match_regex(contains_array_regex)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
@@ -1,71 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "spec_helper"
|
4
|
-
|
5
|
-
RSpec.describe "Either Methods SQL Queries" do
|
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
|
-
let(:where_join_case) do
|
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
|
-
end
|
15
|
-
|
16
|
-
let(:order_case) do
|
17
|
-
"ORDER BY "\
|
18
|
-
"(CASE WHEN profile_ls.likes IS NULL"\
|
19
|
-
" THEN profile_rs.dislikes"\
|
20
|
-
" ELSE profile_ls.likes END)"
|
21
|
-
end
|
22
|
-
|
23
|
-
describe ".either_join/2" do
|
24
|
-
it "Should contain outer joins on the provided relationships" do
|
25
|
-
query = User.either_join(:profile_l, :profile_r).to_sql
|
26
|
-
expect(query).to match_regex(profile_l_outer_join)
|
27
|
-
expect(query).to match_regex(profile_r_outer_join)
|
28
|
-
end
|
29
|
-
|
30
|
-
it "Should contain a case statement that will conditionally alternative between tables" do
|
31
|
-
query = User.either_join(:profile_l, :profile_r).to_sql
|
32
|
-
expect(query).to include(where_join_case)
|
33
|
-
end
|
34
|
-
|
35
|
-
context "Through association .either_joins/2" do
|
36
|
-
let!(:four) { User.create! }
|
37
|
-
let!(:group) { Group.create!(users: [four]) }
|
38
|
-
let(:where_join_through_case) do
|
39
|
-
"WHERE ((CASE WHEN profile_ls.user_id IS NULL"\
|
40
|
-
" THEN groups_users.user_id"\
|
41
|
-
" ELSE profile_ls.user_id END) "\
|
42
|
-
"= users.id)"
|
43
|
-
end
|
44
|
-
|
45
|
-
it "Should contain a case statement that will conditionally alternative between tables" do
|
46
|
-
query = User.either_join(:profile_l, :groups).to_sql
|
47
|
-
expect(query).to include(where_join_through_case)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
describe ".either_order/2" do
|
53
|
-
let(:ascended_order) { User.either_order(:asc, profile_l: :likes, profile_r: :dislikes).to_sql }
|
54
|
-
let(:descended_order) { User.either_order(:desc, profile_l: :likes, profile_r: :dislikes).to_sql }
|
55
|
-
|
56
|
-
it "Should contain outer joins on the provided relationships" do
|
57
|
-
expect(ascended_order).to match_regex(profile_l_outer_join)
|
58
|
-
expect(ascended_order).to match_regex(profile_r_outer_join)
|
59
|
-
expect(descended_order).to match_regex(profile_l_outer_join)
|
60
|
-
expect(descended_order).to match_regex(profile_r_outer_join)
|
61
|
-
end
|
62
|
-
|
63
|
-
it "Should contain a relational ordering case statement for a relations column" do
|
64
|
-
expect(ascended_order).to include(order_case)
|
65
|
-
expect(ascended_order).to end_with("asc")
|
66
|
-
|
67
|
-
expect(descended_order).to include(order_case)
|
68
|
-
expect(descended_order).to end_with("desc")
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
@@ -1,82 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "spec_helper"
|
4
|
-
|
5
|
-
RSpec.describe "JSON Methods SQL Queries" do
|
6
|
-
let(:single_with) { /^WITH .all_others. AS(?!.*WITH \w?)/mi }
|
7
|
-
let(:override_with) { /^WITH .all_others. AS \(.+WHERE .users.\..id. = 10\)/mi }
|
8
|
-
|
9
|
-
describe ".select_row_to_json" do
|
10
|
-
context "when a subquery contains a CTE table" do
|
11
|
-
let(:cte_person) { User.with(all_others: User.where.not(id: 1)).where(id: 2) }
|
12
|
-
|
13
|
-
it "should push the CTE to the callee's level" do
|
14
|
-
query = User.select_row_to_json(cte_person, as: :results).to_sql
|
15
|
-
expect(query).to match_regex(single_with)
|
16
|
-
end
|
17
|
-
|
18
|
-
it "should favor the parents CTE table if names collide" do
|
19
|
-
query = User.with(all_others: User.where(id: 10))
|
20
|
-
query = query.select_row_to_json(cte_person, as: :results).to_sql
|
21
|
-
|
22
|
-
expect(query).to match_regex(single_with)
|
23
|
-
expect(query).to match_regex(override_with)
|
24
|
-
end
|
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
|
62
|
-
end
|
63
|
-
|
64
|
-
describe ".json_build_object" do
|
65
|
-
context "when a subquery contains a CTE table" do
|
66
|
-
let(:cte_person) { User.with(all_others: User.where.not(id: 1)).where(id: 2) }
|
67
|
-
|
68
|
-
it "should push the CTE to the callee's level" do
|
69
|
-
query = User.json_build_object(:userss, cte_person).to_sql
|
70
|
-
expect(query).to match_regex(single_with)
|
71
|
-
end
|
72
|
-
|
73
|
-
it "should favor the parents CTE table if names collide" do
|
74
|
-
query = User.with(all_others: User.where(id: 10))
|
75
|
-
query = query.json_build_object(:users, cte_person).to_sql
|
76
|
-
|
77
|
-
expect(query).to match_regex(single_with)
|
78
|
-
expect(query).to match_regex(override_with)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
@@ -1,124 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "spec_helper"
|
4
|
-
|
5
|
-
RSpec.describe "Union SQL Queries" do
|
6
|
-
let(:user) { User.where(id: 1) }
|
7
|
-
let(:other_user) { User.where("id = 2") }
|
8
|
-
|
9
|
-
shared_examples_for "unions" do
|
10
|
-
it { is_expected.to eq("( (#{user.to_sql}) #{described_union} (#{other_user.to_sql}) )") }
|
11
|
-
end
|
12
|
-
|
13
|
-
shared_examples_for "piping nest CTE tables" do
|
14
|
-
let(:cte_user) { User.with(all_others: User.where.not(id: 1)).where(id: 2) }
|
15
|
-
let(:method) { raise "Required to override this method!" }
|
16
|
-
let(:single_with) { /^WITH .all_others. AS(?!.*WITH \w?)/mi }
|
17
|
-
let(:override_with) { /^WITH .all_others. AS \(.+WHERE .users.\..id. = 10\)/mi }
|
18
|
-
|
19
|
-
it "should push the CTE to the callee's level" do
|
20
|
-
query = User.send(method.to_sym, cte_user, other_user).to_sql
|
21
|
-
expect(query).to match_regex(single_with)
|
22
|
-
end
|
23
|
-
|
24
|
-
it "should favor the parents CTE table if names collide" do
|
25
|
-
query = User.with(all_others: User.where(id: 10))
|
26
|
-
query = query.send(method.to_sym, cte_user, other_user).to_sql
|
27
|
-
|
28
|
-
expect(query).to match_regex(single_with)
|
29
|
-
expect(query).to match_regex(override_with)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
describe ".union" do
|
34
|
-
let!(:described_union) { "UNION" }
|
35
|
-
subject(:described_method) { User.union(user, other_user).to_union_sql }
|
36
|
-
it_behaves_like "unions"
|
37
|
-
it_behaves_like "piping nest CTE tables" do
|
38
|
-
let!(:method) { :union }
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
describe ".union.all" do
|
43
|
-
let!(:described_union) { "UNION ALL" }
|
44
|
-
subject(:described_method) { User.union.all(user, other_user).to_union_sql }
|
45
|
-
it_behaves_like "unions"
|
46
|
-
it_behaves_like "piping nest CTE tables" do
|
47
|
-
let!(:method) { :union_all }
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
describe ".union.except" do
|
52
|
-
let!(:described_union) { "EXCEPT" }
|
53
|
-
subject(:described_method) { User.union.except(user, other_user).to_union_sql }
|
54
|
-
it_behaves_like "unions"
|
55
|
-
it_behaves_like "piping nest CTE tables" do
|
56
|
-
let!(:method) { :union_except }
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
describe "union.intersect" do
|
61
|
-
let!(:described_union) { "INTERSECT" }
|
62
|
-
subject(:described_method) { User.union.intersect(user, other_user).to_union_sql }
|
63
|
-
it_behaves_like "unions"
|
64
|
-
it_behaves_like "piping nest CTE tables" do
|
65
|
-
let!(:method) { :union_intersect }
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
describe "union.as" do
|
70
|
-
context "when a union.as has been called" do
|
71
|
-
subject(:described_method) do
|
72
|
-
User.select("happy_users.id").union(user, other_user).union.as(:happy_users).to_sql
|
73
|
-
end
|
74
|
-
|
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
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
context "when user.as hasn't been called" do
|
82
|
-
subject(:described_method) { User.select(:id).union(user, other_user).to_sql }
|
83
|
-
|
84
|
-
it "should retain the actual class calling table name as the union alias" do
|
85
|
-
expect(described_method).to match_regex(/FROM \(+.+\) UNION \(.+\)+ users$/)
|
86
|
-
expect(described_method).to match_regex(/^SELECT "users"\."id" FROM.+users$/)
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
describe "union.order" do
|
92
|
-
context "when rendering with .to_union_sql" do
|
93
|
-
subject(:described_method) { User.union(user, other_user).union.order(:id, name: :desc).to_union_sql }
|
94
|
-
|
95
|
-
it "Should append an 'ORDER BY' to the end of the union statements" do
|
96
|
-
expect(described_method).to match_regex(/^\(+.+\) UNION \(.+\) \) ORDER BY id, name DESC$/)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
context "when rendering with .to_sql" do
|
101
|
-
subject(:described_method) { User.union(user, other_user).union.order(:id, name: :desc).to_sql }
|
102
|
-
|
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\) users$/)
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
context "when a there are multiple union statements" do
|
109
|
-
let(:query_regex) { /(?<=\)\s(ORDER BY)) id/ }
|
110
|
-
|
111
|
-
it "should only append an order by to the very end of a union statements" do
|
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
|
-
|
118
|
-
index = query.index(query_regex)
|
119
|
-
expect(index).to be_truthy
|
120
|
-
expect(query[index..-1]).to eq(" id ASC, tags DESC")
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|
124
|
-
end
|
@@ -1,98 +0,0 @@
|
|
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
|