Commit 4b99dd01 authored by Steve Abrams's avatar Steve Abrams

Refactor package scopes to use arel nodes

Refactor package scopes that sort by a joined
table to use arel nodes to enable keyset pagination.
parent c3175cb4
......@@ -8,6 +8,23 @@ class Packages::Package < ApplicationRecord
DISPLAYABLE_STATUSES = [:default, :error].freeze
INSTALLABLE_STATUSES = [:default].freeze
enum package_type: {
maven: 1,
npm: 2,
conan: 3,
nuget: 4,
pypi: 5,
composer: 6,
generic: 7,
golang: 8,
debian: 9,
rubygems: 10,
helm: 11,
terraform_module: 12
}
enum status: { default: 0, hidden: 1, processing: 2, error: 3 }
belongs_to :project
belongs_to :creator, class_name: 'User'
......@@ -72,12 +89,6 @@ class Packages::Package < ApplicationRecord
if: :debian_package?
validate :forbidden_debian_changes, if: :debian?
enum package_type: { maven: 1, npm: 2, conan: 3, nuget: 4, pypi: 5,
composer: 6, generic: 7, golang: 8, debian: 9,
rubygems: 10, helm: 11, terraform_module: 12 }
enum status: { default: 0, hidden: 1, processing: 2, error: 3 }
scope :for_projects, ->(project_ids) { where(project_id: project_ids) }
scope :with_name, ->(name) { where(name: name) }
scope :with_name_like, ->(name) { where(arel_table[:name].matches(name)) }
......@@ -133,10 +144,28 @@ class Packages::Package < ApplicationRecord
scope :order_type_desc, -> { reorder(package_type: :desc) }
scope :order_project_name, -> { joins(:project).reorder('projects.name ASC') }
scope :order_project_name_desc, -> { joins(:project).reorder('projects.name DESC') }
scope :order_project_path, -> { joins(:project).reorder('projects.path ASC, id ASC') }
scope :order_project_path_desc, -> { joins(:project).reorder('projects.path DESC, id DESC') }
scope :order_by_package_file, -> { joins(:package_files).order('packages_package_files.created_at ASC') }
scope :order_project_path, -> do
if Feature.enabled?(:arel_package_scopes)
keyset_order = keyset_pagination_order(join_class: Project, column_name: :path, direction: :asc)
joins(:project).reorder(keyset_order)
else
joins(:project).reorder('projects.path ASC, id ASC')
end
end
scope :order_project_path_desc, -> do
if Feature.enabled?(:arel_package_scopes)
keyset_order = keyset_pagination_order(join_class: Project, column_name: :path, direction: :desc)
joins(:project).reorder(keyset_order)
else
joins(:project).reorder('projects.path DESC, id DESC')
end
end
after_commit :update_composer_cache, on: :destroy, if: -> { composer? }
def self.only_maven_packages_with_path(path, use_cte: false)
......@@ -196,6 +225,32 @@ class Packages::Package < ApplicationRecord
end
end
def self.keyset_pagination_order(join_class:, column_name:, direction: :asc)
join_table = join_class.table_name
asc_order_expression = Gitlab::Database.nulls_last_order("#{join_table}.#{column_name}", :asc)
desc_order_expression = Gitlab::Database.nulls_first_order("#{join_table}.#{column_name}", :desc)
order_direction = direction == :asc ? asc_order_expression : desc_order_expression
reverse_order_direction = direction == :asc ? desc_order_expression : asc_order_expression
arel_order_classes = ::Gitlab::Pagination::Keyset::ColumnOrderDefinition::AREL_ORDER_CLASSES.invert
::Gitlab::Pagination::Keyset::Order.build([
::Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: "#{join_table}_#{column_name}",
column_expression: join_class.arel_table[column_name],
order_expression: order_direction,
reversed_order_expression: reverse_order_direction,
order_direction: direction,
distinct: false,
add_to_projections: true
),
::Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: 'id',
order_expression: arel_order_classes[direction].new(Packages::Package.arel_table[:id]),
add_to_projections: true
)
])
end
def versions
project.packages
.including_build_info
......
---
name: arel_package_scopes
introduced_by_url:
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/331306
milestone: '13.12'
type: development
group: group::package
default_enabled: false
......@@ -729,6 +729,49 @@ RSpec.describe Packages::Package, type: :model do
end
end
context 'sorting' do
let_it_be(:project) { create(:project, name: 'aaa' ) }
let_it_be(:project2) { create(:project, name: 'bbb' ) }
let_it_be(:package1) { create(:package, project: project ) }
let_it_be(:package2) { create(:package, project: project2 ) }
let_it_be(:package3) { create(:package, project: project2 ) }
let_it_be(:package4) { create(:package, project: project ) }
it 'orders packages by their projects name ascending' do
expect(Packages::Package.order_project_name).to eq([package1, package4, package2, package3])
end
it 'orders packages by their projects name descending' do
expect(Packages::Package.order_project_name_desc).to eq([package2, package3, package1, package4])
end
shared_examples 'order_project_path scope' do
it 'orders packages by their projects path asc, then package id asc' do
expect(Packages::Package.order_project_path).to eq([package1, package4, package2, package3])
end
end
shared_examples 'order_project_path_desc scope' do
it 'orders packages by their projects path desc, then package id desc' do
expect(Packages::Package.order_project_path_desc).to eq([package3, package2, package4, package1])
end
end
context 'with arel scope feature flag enabled' do
it_behaves_like 'order_project_path scope'
it_behaves_like 'order_project_path_desc scope'
end
context 'with feature flag disabled' do
before do
stub_feature_flags(arel_package_scopes: false)
end
it_behaves_like 'order_project_path scope'
it_behaves_like 'order_project_path_desc scope'
end
end
describe '.order_by_package_file' do
let_it_be(:project) { create(:project) }
let_it_be(:package1) { create(:maven_package, project: project) }
......@@ -743,6 +786,33 @@ RSpec.describe Packages::Package, type: :model do
end
end
describe '.keyset_pagination_order' do
let(:join_class) { nil }
let(:column_name) { nil }
let(:direction) { nil }
subject { described_class.keyset_pagination_order(join_class: join_class, column_name: column_name, direction: direction) }
it { expect { subject }.to raise_error(NoMethodError) }
context 'with valid params' do
let(:join_class) { Project }
let(:column_name) { :name }
context 'ascending direction' do
let(:direction) { :asc }
it { is_expected.to eq('projects.name asc NULLS LAST, "packages_packages"."id" ASC') }
end
context 'descending direction' do
let(:direction) { :desc }
it { is_expected.to eq('projects.name desc NULLS FIRST, "packages_packages"."id" DESC') }
end
end
end
describe '#versions' do
let_it_be(:project) { create(:project) }
let_it_be(:package) { create(:maven_package, project: project) }
......
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