Class: Gitlab::Pagination::Keyset::ColumnOrderDefinition

Inherits:
Object
  • Object
show all
Defined in:
lib/gitlab/pagination/keyset/column_order_definition.rb

Overview

This class stores information for one column (or SQL expression) which can be used in an ORDER BY SQL clasue. The goal of this class is to encapsulate all the metadata in one place which are needed to make keyset pagination work in a generalized way.

Arguments

**order expression** (Arel::Nodes::Node | String)

The actual SQL expression for the ORDER BY clause.

Examples:

# Arel column order definition
Project.arel_table[:id].asc # ORDER BY projects.id ASC

# Arel expression, calculated order definition
Arel::Nodes::NamedFunction.new("COALESCE", [Project.arel_table[:issue_count].asc, 0]).asc # ORDER BY COALESCE(projects.issue_count, 0)

# Another Arel expression
Arel::Nodes::Multiplication(Issue.arel_table[:weight], Issue.arel_table[:time_spent]).desc

# Raw string order definition
'issues.type DESC NULLS LAST'

column_expression (Arel::Nodes::Node | String)

Expression for the database column or an expression. This value will be used with logical operations (>, <, =, !=) when building the database query for the next page.

Examples:

# Arel column reference
Issue.arel_table[:title]

# Calculated value
Arel::Nodes::Multiplication(Issue.arel_table[:weight], Issue.arel_table[:time_spent])

attribute_name (String | Symbol)

An attribute on the loaded ActiveRecord model where the value can be obtained.

Examples:

# Simple attribute definition
attribute_name = :title

# Later on this attribute will be used like this:
my_record = Issue.find(x)
value = my_record[attribute_name] # reads data from the title column

# Calculated value based on an Arel or raw SQL expression

attribute_name = :lowercase_title

# `lowercase_title` is not is not a table column therefore we need to make sure it's available in the `SELECT` clause

my_record = Issue.select(:id, 'LOWER(title) as lowercase_title').last
value = my_record[:lowercase_title]

distinct

Boolean value.

Tells us whether the database column contains only distinct values. If the column is covered by a unique index then set to true.

nullable (:not_nullable | :nulls_last | :nulls_first)

Tells us whether the database column is nullable or not. This information can be obtained from the DB schema.

If the column is not nullable, set this attribute to :not_nullable.

If the column is nullable, then additional information is needed. Based on the ordering, the null values will show up at the top or at the bottom of the resultset.

Examples:

# Nulls are showing up at the top (for example: ORDER BY column ASC):
nullable = :nulls_first

# Nulls are showing up at the bottom (for example: ORDER BY column DESC):
nullable = :nulls_last

order_direction

:asc or :desc

Note: this is an optional attribute, the value will be inferred from the order_expression. Sometimes it's not possible to infer the order automatically. In this case an exception will be raised (when the query is executed). If the reverse order cannot be computed, it must be provided explicitly.

reversed_order_expression

The reversed version of the order_expression.

A ColumnOrderDefinition object is able to reverse itself which is used when paginating backwards. When a complex order_expression is provided (raw string), then reversing the order automatically is not possible. In this case an exception will be raised.

Example:

order_expression = Project.arel_table[:id].asc
reversed_order_expression = Project.arel_table[:id].desc

add_to_projections

Set to true if the column is not part of the queried table. (Not part of SELECT *)

Example:

- When the order is a calculated expression or the column is in another table (JOIN-ed)

If the add_to_projections is true, the query builder will automatically add the column to the SELECT values

**sql_type**

The SQL type of the column or SQL expression. This is an optional field which is only required when using the column with the InOperatorOptimization class.

Example: When the order expression is a calculated SQL expression.

{
  attribute_name: 'id_times_count',
  order_expression: Arel.sql('(id * count)').asc,
  sql_type: 'integer' # the SQL type here must match with the type of the produced data by the order_expression. Putting 'text' here would be incorrect.
}

Constant Summary collapse

REVERSED_ORDER_DIRECTIONS =
{ asc: :desc, desc: :asc }.freeze
REVERSED_NULL_POSITIONS =
{ nulls_first: :nulls_last, nulls_last: :nulls_first }.freeze
AREL_ORDER_CLASSES =
{ Arel::Nodes::Ascending => :asc, Arel::Nodes::Descending => :desc }.freeze
ALLOWED_NULLABLE_VALUES =
[:not_nullable, :nulls_first, :nulls_last].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attribute_name:, order_expression:, column_expression: nil, reversed_order_expression: nil, nullable: :not_nullable, distinct: true, order_direction: nil, sql_type: nil, add_to_projections: false) ⇒ ColumnOrderDefinition

rubocop: disable Metrics/ParameterLists


140
141
142
143
144
145
146
147
148
149
150
# File 'lib/gitlab/pagination/keyset/column_order_definition.rb', line 140

def initialize(attribute_name:, order_expression:, column_expression: nil, reversed_order_expression: nil, nullable: :not_nullable, distinct: true, order_direction: nil, sql_type: nil, add_to_projections: false)
  @attribute_name = attribute_name
  @order_expression = order_expression
  @column_expression = column_expression || calculate_column_expression(order_expression)
  @distinct = distinct
  @reversed_order_expression = reversed_order_expression || calculate_reversed_order(order_expression)
  @nullable = parse_nullable(nullable, distinct)
  @order_direction = parse_order_direction(order_expression, order_direction)
  @sql_type = sql_type
  @add_to_projections = add_to_projections
end

Instance Attribute Details

#add_to_projectionsObject (readonly)

Returns the value of attribute add_to_projections.


137
138
139
# File 'lib/gitlab/pagination/keyset/column_order_definition.rb', line 137

def add_to_projections
  @add_to_projections
end

#attribute_nameObject (readonly)

Returns the value of attribute attribute_name.


137
138
139
# File 'lib/gitlab/pagination/keyset/column_order_definition.rb', line 137

def attribute_name
  @attribute_name
end

#column_expressionObject (readonly)

Returns the value of attribute column_expression.


137
138
139
# File 'lib/gitlab/pagination/keyset/column_order_definition.rb', line 137

def column_expression
  @column_expression
end

#order_directionObject (readonly)

Returns the value of attribute order_direction.


137
138
139
# File 'lib/gitlab/pagination/keyset/column_order_definition.rb', line 137

def order_direction
  @order_direction
end

#order_expressionObject (readonly)

Returns the value of attribute order_expression.


137
138
139
# File 'lib/gitlab/pagination/keyset/column_order_definition.rb', line 137

def order_expression
  @order_expression
end

Instance Method Details

#ascending_order?Boolean

Returns:

  • (Boolean)

165
166
167
# File 'lib/gitlab/pagination/keyset/column_order_definition.rb', line 165

def ascending_order?
  order_direction == :asc
end

#descending_order?Boolean

Returns:

  • (Boolean)

169
170
171
# File 'lib/gitlab/pagination/keyset/column_order_definition.rb', line 169

def descending_order?
  order_direction == :desc
end

#distinct?Boolean

Returns:

  • (Boolean)

189
190
191
# File 'lib/gitlab/pagination/keyset/column_order_definition.rb', line 189

def distinct?
  distinct
end

#not_nullable?Boolean

Returns:

  • (Boolean)

181
182
183
# File 'lib/gitlab/pagination/keyset/column_order_definition.rb', line 181

def not_nullable?
  nullable == :not_nullable
end

#nullable?Boolean

Returns:

  • (Boolean)

185
186
187
# File 'lib/gitlab/pagination/keyset/column_order_definition.rb', line 185

def nullable?
  !not_nullable?
end

#nulls_first?Boolean

Returns:

  • (Boolean)

173
174
175
# File 'lib/gitlab/pagination/keyset/column_order_definition.rb', line 173

def nulls_first?
  nullable == :nulls_first
end

#nulls_last?Boolean

Returns:

  • (Boolean)

177
178
179
# File 'lib/gitlab/pagination/keyset/column_order_definition.rb', line 177

def nulls_last?
  nullable == :nulls_last
end

#order_direction_as_sql_stringObject


193
194
195
196
197
198
199
200
201
202
203
# File 'lib/gitlab/pagination/keyset/column_order_definition.rb', line 193

def order_direction_as_sql_string
  sql_string = ascending_order? ? +'ASC' : +'DESC'

  if nulls_first?
    sql_string << ' NULLS FIRST'
  elsif nulls_last?
    sql_string << ' NULLS LAST'
  end

  sql_string
end

#reverseObject

rubocop: enable Metrics/ParameterLists


153
154
155
156
157
158
159
160
161
162
163
# File 'lib/gitlab/pagination/keyset/column_order_definition.rb', line 153

def reverse
  self.class.new(
    attribute_name: attribute_name,
    column_expression: column_expression,
    order_expression: reversed_order_expression,
    reversed_order_expression: order_expression,
    nullable: not_nullable? ? :not_nullable : REVERSED_NULL_POSITIONS[nullable],
    distinct: distinct,
    order_direction: REVERSED_ORDER_DIRECTIONS[order_direction]
  )
end

#sql_typeObject


205
206
207
208
209
# File 'lib/gitlab/pagination/keyset/column_order_definition.rb', line 205

def sql_type
  raise Gitlab::Pagination::Keyset::SqlTypeMissingError.for_column(self) if @sql_type.nil?

  @sql_type
end