/*
 * Copyright (c) 2008, 2018, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2.0, as
 * published by the Free Software Foundation.
 *
 * This program is also distributed with certain software (including
 * but not limited to OpenSSL) that is licensed under separate terms,
 * as designated in a particular file or component or in included license
 * documentation.  The authors of MySQL hereby grant you an
 * additional permission to link the program and your derivative works
 * with the separately licensed software that they have included with
 * MySQL.
 *
 * Without limiting anything contained in the foregoing, this file,
 * which is part of MySQL Connector/C++, is also subject to the
 * Universal FOSS Exception, version 1.0, a copy of which can be found at
 * http://oss.oracle.com/licenses/universal-foss-exception.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License, version 2.0, for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
 */




#include <cppconn/exception.h>
#include "mysql_util.h"
#include "mysql_debug.h"
#include "mysql_resultbind.h"

#include "nativeapi/native_statement_wrapper.h"
#include "nativeapi/native_resultset_wrapper.h"

#include <string.h>

#include <boost/scoped_ptr.hpp>

namespace sql
{
namespace mysql
{

struct st_buffer_size_type
{
  char * buffer;
  size_t size;
  enum_field_types type;
  st_buffer_size_type(char * b, size_t s, enum_field_types t) : buffer(b), size(s), type(t) {}
};


/* {{{ allocate_buffer_for_field() -I- */
typedef std::pair<char *, size_t> BufferSizePair;
static struct st_buffer_size_type
    allocate_buffer_for_field(const MYSQL_FIELD * const field)
{
  switch (field->type)
  {
    case MYSQL_TYPE_NULL:
      return st_buffer_size_type(NULL, 0, field->type);
    case MYSQL_TYPE_TINY:
      return st_buffer_size_type(new char[1], 1, field->type);
    case MYSQL_TYPE_SHORT:
      return st_buffer_size_type(new char[2], 2, field->type);
    case MYSQL_TYPE_INT24:
    case MYSQL_TYPE_LONG:
    case MYSQL_TYPE_FLOAT:
      return st_buffer_size_type(new char[4], 4, field->type);
    case MYSQL_TYPE_DOUBLE:
    case MYSQL_TYPE_LONGLONG:
      return st_buffer_size_type(new char[8], 8, field->type);
    case MYSQL_TYPE_YEAR:
      return st_buffer_size_type(new char[2], 2, MYSQL_TYPE_SHORT);
    case MYSQL_TYPE_TIMESTAMP:
    case MYSQL_TYPE_DATE:
    case MYSQL_TYPE_TIME:
    case MYSQL_TYPE_DATETIME:
      return st_buffer_size_type(new char[sizeof(MYSQL_TIME)], sizeof(MYSQL_TIME), field->type);


    case MYSQL_TYPE_TINY_BLOB:
    case MYSQL_TYPE_MEDIUM_BLOB:
    case MYSQL_TYPE_LONG_BLOB:
    case MYSQL_TYPE_BLOB:
    case MYSQL_TYPE_STRING:
    case MYSQL_TYPE_VAR_STRING:
#if LIBMYSQL_VERSION_ID > 50700
    case MYSQL_TYPE_JSON:
#endif //LIBMYSQL_VERSION_ID > 50700
      return st_buffer_size_type(new char[field->max_length + 1], field->max_length + 1, field->type);

    case MYSQL_TYPE_DECIMAL:
    case MYSQL_TYPE_NEWDECIMAL:
      return st_buffer_size_type(new char[64], 64, field->type);
#if A1
    case MYSQL_TYPE_TIMESTAMP:
    case MYSQL_TYPE_YEAR:
      return st_buffer_size_type(new char[10], 10, field->type);
#endif
#if A0
      // There two are not sent over the wire
    case MYSQL_TYPE_ENUM:
    case MYSQL_TYPE_SET:
#endif
    case MYSQL_TYPE_BIT:
      return st_buffer_size_type(new char[8], 8, MYSQL_TYPE_BIT);
    case MYSQL_TYPE_GEOMETRY:
    default:
      // TODO: Andrey, there can be crashes when we go through this. Please fix.
      throw sql::InvalidArgumentException("allocate_buffer_for_field: invalid rbind data type");
  }
}
/* }}} */


/* {{{ MySQL_ResultBind::MySQL_ResultBind -I- */
MySQL_ResultBind::MySQL_ResultBind(boost::shared_ptr< NativeAPI::NativeStatementWrapper > & stmt,
                                   boost::shared_ptr< MySQL_DebugLogger > & log)
  : num_fields(0), is_null(NULL), err(NULL), len(NULL), proxy(stmt), logger(log), rbind(NULL)
{
}
/* }}} */


/* {{{ MySQL_ResultBind::~MySQL_ResultBind() -I- */
MySQL_ResultBind::~MySQL_ResultBind()
{
  if (rbind.get()) {
    for (unsigned int i = 0; i < num_fields; ++i) {
      delete[] (char *) rbind[i].buffer;
    }
  }
}
/* }}} */


/* {{{ MySQL_ResultBind::bindResult() -I- */
void MySQL_ResultBind::bindResult()
{
  CPP_ENTER("MySQL_Prepared_Statement::bindResult");
  for (unsigned int i = 0; i < num_fields; ++i) {
    delete[] (char *) rbind[i].buffer;
  }
  rbind.reset(NULL);
  is_null.reset(NULL);
  err.reset(NULL);
  len.reset(NULL);

  num_fields = proxy->field_count();
  if (!num_fields) {
    return;
  }

  rbind.reset(new MYSQL_BIND[num_fields]);
  memset(rbind.get(), 0, sizeof(MYSQL_BIND) * num_fields);

  is_null.reset(new my_bool[num_fields]);
  memset(is_null.get(), 0, sizeof(my_bool) * num_fields);

  err.reset(new my_bool[num_fields]);
  memset(err.get(), 0, sizeof(my_bool) * num_fields);

  len.reset(new unsigned long[num_fields]);
  memset(len.get(), 0, sizeof(unsigned long) * num_fields);

  boost::scoped_ptr< NativeAPI::NativeResultsetWrapper > resultMeta(proxy->result_metadata());

  for (unsigned int i = 0; i < num_fields; ++i) {
    MYSQL_FIELD * field = resultMeta->fetch_field();

    struct st_buffer_size_type p = allocate_buffer_for_field(field);
    rbind[i].buffer_type= p.type;
    rbind[i].buffer		= p.buffer;
    rbind[i].buffer_length= static_cast<unsigned long>(p.size);
    rbind[i].length		= &len[i];
    rbind[i].is_null	= &is_null[i];
    rbind[i].error		= &err[i];
    rbind[i].is_unsigned= field->flags & UNSIGNED_FLAG;
  }
  if (proxy->bind_result(rbind.get())) {
    CPP_ERR_FMT("Couldn't bind : %d:(%s) %s", proxy->errNo(), proxy->sqlstate().c_str(), proxy->error().c_str());
    sql::mysql::util::throwSQLException(*proxy.get());
  }
}
/* }}} */


} /* namespace mysql */
} /* namespace sql */

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */

