Welcome to mirror list, hosted at ThFree Co, Russian Federation.

linear_scopes.rb « traversal « namespaces « models « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 2da0e48c2dabd23ea985bd678742454117c18c37 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# frozen_string_literal: true

module Namespaces
  module Traversal
    module LinearScopes
      extend ActiveSupport::Concern

      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?

          select('namespaces.traversal_ids[array_length(namespaces.traversal_ids, 1)] AS id')
        end

        def self_and_ancestors(include_self: true, hierarchy_order: nil)
          return super unless use_traversal_ids_for_ancestor_scopes?

          records = unscoped
            .without_sti_condition
            .where(id: without_sti_condition.select('unnest(traversal_ids)'))
            .order_by_depth(hierarchy_order)
            .normal_select

          if include_self
            records
          else
            records.where.not(id: all.as_ids)
          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

        def self_and_descendants(include_self: true)
          return super unless use_traversal_ids?

          records = self_and_descendants_with_duplicates(include_self: include_self)

          distinct = records.select('DISTINCT on(namespaces.id) namespaces.*')

          distinct.normal_select
        end

        def self_and_descendant_ids(include_self: true)
          return super unless use_traversal_ids?

          self_and_descendants_with_duplicates(include_self: include_self)
            .select('DISTINCT namespaces.id')
        end

        # Make sure we drop the STI `type = 'Group'` condition for better performance.
        # Logically equivalent so long as hierarchies remain homogeneous.
        def without_sti_condition
          unscope(where: :type)
        end

        def order_by_depth(hierarchy_order)
          return all unless hierarchy_order

          depth_order = hierarchy_order == :asc ? :desc : :asc

          all
            .select(Arel.star, 'array_length(traversal_ids, 1) as depth')
            .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
          unscoped.without_sti_condition.from(all, :namespaces)
        end

        private

        def use_traversal_ids?
          Feature.enabled?(:use_traversal_ids, default_enabled: :yaml)
        end

        def use_traversal_ids_for_ancestor_scopes?
          Feature.enabled?(:use_traversal_ids_for_ancestor_scopes, default_enabled: :yaml) &&
          use_traversal_ids?
        end

        def self_and_descendants_with_duplicates(include_self: true)
          base_ids = select(:id)

          records = unscoped
            .without_sti_condition
            .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
      end
    end
  end
end