gitlab-org--gitlab-foss/lib/gitlab/sql/cte.rb

66 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