Commit 77dd6c63 authored by Heinrich Lee Yu's avatar Heinrich Lee Yu Committed by Jan Provaznik

Add option to skip ordering when using DISTINCT

When using the DISTINCT workaround, we had to reorder the results
because some instances depended on the breadth-first ordering of
results.

This is not required in all cases so we add an option to skip this so
that we can optimize some queries that are executed very frequently.
parent a9f4517a
...@@ -74,9 +74,16 @@ module Gitlab ...@@ -74,9 +74,16 @@ module Gitlab
read_only(model.from(Arel::Nodes::As.new(recursive_query.arel, objects_table)).order(depth: hierarchy_order)) read_only(model.from(Arel::Nodes::As.new(recursive_query.arel, objects_table)).order(depth: hierarchy_order))
else else
recursive_query = base_and_ancestors_cte(upto).apply_to(model.all) recursive_query = base_and_ancestors_cte(upto).apply_to(model.all)
recursive_query = recursive_query.reselect(*recursive_query.arel.projections, 'ROW_NUMBER() OVER () as depth').distinct
recursive_query = model.from(Arel::Nodes::As.new(recursive_query.arel, objects_table)) if skip_ordering?
read_only(remove_depth_and_maintain_order(recursive_query, hierarchy_order: hierarchy_order)) recursive_query = recursive_query.distinct
else
recursive_query = recursive_query.reselect(*recursive_query.arel.projections, 'ROW_NUMBER() OVER () as depth').distinct
recursive_query = model.from(Arel::Nodes::As.new(recursive_query.arel, objects_table))
recursive_query = remove_depth_and_maintain_order(recursive_query, hierarchy_order: hierarchy_order)
end
read_only(recursive_query)
end end
else else
recursive_query = base_and_ancestors_cte(upto, hierarchy_order).apply_to(model.all) recursive_query = base_and_ancestors_cte(upto, hierarchy_order).apply_to(model.all)
...@@ -100,9 +107,16 @@ module Gitlab ...@@ -100,9 +107,16 @@ module Gitlab
read_only(model.from(Arel::Nodes::As.new(base_cte.arel, objects_table)).order(depth: :asc)) read_only(model.from(Arel::Nodes::As.new(base_cte.arel, objects_table)).order(depth: :asc))
else else
base_cte = base_and_descendants_cte.apply_to(model.all) base_cte = base_and_descendants_cte.apply_to(model.all)
base_cte = base_cte.reselect(*base_cte.arel.projections, 'ROW_NUMBER() OVER () as depth').distinct
base_cte = model.from(Arel::Nodes::As.new(base_cte.arel, objects_table)) if skip_ordering?
read_only(remove_depth_and_maintain_order(base_cte, hierarchy_order: :asc)) base_cte = base_cte.distinct
else
base_cte = base_cte.reselect(*base_cte.arel.projections, 'ROW_NUMBER() OVER () as depth').distinct
base_cte = model.from(Arel::Nodes::As.new(base_cte.arel, objects_table))
base_cte = remove_depth_and_maintain_order(base_cte, hierarchy_order: :asc)
end
read_only(base_cte)
end end
else else
read_only(base_and_descendants_cte(with_depth: with_depth).apply_to(model.all)) read_only(base_and_descendants_cte(with_depth: with_depth).apply_to(model.all))
...@@ -173,6 +187,13 @@ module Gitlab ...@@ -173,6 +187,13 @@ module Gitlab
false false
end end
# Skips the extra ordering when using distinct on the namespace queries
def skip_ordering?
return options[:skip_ordering] if options.key?(:skip_ordering)
true
end
# Remove the extra `depth` field using an INNER JOIN to avoid breaking UNION queries # Remove the extra `depth` field using an INNER JOIN to avoid breaking UNION queries
# and ordering the rows based on the `depth` column to maintain the row order. # and ordering the rows based on the `depth` column to maintain the row order.
# #
......
...@@ -3,14 +3,16 @@ ...@@ -3,14 +3,16 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::ObjectHierarchy do RSpec.describe Gitlab::ObjectHierarchy do
let!(:parent) { create(:group) } let_it_be(:parent) { create(:group) }
let!(:child1) { create(:group, parent: parent) } let_it_be(:child1) { create(:group, parent: parent) }
let!(:child2) { create(:group, parent: child1) } let_it_be(:child2) { create(:group, parent: child1) }
let(:options) { {} }
shared_context 'Gitlab::ObjectHierarchy test cases' do shared_context 'Gitlab::ObjectHierarchy test cases' do
describe '#base_and_ancestors' do describe '#base_and_ancestors' do
let(:relation) do let(:relation) do
described_class.new(Group.where(id: child2.id)).base_and_ancestors described_class.new(Group.where(id: child2.id), options: options).base_and_ancestors
end end
it 'includes the base rows' do it 'includes the base rows' do
...@@ -22,13 +24,13 @@ RSpec.describe Gitlab::ObjectHierarchy do ...@@ -22,13 +24,13 @@ RSpec.describe Gitlab::ObjectHierarchy do
end end
it 'can find ancestors upto a certain level' do it 'can find ancestors upto a certain level' do
relation = described_class.new(Group.where(id: child2)).base_and_ancestors(upto: child1) relation = described_class.new(Group.where(id: child2), options: options).base_and_ancestors(upto: child1)
expect(relation).to contain_exactly(child2) expect(relation).to contain_exactly(child2)
end end
it 'uses ancestors_base #initialize argument' do it 'uses ancestors_base #initialize argument' do
relation = described_class.new(Group.where(id: child2.id), Group.none).base_and_ancestors relation = described_class.new(Group.where(id: child2.id), Group.none, options: options).base_and_ancestors
expect(relation).to include(parent, child1, child2) expect(relation).to include(parent, child1, child2)
end end
...@@ -40,7 +42,7 @@ RSpec.describe Gitlab::ObjectHierarchy do ...@@ -40,7 +42,7 @@ RSpec.describe Gitlab::ObjectHierarchy do
describe 'hierarchy_order option' do describe 'hierarchy_order option' do
let(:relation) do let(:relation) do
described_class.new(Group.where(id: child2.id)).base_and_ancestors(hierarchy_order: hierarchy_order) described_class.new(Group.where(id: child2.id), options: options).base_and_ancestors(hierarchy_order: hierarchy_order)
end end
context ':asc' do context ':asc' do
...@@ -63,7 +65,7 @@ RSpec.describe Gitlab::ObjectHierarchy do ...@@ -63,7 +65,7 @@ RSpec.describe Gitlab::ObjectHierarchy do
describe '#base_and_descendants' do describe '#base_and_descendants' do
let(:relation) do let(:relation) do
described_class.new(Group.where(id: parent.id)).base_and_descendants described_class.new(Group.where(id: parent.id), options: options).base_and_descendants
end end
it 'includes the base rows' do it 'includes the base rows' do
...@@ -75,7 +77,7 @@ RSpec.describe Gitlab::ObjectHierarchy do ...@@ -75,7 +77,7 @@ RSpec.describe Gitlab::ObjectHierarchy do
end end
it 'uses descendants_base #initialize argument' do it 'uses descendants_base #initialize argument' do
relation = described_class.new(Group.none, Group.where(id: parent.id)).base_and_descendants relation = described_class.new(Group.none, Group.where(id: parent.id), options: options).base_and_descendants
expect(relation).to include(parent, child1, child2) expect(relation).to include(parent, child1, child2)
end end
...@@ -87,7 +89,7 @@ RSpec.describe Gitlab::ObjectHierarchy do ...@@ -87,7 +89,7 @@ RSpec.describe Gitlab::ObjectHierarchy do
context 'when with_depth is true' do context 'when with_depth is true' do
let(:relation) do let(:relation) do
described_class.new(Group.where(id: parent.id)).base_and_descendants(with_depth: true) described_class.new(Group.where(id: parent.id), options: options).base_and_descendants(with_depth: true)
end end
it 'includes depth in the results' do it 'includes depth in the results' do
...@@ -106,14 +108,14 @@ RSpec.describe Gitlab::ObjectHierarchy do ...@@ -106,14 +108,14 @@ RSpec.describe Gitlab::ObjectHierarchy do
describe '#descendants' do describe '#descendants' do
it 'includes only the descendants' do it 'includes only the descendants' do
relation = described_class.new(Group.where(id: parent)).descendants relation = described_class.new(Group.where(id: parent), options: options).descendants
expect(relation).to contain_exactly(child1, child2) expect(relation).to contain_exactly(child1, child2)
end end
end end
describe '#max_descendants_depth' do describe '#max_descendants_depth' do
subject { described_class.new(base_relation).max_descendants_depth } subject { described_class.new(base_relation, options: options).max_descendants_depth }
context 'when base relation is empty' do context 'when base relation is empty' do
let(:base_relation) { Group.where(id: nil) } let(:base_relation) { Group.where(id: nil) }
...@@ -136,13 +138,13 @@ RSpec.describe Gitlab::ObjectHierarchy do ...@@ -136,13 +138,13 @@ RSpec.describe Gitlab::ObjectHierarchy do
describe '#ancestors' do describe '#ancestors' do
it 'includes only the ancestors' do it 'includes only the ancestors' do
relation = described_class.new(Group.where(id: child2)).ancestors relation = described_class.new(Group.where(id: child2), options: options).ancestors
expect(relation).to contain_exactly(child1, parent) expect(relation).to contain_exactly(child1, parent)
end end
it 'can find ancestors upto a certain level' do it 'can find ancestors upto a certain level' do
relation = described_class.new(Group.where(id: child2)).ancestors(upto: child1) relation = described_class.new(Group.where(id: child2), options: options).ancestors(upto: child1)
expect(relation).to be_empty expect(relation).to be_empty
end end
...@@ -150,7 +152,7 @@ RSpec.describe Gitlab::ObjectHierarchy do ...@@ -150,7 +152,7 @@ RSpec.describe Gitlab::ObjectHierarchy do
describe '#all_objects' do describe '#all_objects' do
let(:relation) do let(:relation) do
described_class.new(Group.where(id: child1.id)).all_objects described_class.new(Group.where(id: child1.id), options: options).all_objects
end end
it 'includes the base rows' do it 'includes the base rows' do
...@@ -166,13 +168,13 @@ RSpec.describe Gitlab::ObjectHierarchy do ...@@ -166,13 +168,13 @@ RSpec.describe Gitlab::ObjectHierarchy do
end end
it 'uses ancestors_base #initialize argument for ancestors' do it 'uses ancestors_base #initialize argument for ancestors' do
relation = described_class.new(Group.where(id: child1.id), Group.where(id: non_existing_record_id)).all_objects relation = described_class.new(Group.where(id: child1.id), Group.where(id: non_existing_record_id), options: options).all_objects
expect(relation).to include(parent) expect(relation).to include(parent)
end end
it 'uses descendants_base #initialize argument for descendants' do it 'uses descendants_base #initialize argument for descendants' do
relation = described_class.new(Group.where(id: non_existing_record_id), Group.where(id: child1.id)).all_objects relation = described_class.new(Group.where(id: non_existing_record_id), Group.where(id: child1.id), options: options).all_objects
expect(relation).to include(child2) expect(relation).to include(child2)
end end
...@@ -210,6 +212,19 @@ RSpec.describe Gitlab::ObjectHierarchy do ...@@ -210,6 +212,19 @@ RSpec.describe Gitlab::ObjectHierarchy do
expect(parent.self_and_descendants.to_sql).to include("DISTINCT") expect(parent.self_and_descendants.to_sql).to include("DISTINCT")
expect(child2.self_and_ancestors.to_sql).to include("DISTINCT") expect(child2.self_and_ancestors.to_sql).to include("DISTINCT")
end end
context 'when the skip_ordering option is set' do
let(:options) { { skip_ordering: true } }
it_behaves_like 'Gitlab::ObjectHierarchy test cases'
it 'does not include ROW_NUMBER()' do
query = described_class.new(Group.where(id: parent.id), options: options).base_and_descendants.to_sql
expect(query).to include("DISTINCT")
expect(query).not_to include("ROW_NUMBER()")
end
end
end end
context 'when the use_distinct_in_object_hierarchy feature flag is disabled' do context 'when the use_distinct_in_object_hierarchy feature flag is disabled' 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