blob: 607ce10d77815e4e2c556709f3fe596aba21a7bb (
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
|
# frozen_string_literal: true
module Gitlab
module SQL
# Class for easily building recursive CTE statements.
#
# Example:
#
# cte = RecursiveCTE.new(:my_cte_name)
# ns = Arel::Table.new(:namespaces)
#
# cte << Namespace.
# where(ns[:parent_id].eq(some_namespace_id))
#
# cte << Namespace.
# from([ns, cte.table]).
# where(ns[:parent_id].eq(cte.table[:id]))
#
# Namespace.with.
# recursive(cte.to_arel).
# from(cte.alias_to(ns))
class RecursiveCTE
attr_reader :table
# name - The name of the CTE as a String or Symbol.
# union_args - The arguments supplied to Gitlab::SQL::Union class when building inner recursive query
def initialize(name, union_args: {})
@table = Arel::Table.new(name)
@queries = []
@union_args = union_args
end
# Adds a query to the body of the CTE.
#
# relation - The relation object to add to the body of the CTE.
def <<(relation)
@queries << relation
end
# Returns the Arel relation for this CTE.
def to_arel
sql = Arel::Nodes::SqlLiteral.new(Union.new(@queries, **@union_args).to_sql)
Arel::Nodes::As.new(table, Arel::Nodes::Grouping.new(sql))
end
# Returns an "AS" statement that aliases the CTE name as the given table
# name. This allows one to trick ActiveRecord into thinking it's selecting
# from an actual table, when in reality it's selecting from a CTE.
#
# alias_table - The Arel table to use as the alias.
def alias_to(alias_table)
Arel::Nodes::As.new(table, Arel::Table.new(alias_table.name.tr('.', '_')))
end
# Applies the CTE to the given relation, returning a new one that will
# query from it.
def apply_to(relation)
relation.except(:where)
.with
.recursive(to_arel)
.from(alias_to(relation.model.arel_table))
end
end
end
end
|