2021-10-27 15:23:28 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module Namespaces
|
|
|
|
module Traversal
|
|
|
|
module LinearScopes
|
|
|
|
extend ActiveSupport::Concern
|
|
|
|
|
2022-07-23 23:45:48 +05:30
|
|
|
include AsCte
|
|
|
|
|
2021-10-27 15:23:28 +05:30
|
|
|
class_methods do
|
|
|
|
# When filtering namespaces by the traversal_ids column to compile a
|
|
|
|
# list of namespace IDs, it can be faster to reference the ID in
|
|
|
|
# traversal_ids than the primary key ID column.
|
|
|
|
def as_ids
|
|
|
|
return super unless use_traversal_ids?
|
|
|
|
|
2022-04-04 11:22:00 +05:30
|
|
|
select(Arel.sql('namespaces.traversal_ids[array_length(namespaces.traversal_ids, 1)]').as('id'))
|
2021-10-27 15:23:28 +05:30
|
|
|
end
|
|
|
|
|
2021-12-11 22:18:48 +05:30
|
|
|
def roots
|
|
|
|
return super unless use_traversal_ids_roots?
|
|
|
|
|
|
|
|
root_ids = all.select("#{quoted_table_name}.traversal_ids[1]").distinct
|
|
|
|
unscoped.where(id: root_ids)
|
|
|
|
end
|
|
|
|
|
2022-03-02 08:16:31 +05:30
|
|
|
def self_and_ancestors(include_self: true, upto: nil, hierarchy_order: nil)
|
2021-11-11 11:23:49 +05:30
|
|
|
return super unless use_traversal_ids_for_ancestor_scopes?
|
|
|
|
|
2022-07-23 23:45:48 +05:30
|
|
|
if Feature.enabled?(:use_traversal_ids_for_ancestor_scopes_with_inner_join)
|
|
|
|
self_and_ancestors_from_inner_join(include_self: include_self,
|
|
|
|
upto: upto, hierarchy_order:
|
|
|
|
hierarchy_order)
|
|
|
|
else
|
|
|
|
self_and_ancestors_from_ancestors_cte(include_self: include_self,
|
|
|
|
upto: upto,
|
|
|
|
hierarchy_order: hierarchy_order)
|
2021-11-11 11:23:49 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self_and_ancestor_ids(include_self: true)
|
|
|
|
return super unless use_traversal_ids_for_ancestor_scopes?
|
|
|
|
|
|
|
|
self_and_ancestors(include_self: include_self).as_ids
|
|
|
|
end
|
|
|
|
|
2021-10-27 15:23:28 +05:30
|
|
|
def self_and_descendants(include_self: true)
|
2022-04-04 11:22:00 +05:30
|
|
|
return super unless use_traversal_ids_for_descendants_scopes?
|
2021-10-27 15:23:28 +05:30
|
|
|
|
2022-07-16 23:28:13 +05:30
|
|
|
if Feature.enabled?(:traversal_ids_btree)
|
2021-12-11 22:18:48 +05:30
|
|
|
self_and_descendants_with_comparison_operators(include_self: include_self)
|
|
|
|
else
|
|
|
|
records = self_and_descendants_with_duplicates_with_array_operator(include_self: include_self)
|
|
|
|
distinct = records.select('DISTINCT on(namespaces.id) namespaces.*')
|
|
|
|
distinct.normal_select
|
|
|
|
end
|
2021-10-27 15:23:28 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def self_and_descendant_ids(include_self: true)
|
2022-04-04 11:22:00 +05:30
|
|
|
return super unless use_traversal_ids_for_descendants_scopes?
|
2021-10-27 15:23:28 +05:30
|
|
|
|
2022-07-16 23:28:13 +05:30
|
|
|
if Feature.enabled?(:traversal_ids_btree)
|
2021-12-11 22:18:48 +05:30
|
|
|
self_and_descendants_with_comparison_operators(include_self: include_self).as_ids
|
|
|
|
else
|
|
|
|
self_and_descendants_with_duplicates_with_array_operator(include_self: include_self)
|
|
|
|
.select('DISTINCT namespaces.id')
|
|
|
|
end
|
2021-10-27 15:23:28 +05:30
|
|
|
end
|
|
|
|
|
2022-04-04 11:22:00 +05:30
|
|
|
def self_and_hierarchy
|
|
|
|
return super unless use_traversal_ids_for_self_and_hierarchy_scopes?
|
|
|
|
|
|
|
|
unscoped.from_union([all.self_and_ancestors, all.self_and_descendants(include_self: false)])
|
|
|
|
end
|
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
def order_by_depth(hierarchy_order)
|
|
|
|
return all unless hierarchy_order
|
|
|
|
|
|
|
|
depth_order = hierarchy_order == :asc ? :desc : :asc
|
|
|
|
|
|
|
|
all
|
2022-07-23 23:45:48 +05:30
|
|
|
.select(Namespace.default_select_columns, 'array_length(traversal_ids, 1) as depth')
|
2021-11-11 11:23:49 +05:30
|
|
|
.order(depth: depth_order, id: :asc)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Produce a query of the form: SELECT * FROM namespaces;
|
|
|
|
#
|
|
|
|
# When we have queries that break this SELECT * format we can run in to errors.
|
|
|
|
# For example `SELECT DISTINCT on(...)` will fail when we chain a `.count` c
|
|
|
|
def normal_select
|
2021-12-11 22:18:48 +05:30
|
|
|
unscoped.from(all, :namespaces)
|
2021-11-11 11:23:49 +05:30
|
|
|
end
|
|
|
|
|
2021-10-27 15:23:28 +05:30
|
|
|
private
|
|
|
|
|
|
|
|
def use_traversal_ids?
|
2022-07-16 23:28:13 +05:30
|
|
|
Feature.enabled?(:use_traversal_ids)
|
2021-10-27 15:23:28 +05:30
|
|
|
end
|
|
|
|
|
2021-12-11 22:18:48 +05:30
|
|
|
def use_traversal_ids_roots?
|
2022-07-16 23:28:13 +05:30
|
|
|
Feature.enabled?(:use_traversal_ids_roots) &&
|
2021-12-11 22:18:48 +05:30
|
|
|
use_traversal_ids?
|
|
|
|
end
|
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
def use_traversal_ids_for_ancestor_scopes?
|
2022-07-16 23:28:13 +05:30
|
|
|
Feature.enabled?(:use_traversal_ids_for_ancestor_scopes) &&
|
2021-11-11 11:23:49 +05:30
|
|
|
use_traversal_ids?
|
|
|
|
end
|
|
|
|
|
2022-04-04 11:22:00 +05:30
|
|
|
def use_traversal_ids_for_descendants_scopes?
|
2022-07-16 23:28:13 +05:30
|
|
|
Feature.enabled?(:use_traversal_ids_for_descendants_scopes) &&
|
2022-04-04 11:22:00 +05:30
|
|
|
use_traversal_ids?
|
|
|
|
end
|
|
|
|
|
|
|
|
def use_traversal_ids_for_self_and_hierarchy_scopes?
|
2022-07-16 23:28:13 +05:30
|
|
|
Feature.enabled?(:use_traversal_ids_for_self_and_hierarchy_scopes) &&
|
2022-04-04 11:22:00 +05:30
|
|
|
use_traversal_ids?
|
|
|
|
end
|
|
|
|
|
2022-07-23 23:45:48 +05:30
|
|
|
def self_and_ancestors_from_ancestors_cte(include_self: true, upto: nil, hierarchy_order: nil)
|
|
|
|
base_cte = all.select('namespaces.id', 'namespaces.traversal_ids').as_cte(:base_ancestors_cte)
|
|
|
|
|
|
|
|
# We have to alias id with 'AS' to avoid ambiguous column references by calling methods.
|
|
|
|
ancestors_cte = unscoped
|
|
|
|
.unscope(where: [:type])
|
|
|
|
.select('id as base_id',
|
|
|
|
"#{unnest_func(base_cte.table['traversal_ids']).to_sql} as ancestor_id")
|
|
|
|
.from(base_cte.table)
|
|
|
|
.as_cte(:ancestors_cte)
|
|
|
|
|
|
|
|
namespaces = Arel::Table.new(:namespaces)
|
|
|
|
|
|
|
|
records = unscoped
|
|
|
|
.with(base_cte.to_arel, ancestors_cte.to_arel)
|
|
|
|
.distinct
|
|
|
|
.from([ancestors_cte.table, namespaces])
|
|
|
|
.where(namespaces[:id].eq(ancestors_cte.table[:ancestor_id]))
|
|
|
|
.order_by_depth(hierarchy_order)
|
|
|
|
|
|
|
|
unless include_self
|
|
|
|
records = records.where(ancestors_cte.table[:base_id].not_eq(ancestors_cte.table[:ancestor_id]))
|
|
|
|
end
|
|
|
|
|
|
|
|
if upto
|
|
|
|
records = records.where.not(id: unscoped.where(id: upto).select('unnest(traversal_ids)'))
|
|
|
|
end
|
|
|
|
|
|
|
|
records
|
|
|
|
end
|
|
|
|
|
|
|
|
def self_and_ancestors_from_inner_join(include_self: true, upto: nil, hierarchy_order: nil)
|
|
|
|
base_cte = all.reselect('namespaces.traversal_ids').as_cte(:base_ancestors_cte)
|
|
|
|
|
|
|
|
unnest = if include_self
|
|
|
|
base_cte.table[:traversal_ids]
|
|
|
|
else
|
|
|
|
base_cte_traversal_ids = 'base_ancestors_cte.traversal_ids'
|
|
|
|
traversal_ids_range = "1:array_length(#{base_cte_traversal_ids},1)-1"
|
|
|
|
Arel.sql("#{base_cte_traversal_ids}[#{traversal_ids_range}]")
|
|
|
|
end
|
|
|
|
|
|
|
|
ancestor_subselect = "SELECT DISTINCT #{unnest_func(unnest).to_sql} FROM base_ancestors_cte"
|
|
|
|
ancestors_join = <<~SQL
|
|
|
|
INNER JOIN (#{ancestor_subselect}) AS ancestors(ancestor_id) ON namespaces.id = ancestors.ancestor_id
|
|
|
|
SQL
|
|
|
|
|
|
|
|
namespaces = Arel::Table.new(:namespaces)
|
|
|
|
|
|
|
|
records = unscoped
|
|
|
|
.with(base_cte.to_arel)
|
|
|
|
.from(namespaces)
|
|
|
|
.joins(ancestors_join)
|
|
|
|
.order_by_depth(hierarchy_order)
|
|
|
|
|
|
|
|
if upto
|
|
|
|
upto_ancestor_ids = unscoped.where(id: upto).select(unnest_func(Arel.sql('traversal_ids')))
|
|
|
|
records = records.where.not(id: upto_ancestor_ids)
|
|
|
|
end
|
|
|
|
|
|
|
|
records
|
|
|
|
end
|
|
|
|
|
2021-12-11 22:18:48 +05:30
|
|
|
def self_and_descendants_with_comparison_operators(include_self: true)
|
2022-05-07 20:08:51 +05:30
|
|
|
base = all.select(:traversal_ids)
|
2022-07-23 23:45:48 +05:30
|
|
|
base = base.select(:id) if Feature.enabled?(:linear_scopes_superset)
|
|
|
|
base_cte = base.as_cte(:descendants_base_cte)
|
2021-12-11 22:18:48 +05:30
|
|
|
|
|
|
|
namespaces = Arel::Table.new(:namespaces)
|
|
|
|
|
2022-07-23 23:45:48 +05:30
|
|
|
withs = [base_cte.to_arel]
|
|
|
|
froms = []
|
|
|
|
|
|
|
|
if Feature.enabled?(:linear_scopes_superset)
|
|
|
|
superset_cte = self.superset_cte(base_cte.table.name)
|
|
|
|
withs += [superset_cte.to_arel]
|
|
|
|
froms = [superset_cte.table]
|
|
|
|
else
|
|
|
|
froms = [base_cte.table]
|
|
|
|
end
|
|
|
|
|
|
|
|
# Order is important. namespace should be last to handle future joins.
|
|
|
|
froms += [namespaces]
|
|
|
|
|
|
|
|
base_ref = froms.first
|
|
|
|
|
2021-12-11 22:18:48 +05:30
|
|
|
# Bound the search space to ourselves (optional) and descendants.
|
|
|
|
#
|
2022-05-07 20:08:51 +05:30
|
|
|
# WHERE next_traversal_ids_sibling(base_cte.traversal_ids) > namespaces.traversal_ids
|
2022-01-26 12:08:38 +05:30
|
|
|
records = unscoped
|
2022-05-07 20:08:51 +05:30
|
|
|
.distinct
|
2022-07-23 23:45:48 +05:30
|
|
|
.with(*withs)
|
|
|
|
.from(froms)
|
|
|
|
.where(next_sibling_func(base_ref[:traversal_ids]).gt(namespaces[:traversal_ids]))
|
2021-12-11 22:18:48 +05:30
|
|
|
|
|
|
|
# AND base_cte.traversal_ids <= namespaces.traversal_ids
|
2022-05-07 20:08:51 +05:30
|
|
|
if include_self
|
2022-07-23 23:45:48 +05:30
|
|
|
records.where(base_ref[:traversal_ids].lteq(namespaces[:traversal_ids]))
|
2022-05-07 20:08:51 +05:30
|
|
|
else
|
2022-07-23 23:45:48 +05:30
|
|
|
records.where(base_ref[:traversal_ids].lt(namespaces[:traversal_ids]))
|
2022-05-07 20:08:51 +05:30
|
|
|
end
|
2021-12-11 22:18:48 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def next_sibling_func(*args)
|
|
|
|
Arel::Nodes::NamedFunction.new('next_traversal_ids_sibling', args)
|
|
|
|
end
|
|
|
|
|
2022-07-23 23:45:48 +05:30
|
|
|
def unnest_func(*args)
|
|
|
|
Arel::Nodes::NamedFunction.new('unnest', args)
|
|
|
|
end
|
|
|
|
|
2021-12-11 22:18:48 +05:30
|
|
|
def self_and_descendants_with_duplicates_with_array_operator(include_self: true)
|
2021-10-27 15:23:28 +05:30
|
|
|
base_ids = select(:id)
|
|
|
|
|
|
|
|
records = unscoped
|
|
|
|
.from("namespaces, (#{base_ids.to_sql}) base")
|
|
|
|
.where('namespaces.traversal_ids @> ARRAY[base.id]')
|
|
|
|
|
|
|
|
if include_self
|
|
|
|
records
|
|
|
|
else
|
|
|
|
records.where('namespaces.id <> base.id')
|
|
|
|
end
|
|
|
|
end
|
2022-03-02 08:16:31 +05:30
|
|
|
|
2022-07-23 23:45:48 +05:30
|
|
|
def superset_cte(base_name)
|
|
|
|
superset_sql = <<~SQL
|
|
|
|
SELECT d1.traversal_ids
|
|
|
|
FROM #{base_name} d1
|
|
|
|
WHERE NOT EXISTS (
|
|
|
|
SELECT 1
|
|
|
|
FROM #{base_name} d2
|
|
|
|
WHERE d2.id = ANY(d1.traversal_ids)
|
|
|
|
AND d2.id <> d1.id
|
|
|
|
)
|
|
|
|
SQL
|
|
|
|
|
|
|
|
Gitlab::SQL::CTE.new(:superset, superset_sql, materialized: false)
|
2022-03-02 08:16:31 +05:30
|
|
|
end
|
2021-10-27 15:23:28 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|