65 lines
1.8 KiB
Ruby
65 lines
1.8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Gitlab
|
|
module SQL
|
|
# Class for easily building CTE statements.
|
|
#
|
|
# Example:
|
|
#
|
|
# cte = CTE.new(:my_cte_name)
|
|
# ns = Arel::Table.new(:namespaces)
|
|
#
|
|
# cte << Namespace.
|
|
# where(ns[:parent_id].eq(some_namespace_id))
|
|
#
|
|
# Namespace
|
|
# with(cte.to_arel).
|
|
# from(cte.alias_to(ns))
|
|
#
|
|
# To skip materialization of the CTE query by passing materialized: false
|
|
# More context: https://www.postgresql.org/docs/12/queries-with.html
|
|
#
|
|
# cte = CTE.new(:my_cte_name, materialized: false)
|
|
#
|
|
class CTE
|
|
attr_reader :table, :query
|
|
|
|
# name - The name of the CTE as a String or Symbol.
|
|
def initialize(name, query, materialized: true)
|
|
@table = Arel::Table.new(name)
|
|
@query = query
|
|
@materialized = materialized
|
|
end
|
|
|
|
# Returns the Arel relation for this CTE.
|
|
def to_arel
|
|
sql = Arel::Nodes::SqlLiteral.new("(#{query_as_sql})")
|
|
|
|
Gitlab::Database::AsWithMaterialized.new(table, sql, materialized: @materialized)
|
|
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, alias_table)
|
|
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(to_arel)
|
|
.from(alias_to(relation.model.arel_table))
|
|
end
|
|
|
|
private
|
|
|
|
def query_as_sql
|
|
query.is_a?(String) ? query : query.to_sql
|
|
end
|
|
end
|
|
end
|
|
end
|