aboutsummaryrefslogblamecommitdiffstats
path: root/lib/arel/engines/sql/relations/compiler.rb
blob: 2941fc2a06dfc1fbec054aed79f59643b7a63185 (plain) (tree)
1
2
3
4
5
6
7
8






                              
                                 


                    

                                                                                       
                                                                                
                                                                              
            


                                                               
                         
                            




                       













                                                                       

                                 
                                                                      

                                                                       
                                                    
                                     

         





                                                                                    

         



                                    

























                                                                                                                                 



                                         






                                     
             





                                                                            





                               
































                                                                                                                                           



       
module Arel
  module SqlCompiler
    class GenericCompiler
      attr_reader :relation

      def initialize(relation)
        @relation = relation
        @engine = relation.engine
      end

      def select_sql
        if relation.projections.first.is_a?(Count) && relation.projections.size == 1 &&
          (taken.present? || wheres.present?) && joins(self).blank?
          subquery = build_query("SELECT 1 FROM #{from_clauses}", build_clauses)
          query = "SELECT COUNT(*) AS count_id FROM (#{subquery}) AS subquery"
        else
          query = [
            "SELECT     #{relation.select_clauses.join(', ')}",
            "FROM       #{relation.from_clauses}",
            build_clauses
          ].compact.join ' '
        end
        query
      end

      def build_clauses
        joins   = joins(self)
        wheres  = relation.where_clauses
        groups  = relation.group_clauses
        havings = relation.having_clauses
        orders  = relation.order_clauses

        clauses = [ "",
          joins,
          ("WHERE     #{wheres.join(' AND ')}" unless wheres.empty?),
          ("GROUP BY  #{groups.join(', ')}" unless groups.empty?),
          ("HAVING    #{havings.join(' AND ')}" unless havings.empty?),
          ("ORDER BY  #{orders.join(', ')}" unless orders.empty?)
        ].compact.join ' '

        offset = relation.skipped
        limit = relation.taken
        @engine.connection.add_limit_offset!(clauses, :limit => limit,
                                  :offset => offset) if offset || limit

        clauses << " #{locked}" unless locked.blank?
        clauses unless clauses.blank?
      end

      def delete_sql
        build_query \
          "DELETE",
          "FROM #{table_sql}",
          ("WHERE #{wheres.collect(&:to_sql).join(' AND ')}" unless wheres.blank? ),
          (add_limit_on_delete(taken)                        unless taken.blank?  )
      end

      def add_limit_on_delete(taken)
        "LIMIT #{taken}"
      end

      def insert_sql(include_returning = true)
        insertion_attributes_values_sql = if record.is_a?(Value)
          record.value
        else
          attributes = record.keys.sort_by do |attribute|
            attribute.name.to_s
          end

          first = attributes.collect do |key|
            engine.quote_column_name(key.name)
          end.join(', ')

          second = attributes.collect do |key|
            key.format(record[key])
          end.join(', ')

          build_query "(#{first})", "VALUES (#{second})"
        end

        build_query \
          "INSERT",
          "INTO #{table_sql}",
          insertion_attributes_values_sql,
          ("RETURNING #{engine.quote_column_name(primary_key)}" if include_returning && compiler.supports_insert_with_returning?)
      end

      def supports_insert_with_returning?
        false
      end

      def update_sql
        build_query \
          "UPDATE #{table_sql} SET",
          assignment_sql,
          build_update_conditions_sql
      end

    protected
      def method_missing(method, *args)
        if block_given?
          relation.send(method, *args)  { |*block_args| yield(*block_args) }
        else
          relation.send(method, *args)
        end
      end

      def build_query(*parts)
        parts.compact.join(" ")
      end

      def assignment_sql
        if assignments.respond_to?(:collect)
          attributes = assignments.keys.sort_by do |attribute|
            attribute.name.to_s
          end

          attributes.map do |attribute|
            value = assignments[attribute]
            "#{engine.quote_column_name(attribute.name)} = #{attribute.format(value)}"
          end.join(", ")
        else
          assignments.value
        end
      end

      def build_update_conditions_sql
        conditions = ""
        conditions << " WHERE #{wheres.collect(&:to_sql).join(' AND ')}" unless wheres.blank?
        conditions << " ORDER BY #{order_clauses.join(', ')}" unless orders.blank?

        unless taken.blank?
          conditions = limited_update_conditions(conditions, taken)
        end

        conditions
      end

      def limited_update_conditions(conditions, taken)
        conditions << " LIMIT #{taken}"
        quoted_primary_key = engine.quote_column_name(primary_key)
        "WHERE #{quoted_primary_key} IN (SELECT #{quoted_primary_key} FROM #{engine.connection.quote_table_name table.name} #{conditions})"
      end

    end

  end
end