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
|
# frozen_string_literal: true
module Gitlab
module Pagination
module Keyset
# This class transforms the `order()` values from an Activerecord scope into a
# Gitlab::Pagination::Keyset::Order instance so the query later can be used in
# keyset pagination.
#
# Return values:
# [transformed_scope, true] # true indicates that the new scope was successfully built
# [orginal_scope, false] # false indicates that the order values are not supported in this class
class SimpleOrderBuilder
def self.build(scope)
new(scope: scope).build
end
def initialize(scope:)
@scope = scope
@order_values = scope.order_values
@model_class = scope.model
@arel_table = @model_class.arel_table
@primary_key = @model_class.primary_key
end
def build
order = if order_values.empty?
primary_key_descending_order
elsif Gitlab::Pagination::Keyset::Order.keyset_aware?(scope)
Gitlab::Pagination::Keyset::Order.extract_keyset_order_object(scope)
elsif ordered_by_primary_key?
primary_key_order
elsif ordered_by_other_column?
column_with_tie_breaker_order
elsif ordered_by_other_column_with_tie_breaker?
tie_breaker_attribute = order_values.second
tie_breaker_column_order = Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: model_class.primary_key,
order_expression: tie_breaker_attribute
)
column_with_tie_breaker_order(tie_breaker_column_order)
end
order ? [scope.reorder!(order), true] : [scope, false] # [scope, success]
end
private
attr_reader :scope, :order_values, :model_class, :arel_table, :primary_key
def primary_key_descending_order
Gitlab::Pagination::Keyset::Order.build([
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: model_class.primary_key,
order_expression: arel_table[primary_key].desc
)
])
end
def primary_key_order
Gitlab::Pagination::Keyset::Order.build([
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: model_class.primary_key,
order_expression: order_values.first
)
])
end
def column_with_tie_breaker_order(tie_breaker_column_order = default_tie_breaker_column_order)
order_expression = order_values.first
attribute_name = order_expression.expr.name
column_nullable = model_class.columns.find { |column| column.name == attribute_name }.null
nullable = if column_nullable && order_expression.is_a?(Arel::Nodes::Ascending)
:nulls_last
elsif column_nullable && order_expression.is_a?(Arel::Nodes::Descending)
:nulls_first
else
:not_nullable
end
Gitlab::Pagination::Keyset::Order.build([
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: attribute_name,
order_expression: order_expression,
nullable: nullable,
distinct: false
),
tie_breaker_column_order
])
end
def ordered_by_primary_key?
return unless order_values.one?
attribute = order_values.first.try(:expr)
return unless attribute
arel_table[primary_key].to_s == attribute.to_s
end
def ordered_by_other_column?
return unless order_values.one?
attribute = order_values.first.try(:expr)
return unless attribute
return unless attribute.try(:name)
model_class.column_names.include?(attribute.name.to_s)
end
def ordered_by_other_column_with_tie_breaker?
return unless order_values.size == 2
attribute = order_values.first.try(:expr)
tie_breaker_attribute = order_values.second.try(:expr)
return unless attribute
return unless tie_breaker_attribute
return unless attribute.respond_to?(:name)
model_class.column_names.include?(attribute.name.to_s) &&
arel_table[primary_key].to_s == tie_breaker_attribute.to_s
end
def default_tie_breaker_column_order
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: model_class.primary_key,
order_expression: arel_table[primary_key].desc
)
end
end
end
end
end
|