# 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