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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gitlab/pagination/keyset')
-rw-r--r--lib/gitlab/pagination/keyset/column_order_definition.rb25
-rw-r--r--lib/gitlab/pagination/keyset/in_operator_optimization/column_data.rb19
-rw-r--r--lib/gitlab/pagination/keyset/in_operator_optimization/order_by_column_data.rb37
-rw-r--r--lib/gitlab/pagination/keyset/in_operator_optimization/order_by_columns.rb6
-rw-r--r--lib/gitlab/pagination/keyset/in_operator_optimization/query_builder.rb4
-rw-r--r--lib/gitlab/pagination/keyset/in_operator_optimization/strategies/order_values_loader_strategy.rb15
-rw-r--r--lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy.rb16
-rw-r--r--lib/gitlab/pagination/keyset/sql_type_missing_error.rb19
8 files changed, 127 insertions, 14 deletions
diff --git a/lib/gitlab/pagination/keyset/column_order_definition.rb b/lib/gitlab/pagination/keyset/column_order_definition.rb
index 2b968c4253f..302e7b406b1 100644
--- a/lib/gitlab/pagination/keyset/column_order_definition.rb
+++ b/lib/gitlab/pagination/keyset/column_order_definition.rb
@@ -114,6 +114,20 @@ module Gitlab
# - 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.
+ # }
+ #
class ColumnOrderDefinition
REVERSED_ORDER_DIRECTIONS = { asc: :desc, desc: :asc }.freeze
REVERSED_NULL_POSITIONS = { nulls_first: :nulls_last, nulls_last: :nulls_first }.freeze
@@ -122,7 +136,8 @@ module Gitlab
attr_reader :attribute_name, :column_expression, :order_expression, :add_to_projections, :order_direction
- def initialize(attribute_name:, order_expression:, column_expression: nil, reversed_order_expression: nil, nullable: :not_nullable, distinct: true, order_direction: nil, add_to_projections: false)
+ # rubocop: disable Metrics/ParameterLists
+ 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)
@@ -130,8 +145,10 @@ module Gitlab
@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
+ # rubocop: enable Metrics/ParameterLists
def reverse
self.class.new(
@@ -185,6 +202,12 @@ module Gitlab
sql_string
end
+ def sql_type
+ raise Gitlab::Pagination::Keyset::SqlTypeMissingError.for_column(self) if @sql_type.nil?
+
+ @sql_type
+ end
+
private
attr_reader :reversed_order_expression, :nullable, :distinct
diff --git a/lib/gitlab/pagination/keyset/in_operator_optimization/column_data.rb b/lib/gitlab/pagination/keyset/in_operator_optimization/column_data.rb
index 3f620f74eca..93b28661bb0 100644
--- a/lib/gitlab/pagination/keyset/in_operator_optimization/column_data.rb
+++ b/lib/gitlab/pagination/keyset/in_operator_optimization/column_data.rb
@@ -4,23 +4,35 @@ module Gitlab
module Pagination
module Keyset
module InOperatorOptimization
+ # This class is used for wrapping an Arel column with
+ # convenient helper methods in order to make the query
+ # building for the InOperatorOptimization a bit cleaner.
class ColumnData
attr_reader :original_column_name, :as, :arel_table
- def initialize(original_column_name, as, arel_table)
- @original_column_name = original_column_name.to_s
+ # column - name of the DB column
+ # as - custom alias for the column
+ # arel_table - relation where the column is located
+ def initialize(column, as, arel_table)
+ @original_column_name = column
@as = as.to_s
@arel_table = arel_table
end
+ # Generates: `issues.name AS my_alias`
def projection
arel_column.as(as)
end
+ # Generates: issues.name`
def arel_column
arel_table[original_column_name]
end
+ # overridden in OrderByColumnData class
+ alias_method :column_expression, :arel_column
+
+ # Generates: `issues.my_alias`
def arel_column_as
arel_table[as]
end
@@ -29,8 +41,9 @@ module Gitlab
"#{arel_table.name}_#{original_column_name}_array"
end
+ # Generates: SELECT ARRAY_AGG(...) AS issues_name_array
def array_aggregated_column
- Arel::Nodes::NamedFunction.new('ARRAY_AGG', [arel_column]).as(array_aggregated_column_name)
+ Arel::Nodes::NamedFunction.new('ARRAY_AGG', [column_expression]).as(array_aggregated_column_name)
end
end
end
diff --git a/lib/gitlab/pagination/keyset/in_operator_optimization/order_by_column_data.rb b/lib/gitlab/pagination/keyset/in_operator_optimization/order_by_column_data.rb
new file mode 100644
index 00000000000..9cb1ba1542d
--- /dev/null
+++ b/lib/gitlab/pagination/keyset/in_operator_optimization/order_by_column_data.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Pagination
+ module Keyset
+ module InOperatorOptimization
+ class OrderByColumnData < ColumnData
+ extend ::Gitlab::Utils::Override
+
+ attr_reader :column
+
+ # column - a ColumnOrderDefinition object
+ # as - custom alias for the column
+ # arel_table - relation where the column is located
+ def initialize(column, as, arel_table)
+ super(column.attribute_name.to_s, as, arel_table)
+ @column = column
+ end
+
+ override :arel_column
+ def arel_column
+ column.column_expression
+ end
+
+ override :column_expression
+ def column_expression
+ arel_table[original_column_name]
+ end
+
+ def column_for_projection
+ column.column_expression.as(original_column_name)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/pagination/keyset/in_operator_optimization/order_by_columns.rb b/lib/gitlab/pagination/keyset/in_operator_optimization/order_by_columns.rb
index d8c69a74e6b..d6513114d08 100644
--- a/lib/gitlab/pagination/keyset/in_operator_optimization/order_by_columns.rb
+++ b/lib/gitlab/pagination/keyset/in_operator_optimization/order_by_columns.rb
@@ -9,16 +9,16 @@ module Gitlab
# This class exposes collection methods for the order by columns
#
- # Example: by modelling the `issues.created_at ASC, issues.id ASC` ORDER BY
+ # Example: by modeling the `issues.created_at ASC, issues.id ASC` ORDER BY
# SQL clause, this class will receive two ColumnOrderDefinition objects
def initialize(columns, arel_table)
@columns = columns.map do |column|
- ColumnData.new(column.attribute_name, "order_by_columns_#{column.attribute_name}", arel_table)
+ OrderByColumnData.new(column, "order_by_columns_#{column.attribute_name}", arel_table)
end
end
def arel_columns
- columns.map(&:arel_column)
+ columns.map(&:column_for_projection)
end
def array_aggregated_columns
diff --git a/lib/gitlab/pagination/keyset/in_operator_optimization/query_builder.rb b/lib/gitlab/pagination/keyset/in_operator_optimization/query_builder.rb
index 53faf8469f2..065a3a0cf20 100644
--- a/lib/gitlab/pagination/keyset/in_operator_optimization/query_builder.rb
+++ b/lib/gitlab/pagination/keyset/in_operator_optimization/query_builder.rb
@@ -120,7 +120,7 @@ module Gitlab
.from(array_cte)
.join(Arel.sql("LEFT JOIN LATERAL (#{initial_keyset_query.to_sql}) #{table_name} ON TRUE"))
- order_by_columns.each { |column| q.where(column.arel_column.not_eq(nil)) }
+ order_by_columns.each { |column| q.where(column.column_expression.not_eq(nil)) }
q.as('array_scope_lateral_query')
end
@@ -231,7 +231,7 @@ module Gitlab
order
.apply_cursor_conditions(keyset_scope, cursor_values, use_union_optimization: true)
- .reselect(*order_by_columns.arel_columns)
+ .reselect(*order_by_columns.map(&:column_for_projection))
.limit(1)
end
diff --git a/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/order_values_loader_strategy.rb b/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/order_values_loader_strategy.rb
index fc2b56048f6..932aa0c2d28 100644
--- a/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/order_values_loader_strategy.rb
+++ b/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/order_values_loader_strategy.rb
@@ -12,11 +12,7 @@ module Gitlab
end
def initializer_columns
- order_by_columns.map do |column|
- column_name = column.original_column_name.to_s
- type = model.columns_hash[column_name].sql_type
- "NULL::#{type} AS #{column_name}"
- end
+ order_by_columns.map { |column_data| null_with_type_cast(column_data) }
end
def columns
@@ -30,6 +26,15 @@ module Gitlab
private
attr_reader :model, :order_by_columns
+
+ def null_with_type_cast(column_data)
+ column_name = column_data.original_column_name.to_s
+ active_record_column = model.columns_hash[column_name]
+
+ type = active_record_column ? active_record_column.sql_type : column_data.column.sql_type
+
+ "NULL::#{type} AS #{column_name}"
+ end
end
end
end
diff --git a/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy.rb b/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy.rb
index b12c33d6e51..51f38c1da58 100644
--- a/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy.rb
+++ b/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy.rb
@@ -9,6 +9,8 @@ module Gitlab
RECORDS_COLUMN = 'records'
def initialize(finder_query, model, order_by_columns)
+ verify_order_by_attributes_on_model!(model, order_by_columns)
+
@finder_query = finder_query
@order_by_columns = order_by_columns
@table_name = model.table_name
@@ -34,6 +36,20 @@ module Gitlab
private
attr_reader :finder_query, :order_by_columns, :table_name
+
+ def verify_order_by_attributes_on_model!(model, order_by_columns)
+ order_by_columns.map(&:column).each do |column|
+ unless model.columns_hash[column.attribute_name.to_s]
+ text = <<~TEXT
+ The "RecordLoaderStrategy" does not support the following ORDER BY column because
+ it's not available on the \"#{model.table_name}\" table: #{column.attribute_name}
+
+ Omit the "finder_query" parameter to use the "OrderValuesLoaderStrategy".
+ TEXT
+ raise text
+ end
+ end
+ end
end
end
end
diff --git a/lib/gitlab/pagination/keyset/sql_type_missing_error.rb b/lib/gitlab/pagination/keyset/sql_type_missing_error.rb
new file mode 100644
index 00000000000..0525ae13e9c
--- /dev/null
+++ b/lib/gitlab/pagination/keyset/sql_type_missing_error.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+module Gitlab
+ module Pagination
+ module Keyset
+ class SqlTypeMissingError < StandardError
+ def self.for_column(column)
+ message = <<~TEXT
+ The "sql_type" attribute is not set for the following column definition:
+ #{column.attribute_name}
+
+ See the ColumnOrderDefinition class for more context.
+ TEXT
+
+ new(message)
+ end
+ end
+ end
+ end
+end