Commit 2c66d576 authored by Brett Walker's avatar Brett Walker

Ordering with NULLS LAST in keyset pagination

is now supported
parent 2c93bad0
......@@ -8,10 +8,15 @@
# https://coderwall.com/p/lkcaag/pagination-you-re-probably-doing-it-wrong
#
# It currently supports sorting on two columns, but the last column must
# be the primary key. For example
# be the primary key. If it's not already included, an order on the
# primary key will be added automatically, like `order(id: :desc)`
#
# Issue.order(created_at: :asc).order(:id)
# Issue.order(due_date: :asc).order(:id)
# Issue.order(due_date: :asc)
#
# You can also use `Gitlab::Database.nulls_last_order`:
#
# Issue.reorder(::Gitlab::Database.nulls_last_order('due_date', 'DESC'))
#
# It will tolerate non-attribute ordering, but only attributes determine the cursor.
# For example, this is legitimate:
......
......@@ -5,12 +5,22 @@ module Gitlab
module Connections
module Keyset
class OrderInfo
attr_reader :attribute_name, :sort_direction
def initialize(order_value)
@order_value = order_value
end
if order_value.is_a?(String)
tokens = order_value.downcase.split(' ')
def attribute_name
order_value.expr.name
unless tokens[-2..-1] == %w(nulls last) && tokens.count == 4
raise ArgumentError.new('Incorrect format for NULLS LAST')
end
@attribute_name = tokens.first
@sort_direction = tokens[1] == 'asc' ? :asc : :desc
else
@attribute_name = order_value.expr.name
@sort_direction = order_value.direction
end
end
def operator_for(before_or_after)
......@@ -22,10 +32,11 @@ module Gitlab
end
end
# Only allow specific node types. For example ignore String nodes
# Only allow specific node types
def self.build_order_list(relation)
order_list = relation.order_values.select do |value|
value.is_a?(Arel::Nodes::Ascending) || value.is_a?(Arel::Nodes::Descending)
value.is_a?(Arel::Nodes::Ascending) || value.is_a?(Arel::Nodes::Descending) ||
(value.is_a?(String) && value.downcase.end_with?('nulls last'))
end
order_list.map { |info| OrderInfo.new(info) }
......@@ -51,14 +62,6 @@ module Gitlab
raise ArgumentError.new("Last ordering field must be the primary key, `#{relation.primary_key}`")
end
end
private
attr_reader :order_value
def sort_direction
order_value.direction
end
end
end
end
......
......@@ -17,6 +17,26 @@ describe Gitlab::Graphql::Connections::Keyset::OrderInfo do
expect(order_list.last.operator_for(:after)).to eq '>'
end
end
context 'when order contains NULLS LAST' do
let(:relation) { Project.order(Arel.sql('projects.updated_at Asc Nulls Last')).order(:id) }
it 'does not ignore the SQL order' do
expect(order_list.count).to eq 2
expect(order_list.first.attribute_name).to eq 'projects.updated_at'
expect(order_list.first.operator_for(:after)).to eq '>'
expect(order_list.last.attribute_name).to eq 'id'
expect(order_list.last.operator_for(:after)).to eq '>'
end
end
context 'when order contains invalid formatted NULLS LAST ' do
let(:relation) { Project.order(Arel.sql('projects.updated_at created_at Asc Nulls Last')).order(:id) }
it 'raises an error' do
expect { order_list }.to raise_error(ArgumentError, 'Incorrect format for NULLS LAST')
end
end
end
describe '#validate_ordering' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment