Commit 08383fd2 authored by Bob Van Landuyt's avatar Bob Van Landuyt

Make it possible to limit ancestors in a `GroupHierarchy`

Passing a parent_id will limit ancestors upto the specified parent if
it is found.

Using `ancestors` and `descendants` the `base` relation will not be included
parent de553961
...@@ -17,12 +17,33 @@ module Gitlab ...@@ -17,12 +17,33 @@ module Gitlab
@model = ancestors_base.model @model = ancestors_base.model
end end
# Returns the set of descendants of a given relation, but excluding the given
# relation
def descendants
base_and_descendants.where.not(id: descendants_base.select(:id))
end
# Returns the set of ancestors of a given relation, but excluding the given
# relation
#
# Passing an `upto` will stop the recursion once the specified parent_id is
# reached. So all ancestors *lower* than the specified ancestor will be
# included.
def ancestors(upto: nil)
read_only(base_and_ancestors_cte(upto).apply_to(model.all))
.where.not(id: ancestors_base.select(:id))
end
# Returns a relation that includes the ancestors_base set of groups # Returns a relation that includes the ancestors_base set of groups
# and all their ancestors (recursively). # and all their ancestors (recursively).
def base_and_ancestors #
# Passing an `upto` will stop the recursion once the specified parent_id is
# reached. So all ancestors *lower* than the specified acestor will be
# included.
def base_and_ancestors(upto: nil)
return ancestors_base unless Group.supports_nested_groups? return ancestors_base unless Group.supports_nested_groups?
read_only(base_and_ancestors_cte.apply_to(model.all)) read_only(base_and_ancestors_cte(upto).apply_to(model.all))
end end
# Returns a relation that includes the descendants_base set of groups # Returns a relation that includes the descendants_base set of groups
...@@ -78,17 +99,19 @@ module Gitlab ...@@ -78,17 +99,19 @@ module Gitlab
private private
def base_and_ancestors_cte def base_and_ancestors_cte(stop_id = nil)
cte = SQL::RecursiveCTE.new(:base_and_ancestors) cte = SQL::RecursiveCTE.new(:base_and_ancestors)
cte << ancestors_base.except(:order) cte << ancestors_base.except(:order)
# Recursively get all the ancestors of the base set. # Recursively get all the ancestors of the base set.
cte << model parent_query = model
.from([groups_table, cte.table]) .from([groups_table, cte.table])
.where(groups_table[:id].eq(cte.table[:parent_id])) .where(groups_table[:id].eq(cte.table[:parent_id]))
.except(:order) .except(:order)
parent_query = parent_query.where(cte.table[:parent_id].not_eq(stop_id)) if stop_id
cte << parent_query
cte cte
end end
......
...@@ -18,6 +18,12 @@ describe Gitlab::GroupHierarchy, :postgresql do ...@@ -18,6 +18,12 @@ describe Gitlab::GroupHierarchy, :postgresql do
expect(relation).to include(parent, child1) expect(relation).to include(parent, child1)
end end
it 'can find ancestors upto a certain level' do
relation = described_class.new(Group.where(id: child2)).base_and_ancestors(upto: child1)
expect(relation).to contain_exactly(child2)
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).base_and_ancestors
...@@ -55,6 +61,28 @@ describe Gitlab::GroupHierarchy, :postgresql do ...@@ -55,6 +61,28 @@ describe Gitlab::GroupHierarchy, :postgresql do
end end
end end
describe '#descendants' do
it 'includes only the descendants' do
relation = described_class.new(Group.where(id: parent)).descendants
expect(relation).to contain_exactly(child1, child2)
end
end
describe '#ancestors' do
it 'includes only the ancestors' do
relation = described_class.new(Group.where(id: child2)).ancestors
expect(relation).to contain_exactly(child1, parent)
end
it 'can find ancestors upto a certain level' do
relation = described_class.new(Group.where(id: child2)).ancestors(upto: child1)
expect(relation).to be_empty
end
end
describe '#all_groups' do describe '#all_groups' do
let(:relation) do let(:relation) do
described_class.new(Group.where(id: child1.id)).all_groups described_class.new(Group.where(id: child1.id)).all_groups
......
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