diff options
Diffstat (limited to 'lib/gitlab/graphql/pagination/keyset')
-rw-r--r-- | lib/gitlab/graphql/pagination/keyset/connection.rb | 3 | ||||
-rw-r--r-- | lib/gitlab/graphql/pagination/keyset/last_items.rb | 57 | ||||
-rw-r--r-- | lib/gitlab/graphql/pagination/keyset/order_info.rb | 16 |
3 files changed, 73 insertions, 3 deletions
diff --git a/lib/gitlab/graphql/pagination/keyset/connection.rb b/lib/gitlab/graphql/pagination/keyset/connection.rb index 17cd22d38ad..252f6371765 100644 --- a/lib/gitlab/graphql/pagination/keyset/connection.rb +++ b/lib/gitlab/graphql/pagination/keyset/connection.rb @@ -110,8 +110,7 @@ module Gitlab end if last - # grab one more than we need - paginated_nodes = sliced_nodes.last(limit_value + 1) + paginated_nodes = LastItems.take_items(sliced_nodes, limit_value + 1) # there is an extra node, so there is a previous page @has_previous_page = paginated_nodes.count > limit_value diff --git a/lib/gitlab/graphql/pagination/keyset/last_items.rb b/lib/gitlab/graphql/pagination/keyset/last_items.rb new file mode 100644 index 00000000000..45bf15236c1 --- /dev/null +++ b/lib/gitlab/graphql/pagination/keyset/last_items.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module Gitlab + module Graphql + module Pagination + module Keyset + # This class handles the last(N) ActiveRecord call even if a special ORDER BY configuration is present. + # For the last(N) call, ActiveRecord calls reverse_order, however for some cases it raises + # ActiveRecord::IrreversibleOrderError error. + class LastItems + # rubocop: disable CodeReuse/ActiveRecord + def self.take_items(scope, count) + if custom_order = lookup_custom_reverse_order(scope.order_values) + items = scope.reorder(*custom_order).first(count) # returns a single record when count is nil + items.is_a?(Array) ? items.reverse : items + else + scope.last(count) + end + end + # rubocop: enable CodeReuse/ActiveRecord + + # Detect special ordering and provide the reversed order + def self.lookup_custom_reverse_order(order_values) + if ordering_by_merged_at_and_mr_id_desc?(order_values) + [ + Gitlab::Database.nulls_first_order('merge_request_metrics.merged_at', 'ASC'), # reversing the order + MergeRequest.arel_table[:id].asc + ] + elsif ordering_by_merged_at_and_mr_id_asc?(order_values) + [ + Gitlab::Database.nulls_first_order('merge_request_metrics.merged_at', 'DESC'), + MergeRequest.arel_table[:id].asc + ] + end + end + + def self.ordering_by_merged_at_and_mr_id_desc?(order_values) + order_values.size == 2 && + order_values.first.to_s == Gitlab::Database.nulls_last_order('merge_request_metrics.merged_at', 'DESC') && + order_values.last.is_a?(Arel::Nodes::Descending) && + order_values.last.to_sql == MergeRequest.arel_table[:id].desc.to_sql + end + + def self.ordering_by_merged_at_and_mr_id_asc?(order_values) + order_values.size == 2 && + order_values.first.to_s == Gitlab::Database.nulls_last_order('merge_request_metrics.merged_at', 'ASC') && + order_values.last.is_a?(Arel::Nodes::Descending) && + order_values.last.to_sql == MergeRequest.arel_table[:id].desc.to_sql + end + + private_class_method :ordering_by_merged_at_and_mr_id_desc? + private_class_method :ordering_by_merged_at_and_mr_id_asc? + end + end + end + end +end diff --git a/lib/gitlab/graphql/pagination/keyset/order_info.rb b/lib/gitlab/graphql/pagination/keyset/order_info.rb index f54695ddb9a..f3ce3a10703 100644 --- a/lib/gitlab/graphql/pagination/keyset/order_info.rb +++ b/lib/gitlab/graphql/pagination/keyset/order_info.rb @@ -94,6 +94,10 @@ module Gitlab [order_value.expr.expressions[0].name.to_s, order_value.direction, order_value.expr] elsif ordering_by_similarity?(order_value) ['similarity', order_value.direction, order_value.expr] + elsif ordering_by_case?(order_value) + ['case_order_value', order_value.direction, order_value.expr] + elsif ordering_by_array_position?(order_value) + ['array_position', order_value.direction, order_value.expr] else [order_value.expr.name, order_value.direction, nil] end @@ -104,9 +108,19 @@ module Gitlab order_value.expr.is_a?(Arel::Nodes::NamedFunction) && order_value.expr&.name&.downcase == 'lower' end + # determine if ordering using ARRAY_POSITION, eg. "ORDER BY ARRAY_POSITION(Array[4,3,1,2]::smallint, state)" + def ordering_by_array_position?(order_value) + order_value.expr.is_a?(Arel::Nodes::NamedFunction) && order_value.expr&.name&.downcase == 'array_position' + end + # determine if ordering using SIMILARITY scoring based on Gitlab::Database::SimilarityScore def ordering_by_similarity?(order_value) - order_value.to_sql.match?(/SIMILARITY\(.+\*/) + Gitlab::Database::SimilarityScore.order_by_similarity?(order_value) + end + + # determine if ordering using CASE + def ordering_by_case?(order_value) + order_value.expr.is_a?(Arel::Nodes::Case) end end end |