Welcome to mirror list, hosted at ThFree Co, Russian Federation.

simple_order_builder.rb « keyset « pagination « gitlab « lib - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 76d6bbadaa4a2ae82460c8e5fe338959fc7ec086 (plain)
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
# 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

          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