module Arel
  class SelectManager < Arel::TreeManager
    AR_CA_SQLSA_NAME = 'ActiveRecord::ConnectionAdapters::SQLServerAdapter'.freeze

    # Getting real Ordering objects is very important for us. We need to be able to call #uniq on
    # a colleciton of them reliably as well as using their true object attributes to mutate them
    # to grouping objects for the inner sql during a select statment with an offset/rownumber. So this
    # is here till ActiveRecord & ARel does this for us instead of using SqlLiteral objects.
    alias_method :order_without_sqlserver, :order
    def order(*expr)
      return order_without_sqlserver(*expr) unless engine_activerecord_sqlserver_adapter?
      @ast.orders.concat(expr.map do |x|
        case x
        when Arel::Attributes::Attribute
          table = Arel::Table.new(x.relation.table_alias || x.relation.name)
          e = table[x.name]
          Arel::Nodes::Ascending.new e
        when Arel::Nodes::Ordering
          x
        when String
          x.split(',').map do |s|
            s = x if x.strip =~ /\A\b\w+\b\(.*,.*\)(\s+(ASC|DESC))?\Z/i # Allow functions with comma(s) to pass thru.
            s.strip!
            d = s =~ /(ASC|DESC)\Z/i ? Regexp.last_match[1].upcase : nil
            e = d.nil? ? s : s.mb_chars[0...-d.length].strip
            e = Arel.sql(e)
            d && d == 'DESC' ? Arel::Nodes::Descending.new(e) : Arel::Nodes::Ascending.new(e)
          end
        else
          e = Arel.sql(x.to_s)
          Arel::Nodes::Ascending.new e
        end
      end.flatten)
      self
    end

    # A friendly over ride that allows us to put a special lock object that can have a default or pass
    # custom string hints down. See the visit_Arel_Nodes_LockWithSQLServer delegation method.
    alias_method :lock_without_sqlserver, :lock
    def lock(locking = true)
      if engine_activerecord_sqlserver_adapter?
        case locking
        when true
          locking = Arel.sql('WITH(HOLDLOCK, ROWLOCK)')
        when Arel::Nodes::SqlLiteral
        when String
          locking = Arel.sql locking
        end
        @ast.lock = Arel::Nodes::Lock.new(locking)
        self
      else
        lock_without_sqlserver(locking)
      end
    end

    private

    def engine_activerecord_sqlserver_adapter?
      @engine.connection && @engine.connection.class.name == AR_CA_SQLSA_NAME
    end
  end
end