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


                         
                                    


                              
                                 

         



                           
                    
                                                                                       
                                                                                              


                                                                   
                                                                              
            


                                                               
                         
                            




                       
                                      












                                                                       

                                 
                                                                      

                                                                       
                                                    
                                     

         


                     


                                                                                                             

         



                                    
                                              

                                                                         
            
                                                                  



                                             
                                                          


                                              
                                            






                                                        
                                       
                                          
                                                                                                                                                     

         



                                         

                     
                                             



                                     



                       





                               
                        

                                                                       



                                       

                                                                                                  

                        
                                    




                                     

                                                                                                                  
 
                              








                                                                   

                                                                                                                                                     

         



       
module Arel
  module SqlCompiler
    class GenericCompiler
      attr_reader :relation, :engine

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

      def christener
        relation.christener
      end

      def select_sql
        if relation.projections.first.is_a?(Count) && relation.projections.size == 1 &&
          (relation.taken.present? || relation.wheres.present?) && relation.joins(self).blank?
          subquery = [
            "SELECT 1 FROM #{relation.from_clauses}", build_clauses
          ].join ' '
          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   = relation.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 #{relation.table_sql}",
          ("WHERE #{relation.wheres.collect { |x| x.to_sql }.join(' AND ')}" unless relation.wheres.blank? ),
          (add_limit_on_delete(relation.taken)                        unless relation.taken.blank?  )
      end

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

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

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

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

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

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

      def supports_insert_with_returning?
        false
      end

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

      protected

      def locked
        relation.locked
      end

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

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

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

      def build_update_conditions_sql
        conditions = ""
        conditions << " WHERE #{relation.wheres.map { |x| x.to_sql }.join(' AND ')}" unless relation.wheres.blank?
        conditions << " ORDER BY #{relation.order_clauses.join(', ')}" unless relation.orders.blank?

        taken = relation.taken
        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.connection.quote_column_name(relation.primary_key)
        "WHERE #{quoted_primary_key} IN (SELECT #{quoted_primary_key} FROM #{@engine.connection.quote_table_name relation.table.name} #{conditions})"
      end

    end

  end
end