1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Pagination::Keyset::SimpleOrderBuilder do
let(:ordered_scope) { described_class.build(scope).first }
let(:order_object) { Gitlab::Pagination::Keyset::Order.extract_keyset_order_object(ordered_scope) }
let(:column_definition) { order_object.column_definitions.first }
subject(:sql_with_order) { ordered_scope.to_sql }
context 'when no order present' do
let(:scope) { Project.where(id: [1, 2, 3]) }
it 'orders by primary key' do
expect(sql_with_order).to end_with('ORDER BY "projects"."id" DESC')
end
it 'sets the column definition distinct and not nullable' do
expect(column_definition).to be_not_nullable
expect(column_definition).to be_distinct
end
context "when the order scope's model uses default_scope" do
let(:scope) do
model = Class.new(ApplicationRecord) do
self.table_name = 'events'
default_scope { reorder(nil) } # rubocop:disable Cop/DefaultScope
end
model.reorder(nil)
end
it 'orders by primary key' do
expect(sql_with_order).to end_with('ORDER BY "events"."id" DESC')
end
end
end
context 'when primary key order present' do
let(:scope) { Project.where(id: [1, 2, 3]).order(id: :asc) }
it 'orders by primary key without altering the direction' do
expect(sql_with_order).to end_with('ORDER BY "projects"."id" ASC')
end
end
context 'when ordered by other column' do
let(:scope) { Project.where(id: [1, 2, 3]).order(created_at: :asc) }
it 'adds extra primary key order as tie-breaker' do
expect(sql_with_order).to end_with('ORDER BY "projects"."created_at" ASC, "projects"."id" DESC')
end
it 'sets the column definition for created_at non-distinct and nullable' do
expect(column_definition.attribute_name).to eq('created_at')
expect(column_definition.nullable?).to eq(true) # be_nullable calls non_null? method for some reason
expect(column_definition).not_to be_distinct
end
end
context 'when ordered by two columns where the last one is the tie breaker' do
let(:scope) { Project.where(id: [1, 2, 3]).order(created_at: :asc, id: :asc) }
it 'preserves the order' do
expect(sql_with_order).to end_with('ORDER BY "projects"."created_at" ASC, "projects"."id" ASC')
end
end
context 'when non-nullable column is given' do
let(:scope) { Project.where(id: [1, 2, 3]).order(namespace_id: :asc, id: :asc) }
it 'sets the column definition for namespace_id non-distinct and non-nullable' do
expect(column_definition.attribute_name).to eq('namespace_id')
expect(column_definition).to be_not_nullable
expect(column_definition).not_to be_distinct
end
end
context 'when ordering by a column with the lower named function' do
let(:scope) { Project.where(id: [1, 2, 3]).order(Project.arel_table[:name].lower.desc) }
it 'sets the column definition for name' do
expect(column_definition.attribute_name).to eq('name')
expect(column_definition.column_expression.expressions.first.name).to eq('name')
expect(column_definition.column_expression.name).to eq('LOWER')
end
it 'adds extra primary key order as tie-breaker' do
expect(sql_with_order).to end_with('ORDER BY LOWER("projects"."name") DESC, "projects"."id" DESC')
end
end
context "NULLS order given as as an Arel node" do
context 'when NULLS LAST order is given without a tie-breaker' do
let(:scope) { Project.order(Project.arel_table[:created_at].asc.nulls_last) }
it 'sets the column definition for created_at appropriately' do
expect(column_definition.attribute_name).to eq('created_at')
end
it 'orders by primary key' do
expect(sql_with_order).to end_with('ORDER BY "projects"."created_at" ASC NULLS LAST, "projects"."id" DESC')
end
end
context 'when NULLS FIRST order is given with a tie-breaker' do
let(:scope) { Issue.order(Issue.arel_table[:relative_position].desc.nulls_first).order(id: :asc) }
it 'sets the column definition for created_at appropriately' do
expect(column_definition.attribute_name).to eq('relative_position')
end
it 'orders by the given primary key' do
expect(sql_with_order).to end_with('ORDER BY "issues"."relative_position" DESC NULLS FIRST, "issues"."id" ASC')
end
end
end
context 'return :unable_to_order symbol when order cannot be built' do
subject(:success) { described_class.build(scope).last }
context 'when raw SQL order is given' do
let(:scope) { Project.order('id DESC') }
it { is_expected.to eq(false) }
end
context 'when an invalid NULLS order is given' do
using RSpec::Parameterized::TableSyntax
where(:scope) do
[
lazy { Project.order(Arel.sql('projects.updated_at created_at Asc Nulls Last')) },
lazy { Project.order(Arel.sql('projects.created_at ZZZ NULLS FIRST')) },
lazy { Project.order(Arel.sql('projects.relative_position ASC NULLS LAST')) }
]
end
with_them do
it { is_expected.to eq(false) }
end
end
context 'when more than 2 columns are given for the order' do
let(:scope) { Project.order(created_at: :asc, updated_at: :desc, id: :asc) }
it { is_expected.to eq(false) }
end
end
end
|