Commit bfc8bcf9 authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch 'undo-cte-hack' into 'master'

Undo CTE fix for PG11 [RUN ALL RSPEC] [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!58499
parents 4adacdd0 d8daab8c
...@@ -78,7 +78,7 @@ module Namespaces ...@@ -78,7 +78,7 @@ module Namespaces
alias_method :recursive_self_and_descendant_ids, :self_and_descendant_ids alias_method :recursive_self_and_descendant_ids, :self_and_descendant_ids
def object_hierarchy(ancestors_base) def object_hierarchy(ancestors_base)
Gitlab::ObjectHierarchy.new(ancestors_base, options: { use_distinct: Feature.enabled?(:use_distinct_in_object_hierarchy, self) }) Gitlab::ObjectHierarchy.new(ancestors_base)
end end
end end
end end
......
...@@ -28,7 +28,7 @@ module Ci ...@@ -28,7 +28,7 @@ module Ci
groups = ::Group.joins(:runner_namespaces).merge(runner.runner_namespaces) groups = ::Group.joins(:runner_namespaces).merge(runner.runner_namespaces)
hierarchy_groups = Gitlab::ObjectHierarchy hierarchy_groups = Gitlab::ObjectHierarchy
.new(groups, options: { use_distinct: ::Feature.enabled?(:use_distinct_in_register_job_object_hierarchy) }) .new(groups)
.base_and_descendants .base_and_descendants
projects = Project.where(namespace_id: hierarchy_groups) projects = Project.where(namespace_id: hierarchy_groups)
......
---
name: use_distinct_for_all_object_hierarchy
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57052
rollout_issue_url:
milestone: '13.11'
type: development
group: group::database
default_enabled: false
---
name: use_distinct_in_object_hierarchy
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56509
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/324644
milestone: '13.10'
type: development
group: group::optimize
default_enabled: false
---
name: use_distinct_in_register_job_object_hierarchy
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57045
rollout_issue_url:
milestone: '13.11'
type: development
group: group::pipeline execution
default_enabled: false
...@@ -47,7 +47,7 @@ module EE ...@@ -47,7 +47,7 @@ module EE
.joins('INNER JOIN namespaces as project_namespaces ON project_namespaces.id = projects.namespace_id') .joins('INNER JOIN namespaces as project_namespaces ON project_namespaces.id = projects.namespace_id')
else else
namespaces = ::Namespace.reorder(nil).where('namespaces.id = projects.namespace_id') namespaces = ::Namespace.reorder(nil).where('namespaces.id = projects.namespace_id')
::Gitlab::ObjectHierarchy.new(namespaces, options: { skip_ordering: true }).roots ::Gitlab::ObjectHierarchy.new(namespaces).roots
end end
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -65,32 +65,9 @@ module Gitlab ...@@ -65,32 +65,9 @@ module Gitlab
# Note: By default the order is breadth-first # Note: By default the order is breadth-first
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def base_and_ancestors(upto: nil, hierarchy_order: nil) def base_and_ancestors(upto: nil, hierarchy_order: nil)
if use_distinct? recursive_query = base_and_ancestors_cte(upto, hierarchy_order).apply_to(unscoped_model.all)
expose_depth = hierarchy_order.present? recursive_query = recursive_query.order(depth: hierarchy_order) if hierarchy_order
hierarchy_order ||= :asc read_only(recursive_query)
# if hierarchy_order is given, the calculated `depth` should be present in SELECT
if expose_depth
recursive_query = base_and_ancestors_cte(upto, hierarchy_order).apply_to(unscoped_model.all).distinct
read_only(unscoped_model.from(Arel::Nodes::As.new(recursive_query.arel, objects_table)).order(depth: hierarchy_order))
else
recursive_query = base_and_ancestors_cte(upto).apply_to(unscoped_model.all)
if skip_ordering?
recursive_query = recursive_query.distinct
else
recursive_query = recursive_query.reselect(*recursive_query.arel.projections, 'ROW_NUMBER() OVER () as depth').distinct
recursive_query = unscoped_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
else
recursive_query = base_and_ancestors_cte(upto, hierarchy_order).apply_to(unscoped_model.all)
recursive_query = recursive_query.order(depth: hierarchy_order) if hierarchy_order
read_only(recursive_query)
end
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
...@@ -101,27 +78,7 @@ module Gitlab ...@@ -101,27 +78,7 @@ module Gitlab
# and incremented as we go down the descendant tree # and incremented as we go down the descendant tree
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def base_and_descendants(with_depth: false) def base_and_descendants(with_depth: false)
if use_distinct? read_only(base_and_descendants_cte(with_depth: with_depth).apply_to(unscoped_model.all))
# Always calculate `depth`, remove it later if with_depth is false
if with_depth
base_cte = base_and_descendants_cte(with_depth: true).apply_to(unscoped_model.all).distinct
read_only(unscoped_model.from(Arel::Nodes::As.new(base_cte.arel, objects_table)).order(depth: :asc))
else
base_cte = base_and_descendants_cte.apply_to(unscoped_model.all)
if skip_ordering?
base_cte = base_cte.distinct
else
base_cte = base_cte.reselect(*base_cte.arel.projections, 'ROW_NUMBER() OVER () as depth').distinct
base_cte = unscoped_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
else
read_only(base_and_descendants_cte(with_depth: with_depth).apply_to(unscoped_model.all))
end
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
...@@ -158,11 +115,6 @@ module Gitlab ...@@ -158,11 +115,6 @@ module Gitlab
ancestors_scope = unscoped_model.from(ancestors_table) ancestors_scope = unscoped_model.from(ancestors_table)
descendants_scope = unscoped_model.from(descendants_table) descendants_scope = unscoped_model.from(descendants_table)
if use_distinct?
ancestors_scope = ancestors_scope.distinct
descendants_scope = descendants_scope.distinct
end
relation = unscoped_model relation = unscoped_model
.with .with
.recursive(ancestors.to_arel, descendants.to_arel) .recursive(ancestors.to_arel, descendants.to_arel)
...@@ -177,23 +129,6 @@ module Gitlab ...@@ -177,23 +129,6 @@ module Gitlab
private private
# Use distinct on the Namespace queries to avoid bad planner behavior in PG11.
def use_distinct?
return unless model <= Namespace
# Global use_distinct_for_all_object_hierarchy takes precedence over use_distinct_in_object_hierarchy
return true if Feature.enabled?(:use_distinct_for_all_object_hierarchy)
return options[:use_distinct] if options.key?(:use_distinct)
false
end
# Skips the extra ordering when using distinct on the namespace queries
def skip_ordering?
return options[:skip_ordering] if options.key?(:skip_ordering)
false
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.
# #
......
...@@ -9,265 +9,178 @@ RSpec.describe Gitlab::ObjectHierarchy do ...@@ -9,265 +9,178 @@ RSpec.describe Gitlab::ObjectHierarchy do
let(:options) { {} } let(:options) { {} }
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), options: options).base_and_ancestors
described_class.new(Group.where(id: child2.id), options: options).base_and_ancestors end
end
it 'includes the base rows' do
expect(relation).to include(child2)
end
it 'includes all of the ancestors' do
expect(relation).to include(parent, child1)
end
it 'can find ancestors upto a certain level' do
relation = described_class.new(Group.where(id: child2), options: options).base_and_ancestors(upto: child1)
expect(relation).to contain_exactly(child2)
end
it 'uses ancestors_base #initialize argument' do
relation = described_class.new(Group.where(id: child2.id), Group.none, options: options).base_and_ancestors
expect(relation).to include(parent, child1, child2) it 'includes the base rows' do
end expect(relation).to include(child2)
end
it 'does not allow the use of #update_all' do it 'includes all of the ancestors' do
expect { relation.update_all(share_with_group_lock: false) } expect(relation).to include(parent, child1)
.to raise_error(ActiveRecord::ReadOnlyRecord) end
end
describe 'hierarchy_order option' do it 'can find ancestors upto a certain level' do
let(:relation) do relation = described_class.new(Group.where(id: child2), options: options).base_and_ancestors(upto: child1)
described_class.new(Group.where(id: child2.id), options: options).base_and_ancestors(hierarchy_order: hierarchy_order)
end
context ':asc' do expect(relation).to contain_exactly(child2)
let(:hierarchy_order) { :asc } end
it 'orders by child to parent' do it 'uses ancestors_base #initialize argument' do
expect(relation).to eq([child2, child1, parent]) relation = described_class.new(Group.where(id: child2.id), Group.none, options: options).base_and_ancestors
end
end
context ':desc' do expect(relation).to include(parent, child1, child2)
let(:hierarchy_order) { :desc } end
it 'orders by parent to child' do it 'does not allow the use of #update_all' do
expect(relation).to eq([parent, child1, child2]) expect { relation.update_all(share_with_group_lock: false) }
end .to raise_error(ActiveRecord::ReadOnlyRecord)
end
end
end end
describe '#base_and_descendants' do describe 'hierarchy_order option' do
let(:relation) do let(:relation) do
described_class.new(Group.where(id: parent.id), options: options).base_and_descendants described_class.new(Group.where(id: child2.id), options: options).base_and_ancestors(hierarchy_order: hierarchy_order)
end
it 'includes the base rows' do
expect(relation).to include(parent)
end
it 'includes all the descendants' do
expect(relation).to include(child1, child2)
end end
it 'uses descendants_base #initialize argument' do context ':asc' do
relation = described_class.new(Group.none, Group.where(id: parent.id), options: options).base_and_descendants let(:hierarchy_order) { :asc }
expect(relation).to include(parent, child1, child2) it 'orders by child to parent' do
end expect(relation).to eq([child2, child1, parent])
it 'does not allow the use of #update_all' do
expect { relation.update_all(share_with_group_lock: false) }
.to raise_error(ActiveRecord::ReadOnlyRecord)
end
context 'when with_depth is true' do
let(:relation) do
described_class.new(Group.where(id: parent.id), options: options).base_and_descendants(with_depth: true)
end end
end
it 'includes depth in the results' do context ':desc' do
object_depths = { let(:hierarchy_order) { :desc }
parent.id => 1,
child1.id => 2,
child2.id => 3
}
relation.each do |object| it 'orders by parent to child' do
expect(object.depth).to eq(object_depths[object.id]) expect(relation).to eq([parent, child1, child2])
end
end end
end end
end end
end
describe '#descendants' do describe '#base_and_descendants' do
it 'includes only the descendants' do let(:relation) do
relation = described_class.new(Group.where(id: parent), options: options).descendants described_class.new(Group.where(id: parent.id), options: options).base_and_descendants
expect(relation).to contain_exactly(child1, child2)
end
end end
describe '#max_descendants_depth' do it 'includes the base rows' do
subject { described_class.new(base_relation, options: options).max_descendants_depth } expect(relation).to include(parent)
context 'when base relation is empty' do
let(:base_relation) { Group.where(id: nil) }
it { expect(subject).to be_nil }
end
context 'when base has no children' do
let(:base_relation) { Group.where(id: child2) }
it { expect(subject).to eq(1) }
end
context 'when base has grandchildren' do
let(:base_relation) { Group.where(id: parent) }
it { expect(subject).to eq(3) }
end
end end
describe '#ancestors' do it 'includes all the descendants' do
it 'includes only the ancestors' do expect(relation).to include(child1, child2)
relation = described_class.new(Group.where(id: child2), options: options).ancestors end
expect(relation).to contain_exactly(child1, parent) it 'uses descendants_base #initialize argument' do
end relation = described_class.new(Group.none, Group.where(id: parent.id), options: options).base_and_descendants
it 'can find ancestors upto a certain level' do expect(relation).to include(parent, child1, child2)
relation = described_class.new(Group.where(id: child2), options: options).ancestors(upto: child1) end
expect(relation).to be_empty it 'does not allow the use of #update_all' do
end expect { relation.update_all(share_with_group_lock: false) }
.to raise_error(ActiveRecord::ReadOnlyRecord)
end end
describe '#all_objects' do context 'when with_depth is true' do
let(:relation) do let(:relation) do
described_class.new(Group.where(id: child1.id), options: options).all_objects described_class.new(Group.where(id: parent.id), options: options).base_and_descendants(with_depth: true)
end end
it 'includes the base rows' do it 'includes depth in the results' do
expect(relation).to include(child1) object_depths = {
end parent.id => 1,
child1.id => 2,
it 'includes the ancestors' do child2.id => 3
expect(relation).to include(parent) }
end
it 'includes the descendants' do relation.each do |object|
expect(relation).to include(child2) expect(object.depth).to eq(object_depths[object.id])
end end
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), options: options).all_objects
expect(relation).to include(parent)
end end
end
end
it 'uses descendants_base #initialize argument for descendants' do describe '#descendants' do
relation = described_class.new(Group.where(id: non_existing_record_id), Group.where(id: child1.id), options: options).all_objects it 'includes only the descendants' do
relation = described_class.new(Group.where(id: parent), options: options).descendants
expect(relation).to include(child2)
end
it 'does not allow the use of #update_all' do expect(relation).to contain_exactly(child1, child2)
expect { relation.update_all(share_with_group_lock: false) }
.to raise_error(ActiveRecord::ReadOnlyRecord)
end
end end
end end
context 'when the use_distinct_in_object_hierarchy feature flag is enabled' do describe '#max_descendants_depth' do
before do subject { described_class.new(base_relation, options: options).max_descendants_depth }
stub_feature_flags(use_distinct_in_object_hierarchy: true)
stub_feature_flags(use_distinct_for_all_object_hierarchy: false)
end
it_behaves_like 'Gitlab::ObjectHierarchy test cases' context 'when base relation is empty' do
let(:base_relation) { Group.where(id: nil) }
it 'calls DISTINCT' do it { expect(subject).to be_nil }
expect(child2.self_and_ancestors.to_sql).to include("DISTINCT")
end end
context 'when use_traversal_ids feature flag is enabled' do context 'when base has no children' do
it 'does not call DISTINCT' do let(:base_relation) { Group.where(id: child2) }
expect(parent.self_and_descendants.to_sql).not_to include("DISTINCT")
end it { expect(subject).to eq(1) }
end end
context 'when use_traversal_ids feature flag is disabled' do context 'when base has grandchildren' do
before do let(:base_relation) { Group.where(id: parent) }
stub_feature_flags(use_traversal_ids: false)
end
it 'calls DISTINCT' do it { expect(subject).to eq(3) }
expect(parent.self_and_descendants.to_sql).to include("DISTINCT")
end
end end
end end
context 'when the use_distinct_for_all_object_hierarchy feature flag is enabled' do describe '#ancestors' do
before do it 'includes only the ancestors' do
stub_feature_flags(use_distinct_in_object_hierarchy: false) relation = described_class.new(Group.where(id: child2), options: options).ancestors
stub_feature_flags(use_distinct_for_all_object_hierarchy: true)
expect(relation).to contain_exactly(child1, parent)
end end
it_behaves_like 'Gitlab::ObjectHierarchy test cases' it 'can find ancestors upto a certain level' do
relation = described_class.new(Group.where(id: child2), options: options).ancestors(upto: child1)
it 'calls DISTINCT' do expect(relation).to be_empty
expect(child2.self_and_ancestors.to_sql).to include("DISTINCT")
end end
end
context 'when use_traversal_ids feature flag is enabled' do describe '#all_objects' do
it 'does not call DISTINCT' do let(:relation) do
expect(parent.self_and_descendants.to_sql).not_to include("DISTINCT") described_class.new(Group.where(id: child1.id), options: options).all_objects
end
end end
context 'when use_traversal_ids feature flag is disabled' do it 'includes the base rows' do
before do expect(relation).to include(child1)
stub_feature_flags(use_traversal_ids: false) end
end
it 'calls DISTINCT' do
expect(parent.self_and_descendants.to_sql).to include("DISTINCT")
end
context 'when the skip_ordering option is set' do it 'includes the ancestors' do
let(:options) { { skip_ordering: true } } expect(relation).to include(parent)
end
it_behaves_like 'Gitlab::ObjectHierarchy test cases' it 'includes the descendants' do
expect(relation).to include(child2)
end
it 'does not include ROW_NUMBER()' do it 'uses ancestors_base #initialize argument for ancestors' do
query = described_class.new(Group.where(id: parent.id), options: options).base_and_descendants.to_sql relation = described_class.new(Group.where(id: child1.id), Group.where(id: non_existing_record_id), options: options).all_objects
expect(query).to include("DISTINCT") expect(relation).to include(parent)
expect(query).not_to include("ROW_NUMBER()")
end
end
end end
end
context 'when the use_distinct_in_object_hierarchy feature flag is disabled' do it 'uses descendants_base #initialize argument for descendants' do
before do relation = described_class.new(Group.where(id: non_existing_record_id), Group.where(id: child1.id), options: options).all_objects
stub_feature_flags(use_distinct_in_object_hierarchy: false)
stub_feature_flags(use_distinct_for_all_object_hierarchy: false)
end
it_behaves_like 'Gitlab::ObjectHierarchy test cases' expect(relation).to include(child2)
end
it 'does not call DISTINCT' do it 'does not allow the use of #update_all' do
expect(parent.self_and_descendants.to_sql).not_to include("DISTINCT") expect { relation.update_all(share_with_group_lock: false) }
expect(child2.self_and_ancestors.to_sql).not_to include("DISTINCT") .to raise_error(ActiveRecord::ReadOnlyRecord)
end end
end end
end end
...@@ -294,32 +294,6 @@ module Ci ...@@ -294,32 +294,6 @@ module Ci
end end
end end
context 'when the use_distinct_in_register_job_object_hierarchy feature flag is enabled' do
before do
stub_feature_flags(use_distinct_in_register_job_object_hierarchy: true)
stub_feature_flags(use_distinct_for_all_object_hierarchy: true)
end
it 'calls DISTINCT' do
queue = ::Ci::Queue::BuildQueueService.new(group_runner)
expect(queue.builds_for_group_runner.to_sql).to include("DISTINCT")
end
end
context 'when the use_distinct_in_register_job_object_hierarchy feature flag is disabled' do
before do
stub_feature_flags(use_distinct_in_register_job_object_hierarchy: false)
stub_feature_flags(use_distinct_for_all_object_hierarchy: false)
end
it 'does not call DISTINCT' do
queue = ::Ci::Queue::BuildQueueService.new(group_runner)
expect(queue.builds_for_group_runner.to_sql).not_to include("DISTINCT")
end
end
context 'group runner' do context 'group runner' do
let(:build) { execute(group_runner) } let(:build) { execute(group_runner) }
......
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