66 lines
		
	
	
		
			1.8 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			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
 |