active_record_extended 2.0.3 → 2.2.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 +9 -9
- data/lib/active_record_extended/active_record/relation_patch.rb +16 -4
- data/lib/active_record_extended/active_record.rb +6 -6
- data/lib/active_record_extended/arel/nodes.rb +1 -1
- data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +16 -12
- data/lib/active_record_extended/query_methods/any_of.rb +1 -1
- data/lib/active_record_extended/query_methods/either.rb +5 -3
- data/lib/active_record_extended/query_methods/unionize.rb +3 -3
- data/lib/active_record_extended/query_methods/where_chain.rb +5 -1
- data/lib/active_record_extended/query_methods/with_cte.rb +2 -2
- data/lib/active_record_extended/version.rb +1 -1
- metadata +11 -59
- data/lib/active_record_extended/patch/5_1/where_clause.rb +0 -11
- 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,51 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "spec_helper"
|
4
|
-
|
5
|
-
RSpec.describe "Active Record Window Function Query Methods" do
|
6
|
-
let!(:user_one) { User.create! }
|
7
|
-
let!(:user_two) { User.create! }
|
8
|
-
|
9
|
-
let!(:tag_one) { Tag.create!(user: user_one, tag_number: 1) }
|
10
|
-
let!(:tag_two) { Tag.create!(user: user_two, tag_number: 2) }
|
11
|
-
|
12
|
-
let!(:tag_three) { Tag.create!(user: user_one, tag_number: 3) }
|
13
|
-
let!(:tag_four) { Tag.create!(user: user_two, tag_number: 4) }
|
14
|
-
|
15
|
-
let(:tag_group1) { [tag_one, tag_three] }
|
16
|
-
let(:tag_group2) { [tag_two, tag_four] }
|
17
|
-
|
18
|
-
describe ".window_select" do
|
19
|
-
context "when using ROW_NUMBER() ordered in asc" do
|
20
|
-
let(:base_query) do
|
21
|
-
Tag.define_window(:w).partition_by(:user_id, order_by: :tag_number).select(:id)
|
22
|
-
end
|
23
|
-
|
24
|
-
it "should return tag_one with r_id 1 and tag_three with r_id 2" do
|
25
|
-
results = base_query.select_window(:row_number, over: :w, as: :r_id).group_by(&:id)
|
26
|
-
tag_group1.each.with_index { |tag, idx| expect(results[tag.id].first.r_id).to eq(idx + 1) }
|
27
|
-
end
|
28
|
-
|
29
|
-
it "should return tag_two with r_id 1 and tag_four with r_id 2" do
|
30
|
-
results = base_query.select_window(:row_number, over: :w, as: :r_id).group_by(&:id)
|
31
|
-
tag_group2.each.with_index { |tag, idx| expect(results[tag.id].first.r_id).to eq(idx + 1) }
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
context "when using ROW_NUMBER() ordered in desc" do
|
36
|
-
let(:base_query) do
|
37
|
-
Tag.define_window(:w).partition_by(:user_id, order_by: { tag_number: :desc }).select(:id)
|
38
|
-
end
|
39
|
-
|
40
|
-
it "should return tag_one with r_id 2 and tag_three with r_id 1" do
|
41
|
-
results = base_query.select_window(:row_number, over: :w, as: :r_id).group_by(&:id)
|
42
|
-
tag_group1.reverse_each.with_index { |tag, idx| expect(results[tag.id].first.r_id).to eq(idx + 1) }
|
43
|
-
end
|
44
|
-
|
45
|
-
it "should return tag_two with r_id 2 and tag_four with r_id 1" do
|
46
|
-
results = base_query.select_window(:row_number, over: :w, as: :r_id).group_by(&:id)
|
47
|
-
tag_group2.reverse_each.with_index { |tag, idx| expect(results[tag.id].first.r_id).to eq(idx + 1) }
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "spec_helper"
|
4
|
-
|
5
|
-
RSpec.describe "Active Record With CTE Query Methods" do
|
6
|
-
let!(:user_one) { User.create! }
|
7
|
-
let!(:user_two) { User.create! }
|
8
|
-
let!(:profile_one) { ProfileL.create!(user_id: user_one.id, likes: 200) }
|
9
|
-
let!(:profile_two) { ProfileL.create!(user_id: user_two.id, likes: 500) }
|
10
|
-
|
11
|
-
describe ".with/1" do
|
12
|
-
context "when using as a standalone query" do
|
13
|
-
it "should only return a person with less than 300 likes" do
|
14
|
-
query = User.with(profile: ProfileL.where("likes < 300"))
|
15
|
-
.joins("JOIN profile ON profile.id = users.id")
|
16
|
-
|
17
|
-
expect(query).to match_array([user_one])
|
18
|
-
end
|
19
|
-
|
20
|
-
it "should return anyone with likes greater than or equal to 200" do
|
21
|
-
query = User.with(profile: ProfileL.where("likes >= 200"))
|
22
|
-
.joins("JOIN profile ON profile.id = users.id")
|
23
|
-
|
24
|
-
expect(query).to match_array([user_one, user_two])
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
context "when merging in query" do
|
29
|
-
let!(:version_one) { VersionControl.create!(versionable: profile_one, source: { help: "me" }) }
|
30
|
-
let!(:version_two) { VersionControl.create!(versionable: profile_two, source: { help: "no one" }) }
|
31
|
-
|
32
|
-
it "will maintain the CTE table when merging into existing AR queries" do
|
33
|
-
sub_query = ProfileL.with(version_controls: VersionControl.where.contains(source: { help: "me" }))
|
34
|
-
query = User.joins(profile_l: :version).merge(sub_query)
|
35
|
-
|
36
|
-
expect(query).to match_array([user_one])
|
37
|
-
end
|
38
|
-
|
39
|
-
it "should contain a unique list of ordered CTE keys when merging in multiple children" do
|
40
|
-
x = User.with(profile: ProfileL.where("likes < 300"))
|
41
|
-
y = User.with(profile: ProfileL.where("likes > 400"))
|
42
|
-
z = y.merge(x).joins("JOIN profile ON profile.id = users.id") # Y should reject X's CTE (FIFO)
|
43
|
-
query = User.with(my_profile: z).joins("JOIN my_profile ON my_profile.id = users.id")
|
44
|
-
|
45
|
-
expect(query.cte.with_keys).to eq([:profile, :my_profile])
|
46
|
-
expect(query).to match_array([user_two])
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
data/spec/spec_helper.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "simplecov"
|
4
|
-
SimpleCov.start
|
5
|
-
|
6
|
-
require "active_record_extended"
|
7
|
-
|
8
|
-
unless ENV["DATABASE_URL"]
|
9
|
-
require "dotenv"
|
10
|
-
Dotenv.load
|
11
|
-
end
|
12
|
-
|
13
|
-
ActiveRecord::Base.establish_connection(ENV["DATABASE_URL"])
|
14
|
-
|
15
|
-
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require File.expand_path(f) }
|
16
|
-
Dir["#{File.dirname(__FILE__)}/**/*examples.rb"].sort.each { |f| require f }
|
17
|
-
|
18
|
-
RSpec.configure do |config|
|
19
|
-
# Enable flags like --only-failures and --next-failure
|
20
|
-
config.example_status_persistence_file_path = ".rspec_status"
|
21
|
-
|
22
|
-
# Disable RSpec exposing methods globally on `Module` and `main`
|
23
|
-
config.disable_monkey_patching!
|
24
|
-
|
25
|
-
config.expect_with :rspec do |c|
|
26
|
-
c.syntax = :expect
|
27
|
-
end
|
28
|
-
end
|
@@ -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
|