unreliable 1.0.1 → 1.0.2
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/CHANGELOG.md +7 -0
- data/lib/unreliable/build_order.rb +13 -2
- data/lib/unreliable/version.rb +1 -1
- data/spec/computed_order_spec.rb +63 -0
- data/spec/select_distinct_raw_spec.rb +58 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 03cacbb1f651eeac84c72fe0534624779978b5c954f9e0612c89784555c1b0bb
|
|
4
|
+
data.tar.gz: b2157f68e867d7ba3ced7b0bb4bc4d0dbad964cfdbe229d915a8080e84a2fd8c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e037706c73288b463555844acbcf70ba73c03416d28252c32c2ceb26280bda10898d55270fbde8c650fc60bad1cf21f1a5ef049d6cad8d7c0cd15a4ca01c7cfa
|
|
7
|
+
data.tar.gz: f9fd9946c411f6691e645626d186ec79a856f7007aedd2becfed8d6bfac25ad04f1e319fd00b26ad1ecb3c3b67870a88c3bd4b102ff8b05b323fcfe0fc620d1a
|
data/CHANGELOG.md
CHANGED
|
@@ -37,13 +37,23 @@ module Unreliable
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def distinct_on_postgres?(adapter_name)
|
|
40
|
-
|
|
40
|
+
distinct_query? && adapter_name == "PostgreSQL"
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def distinct_on_sqlserver?(adapter_name)
|
|
44
44
|
# SQL Server rejects ORDER BY expressions not in the select list when
|
|
45
45
|
# DISTINCT is used, so we don't append NEWID() to DISTINCT queries.
|
|
46
|
-
|
|
46
|
+
distinct_query? && adapter_name == "SQLServer"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def distinct_query?
|
|
50
|
+
# `.distinct` sets distinct_value, but a raw "DISTINCT ..." / "DISTINCT ON (...)"
|
|
51
|
+
# written into `.select` does not. SQL only allows a statement-level DISTINCT as
|
|
52
|
+
# the leading keyword of the select clause, so we detect it at the start of a
|
|
53
|
+
# select fragment (an aggregate's inner `count(DISTINCT x)` is never at the start).
|
|
54
|
+
# A false positive only forgoes randomization; a miss would append an ORDER BY that
|
|
55
|
+
# PostgreSQL and SQL Server reject for DISTINCT queries.
|
|
56
|
+
distinct_value || select_values.any? { |value| value.to_s.match?(/\A\s*DISTINCT\b/i) }
|
|
47
57
|
end
|
|
48
58
|
|
|
49
59
|
def from_only_internal_metadata?(arel)
|
|
@@ -83,6 +93,7 @@ module Unreliable
|
|
|
83
93
|
arel.orders
|
|
84
94
|
.grep(Arel::Nodes::Ordering) # Don't try to parse textual orders
|
|
85
95
|
.map(&:expr)
|
|
96
|
+
.grep(Arel::Attributes::Attribute) # Skip computed expressions
|
|
86
97
|
.select { |expr| expr.relation.name == from_table_name }
|
|
87
98
|
.map(&:name)
|
|
88
99
|
.map(&:to_s) # In Rails < 5.2, the order column names are symbols; >= 5.2, strings
|
data/lib/unreliable/version.rb
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# An order can be a directional node (Ascending/Descending) wrapping a computed
|
|
4
|
+
# expression rather than a plain column. Those wrappers survive the
|
|
5
|
+
# Arel::Nodes::Ordering grep in order_columns, but their .expr is not an
|
|
6
|
+
# Arel::Attributes::Attribute and has no .relation. The wrapped expr can be any
|
|
7
|
+
# of several node classes -- a SqlLiteral, a NamedFunction, an aggregate, etc.
|
|
8
|
+
# None of them can cover a primary key, so the query must be randomized (and
|
|
9
|
+
# must not raise).
|
|
10
|
+
#
|
|
11
|
+
# Note: a plain string order like .order("name DESC") does NOT reach this code;
|
|
12
|
+
# it becomes a bare SqlLiteral that the Ordering grep drops (see
|
|
13
|
+
# textual_order_spec.rb). Only a *directional* node wrapping a non-column does.
|
|
14
|
+
|
|
15
|
+
RSpec.describe "computed order" do
|
|
16
|
+
it "randomly selects when ordered by a string-key hash alias" do
|
|
17
|
+
# No explicit Arel -- the idiomatic hash form. "name_len" isn't a real column,
|
|
18
|
+
# so it becomes a quoted SqlLiteral identifier wrapped in a Descending node.
|
|
19
|
+
expect(Cat.order("name_len" => :desc).to_sql).to end_with(
|
|
20
|
+
adapter_rand('ORDER BY "name_len" DESC, RANDOM()')
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "randomly selects when ordered by a SqlLiteral with a direction" do
|
|
25
|
+
expect(Cat.order(Arel.sql("name_len").desc).to_sql).to end_with(
|
|
26
|
+
adapter_rand("ORDER BY name_len DESC, RANDOM()")
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "randomly selects when ordered by a function expression (NamedFunction)" do
|
|
31
|
+
expect(Cat.order(Cat.arel_table[:name].lower.desc).to_sql).to end_with(
|
|
32
|
+
adapter_rand('ORDER BY LOWER("cats"."name") DESC, RANDOM()')
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "randomly selects when ordered by an aggregate expression (Count)" do
|
|
37
|
+
expect(Cat.order(Cat.arel_table[:id].count.desc).to_sql).to end_with(
|
|
38
|
+
adapter_rand('ORDER BY COUNT("cats"."id") DESC, RANDOM()')
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "randomly selects when ordered by an arithmetic expression (Grouping)" do
|
|
43
|
+
# (attr + 1) is an Arel::Nodes::Grouping, which does not mix in .desc on
|
|
44
|
+
# ActiveRecord 5.2, so wrap it in a Descending node directly.
|
|
45
|
+
expect(Cat.order(Arel::Nodes::Descending.new(Cat.arel_table[:id] + 1)).to_sql).to end_with(
|
|
46
|
+
adapter_rand('ORDER BY ("cats"."id" + 1) DESC, RANDOM()')
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "randomly selects on a composite-PK table ordered by a computed expression" do
|
|
51
|
+
expect(Shelf.order(Arel.sql("LENGTH(contents)").asc).to_sql).to end_with(
|
|
52
|
+
adapter_rand("ORDER BY LENGTH(contents) ASC, RANDOM()")
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "does not randomize when the PK is covered even though a computed order is also present" do
|
|
57
|
+
# order_columns must still recognize the real "id" attribute after skipping
|
|
58
|
+
# the computed expression, so the covered-PK short-circuit still applies.
|
|
59
|
+
expect(Cat.order(:id).order(Arel.sql("LENGTH(name)").desc).to_sql).to end_with(
|
|
60
|
+
adapter_rand('ORDER BY "cats"."id" ASC, LENGTH(name) DESC')
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# DISTINCT written as a raw string in .select (rather than via the .distinct
|
|
4
|
+
# relation method) does not set distinct_value. Without detecting it, the gem
|
|
5
|
+
# appends ORDER BY RANDOM(), which PostgreSQL and SQL Server reject:
|
|
6
|
+
#
|
|
7
|
+
# for SELECT DISTINCT, ORDER BY expressions must appear in select list
|
|
8
|
+
# SELECT DISTINCT ON expressions must match initial ORDER BY expressions
|
|
9
|
+
#
|
|
10
|
+
# On MySQL and SQLite the appended random order is accepted, so -- as with the
|
|
11
|
+
# .distinct relation method -- those adapters still get randomized.
|
|
12
|
+
|
|
13
|
+
RSpec.describe "raw DISTINCT in select" do
|
|
14
|
+
it "does not append a randomizing order to a raw DISTINCT select on postgres/sqlserver" do
|
|
15
|
+
expect(Cat.select("DISTINCT name").to_sql).to end_with(
|
|
16
|
+
case UnreliableTest.find_adapter
|
|
17
|
+
when "postgresql", "sqlserver"
|
|
18
|
+
adapter_quotes('DISTINCT name FROM "cats"')
|
|
19
|
+
else
|
|
20
|
+
adapter_rand("ORDER BY RANDOM()")
|
|
21
|
+
end
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "does not append a randomizing order to a raw DISTINCT ON select on postgres",
|
|
26
|
+
skip: ((UnreliableTest.find_adapter == "postgresql") ? false : "DISTINCT ON is PostgreSQL-only syntax") do
|
|
27
|
+
expect(Cat.select("DISTINCT ON (name) *").to_sql).to end_with(
|
|
28
|
+
adapter_rand('SELECT DISTINCT ON (name) * FROM "cats"')
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "still detects DISTINCT written in lowercase" do
|
|
33
|
+
expect(Cat.select("distinct name").to_sql).to end_with(
|
|
34
|
+
case UnreliableTest.find_adapter
|
|
35
|
+
when "postgresql", "sqlserver"
|
|
36
|
+
adapter_quotes('distinct name FROM "cats"')
|
|
37
|
+
else
|
|
38
|
+
adapter_rand("ORDER BY RANDOM()")
|
|
39
|
+
end
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "does not treat an aggregate's inner DISTINCT as a statement-level DISTINCT" do
|
|
44
|
+
# count(DISTINCT ...) is not a SELECT DISTINCT, so the query must still be randomized.
|
|
45
|
+
expect(Cat.select("count(DISTINCT name) AS n").to_sql).to end_with(
|
|
46
|
+
adapter_rand("ORDER BY RANDOM()")
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "executes a raw DISTINCT select without a server error" do
|
|
51
|
+
expect { Cat.select("DISTINCT name").load }.not_to raise_error
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "executes a raw DISTINCT ON select without a server error",
|
|
55
|
+
skip: ((UnreliableTest.find_adapter == "postgresql") ? false : "DISTINCT ON is PostgreSQL-only syntax") do
|
|
56
|
+
expect { Cat.select("DISTINCT ON (name) *").load }.not_to raise_error
|
|
57
|
+
end
|
|
58
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: unreliable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- James McCarthy
|
|
@@ -69,6 +69,7 @@ files:
|
|
|
69
69
|
- lib/unreliable/railtie.rb
|
|
70
70
|
- lib/unreliable/version.rb
|
|
71
71
|
- spec/adapter_option_spec.rb
|
|
72
|
+
- spec/computed_order_spec.rb
|
|
72
73
|
- spec/config_disable_nesting_spec.rb
|
|
73
74
|
- spec/config_thread_safety_spec.rb
|
|
74
75
|
- spec/env_spec.rb
|
|
@@ -95,6 +96,7 @@ files:
|
|
|
95
96
|
- spec/model_update_arel_10_spec.rb
|
|
96
97
|
- spec/pluck_exists_aggregate_spec.rb
|
|
97
98
|
- spec/railtie_spec.rb
|
|
99
|
+
- spec/select_distinct_raw_spec.rb
|
|
98
100
|
- spec/spec_helper.rb
|
|
99
101
|
- spec/textual_order_raw_spec.rb
|
|
100
102
|
- spec/textual_order_spec.rb
|
|
@@ -119,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
119
121
|
- !ruby/object:Gem::Version
|
|
120
122
|
version: '0'
|
|
121
123
|
requirements: []
|
|
122
|
-
rubygems_version: 4.0.
|
|
124
|
+
rubygems_version: 4.0.16
|
|
123
125
|
specification_version: 4
|
|
124
126
|
summary: For ActiveRecord tests, surface ambiguous-ordering bugs
|
|
125
127
|
test_files: []
|