module Clear::SQL::Query::Pluck
  # Select a specific column of your SQL query, execute the query
  # and return an array containing this field.
  #
  # ```crystal
  # User.query.pluck_col("id") # [1,2,3,4...]
  # ```
  #
  # Note: It returns an array of `Clear::SQL::Any`. Therefore, you may want to use `pluck_col(str, Type)` to return
  #       an array of `Type`:
  #
  # ```crystal
  # User.query.pluck_col("id", Int64)
  # ```
  #
  # The field argument is a SQL fragment; it's not escaped (beware SQL injection) and allow call to functions
  # and aggregate methods:
  #
  #  ```crystal
  #    # ...
  #    User.query.pluck_col("CASE WHEN id % 2 = 0 THEN id ELSE NULL END AS id").each do
  #    # ...
  #  ```
  def pluck_col(field : Clear::SQL::Symbolic)
    field = Clear::SQL.escape(field) if field.is_a?(Symbol)

    sql = self.clear_select.select(field).to_sql

    Clear::SQL::ConnectionPool.with_connection(connection_name) do |cnx|
      rs = Clear::SQL.log_query(sql) { cnx.query(sql) }

      o = [] of Clear::SQL::Any

      while rs.move_next
        o << rs.read
      end
      o
    ensure
      rs.try &.close
    end
  end

  # :ditto:
  def pluck_col(field : Clear::SQL::Symbolic, type : T.class) forall T
    field = Clear::SQL.escape(field) if field.is_a?(Symbol)

    sql = self.clear_select.select(field).to_sql

    Clear::SQL::ConnectionPool.with_connection(connection_name) do |cnx|
      rs = Clear::SQL.log_query(sql) { cnx.query(sql) }

      o = [] of T

      while rs.move_next
        o << rs.read(T)
      end

      o
    ensure
      rs.try &.close
    end
  end

  # Select specifics columns and return an array of Tuple(*Clear::SQL::Any) containing the columns in the order of the selected
  # arguments:
  #
  # ```crystal
  # User.query.pluck("first_name", "last_name").each do |(first_name, last_name)|
  #   # ...
  # end
  # ```
  def pluck(*fields)
    pluck(fields)
  end

  # :ditto:
  def pluck(fields : Tuple(*T)) forall T
    select_clause = fields.map { |f| f.is_a?(Symbol) ? Clear::SQL.escape(f) : f.to_s }.join(", ")
    sql = self.clear_select.select(select_clause).to_sql

    Clear::SQL::ConnectionPool.with_connection(connection_name) do |cnx|
      rs = Clear::SQL.log_query(sql) { cnx.query(sql) }

      {% begin %}
        o = [] of Tuple({% for t in T %}Clear::SQL::Any,{% end %})

        while rs.move_next
            o << { {% for t in T %} rs.read, {% end %} }
        end
        o
      {% end %}
    ensure
      rs.try &.close
    end
  end

  # Select specifics columns and returns on array of tuple of type of the named tuple passed as parameter:
  #
  # ```crystal
  #   User.query.pluck(id: Int64, "UPPER(last_name)": String).each do #...
  # ```
  def pluck(**fields : **T) forall T
    sql = clear_select.select(fields.keys.join(", ")).to_sql

    Clear::SQL::ConnectionPool.with_connection(connection_name) do |cnx|
      rs = Clear::SQL.log_query(sql) { cnx.query(sql) }

      {% begin %}
        o = [] of Tuple({% for k, v in T %}{{v.instance}},{% end %})

        while rs.move_next
          o << { {% for k, v in T %} rs.read({{v.instance}}), {% end %}}
        end
        o
      {% end %}
    ensure
      rs.try &.close
    end
  end
end
