// Copyright (c) 2000, 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/ODBC, 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

/**
  @file  error.c
  @brief Error handling functions.
*/

#include "driver.h"
#include "mysqld_error.h"
#include "errmsg.h"

/*
  @type    : myodbc3 internal error structure
  @purpose : set of internal errors, in the following order
  - SQLSTATE2 (when version is SQL_OV_ODBC2)
  - SQLSTATE3 (when version is SQL_OV_ODBC3)
  - error message text
  - return code
*/

static MYODBC3_ERR_STR myodbc3_errors[]=
{
  {"01000","General warning", SQL_SUCCESS_WITH_INFO},
  {"01004","String data, right truncated", SQL_SUCCESS_WITH_INFO},
  {"01S02","Option value changed", SQL_SUCCESS_WITH_INFO},
  {"01S03","No rows updated/deleted", SQL_SUCCESS_WITH_INFO},
  {"01S04","More than one row updated/deleted", SQL_SUCCESS_WITH_INFO},
  {"01S06","Attempt to fetch before the result set returned the first rowset",
    SQL_SUCCESS_WITH_INFO},
  {"07001","SQLBindParameter not used for all parameters", SQL_ERROR},
  {"07005","Prepared statement not a cursor-specification", SQL_ERROR},
  {"07006","Restricted data type attribute violation", SQL_ERROR},
  {"07009","Invalid descriptor index", SQL_ERROR},
  {"08002","Connection name in use", SQL_ERROR},
  {"08003","Connection does not exist", SQL_ERROR},
  {"24000","Invalid cursor state", SQL_ERROR},
  {"25000","Invalid transaction state", SQL_ERROR},
  {"25S01","Transaction state unknown", SQL_ERROR},
  {"34000","Invalid cursor name", SQL_ERROR},
  {"HYT00","Timeout expired", SQL_ERROR},
  {"HY000","General driver defined error", SQL_ERROR},
  {"HY001","Memory allocation error", SQL_ERROR},
  {"HY002","Invalid column number", SQL_ERROR},
  {"HY003","Invalid application buffer type", SQL_ERROR},
  {"HY004","Invalid SQL data type", SQL_ERROR},
  {"HY007","Associated statement is not prepared", SQL_ERROR},
  {"HY009","Invalid use of null pointer", SQL_ERROR},
  {"HY010","Function sequence error", SQL_ERROR},
  {"HY011","Attribute can not be set now", SQL_ERROR},
  {"HY012","Invalid transaction operation code", SQL_ERROR},
  {"HY013","Memory management error", SQL_ERROR},
  {"HY015","No cursor name available", SQL_ERROR},
  {"HY016","Cannot modify an implementation row descriptor", SQL_ERROR},
  {"HY017","Invalid use of an automatically allocated descriptor handle", SQL_ERROR},
  {"HY024","Invalid attribute value", SQL_ERROR},
  {"HY090","Invalid string or buffer length", SQL_ERROR},
  {"HY091","Invalid descriptor field identifier", SQL_ERROR},
  {"HY092","Invalid attribute/option identifier", SQL_ERROR},
  {"HY093","Invalid parameter number", SQL_ERROR},
  {"HY095","Function type out of range", SQL_ERROR},
  {"HY106","Fetch type out of range", SQL_ERROR},
  {"HY107","Row value out of range", SQL_ERROR},
  {"HY109","Invalid cursor position", SQL_ERROR},
  {"HYC00","Optional feature not implemented", SQL_ERROR},
  /* server related..*/
  {"21S01","Column count does not match value count", SQL_ERROR},
  {"23000","Integrity constraint violation", SQL_ERROR},
  {"42000","Syntax error or access violation", SQL_ERROR},
  {"42S01","Base table or view already exists ", SQL_ERROR},
  {"42S02","Base table or view not found", SQL_ERROR},
  {"42S12","Index not found", SQL_ERROR},
  {"42S21","Column already exists", SQL_ERROR},
  {"42S22","Column not found", SQL_ERROR},
  {"08S01","Communication link failure", SQL_ERROR},
  {"08004","Server rejected the connection", SQL_ERROR},
};


/*
  @type    : myodbc3 internal
  @purpose : If ODBC version is SQL_OV_ODBC2, initialize old SQLSTATEs
*/

void myodbc_sqlstate2_init(void)
{
    /*
      As accessing the SQLSTATE2 is very rare, set this to
      global when required .. one time ..for quick processing
      in set_error/set_conn_error
    */
    uint  i;
    for ( i= MYERR_S1000; i <= MYERR_S1C00; ++i )
    {
        myodbc3_errors[i].sqlstate[0]= 'S';
        myodbc3_errors[i].sqlstate[1]= '1';
    }
    myodbc_stpmov(myodbc3_errors[MYERR_07005].sqlstate,"24000");
    myodbc_stpmov(myodbc3_errors[MYERR_42000].sqlstate,"37000");
    myodbc_stpmov(myodbc3_errors[MYERR_42S01].sqlstate,"S0001");
    myodbc_stpmov(myodbc3_errors[MYERR_42S02].sqlstate,"S0002");
    myodbc_stpmov(myodbc3_errors[MYERR_42S12].sqlstate,"S0012");
    myodbc_stpmov(myodbc3_errors[MYERR_42S21].sqlstate,"S0021");
    myodbc_stpmov(myodbc3_errors[MYERR_42S22].sqlstate,"S0022");
}


/*
  @type    : myodbc3 internal
  @purpose : If ODBC version is SQL_OV_ODBC3, initialize to original SQLSTATEs
*/

void myodbc_sqlstate3_init(void)
{
    uint i;

    for ( i= MYERR_S1000; i <= MYERR_S1C00; ++i )
    {
        myodbc3_errors[i].sqlstate[0]= 'H';
        myodbc3_errors[i].sqlstate[1]= 'Y';
    }
    myodbc_stpmov(myodbc3_errors[MYERR_07005].sqlstate,"07005");
    myodbc_stpmov(myodbc3_errors[MYERR_42000].sqlstate,"42000");
    myodbc_stpmov(myodbc3_errors[MYERR_42S01].sqlstate,"42S01");
    myodbc_stpmov(myodbc3_errors[MYERR_42S02].sqlstate,"42S02");
    myodbc_stpmov(myodbc3_errors[MYERR_42S12].sqlstate,"42S12");
    myodbc_stpmov(myodbc3_errors[MYERR_42S21].sqlstate,"42S21");
    myodbc_stpmov(myodbc3_errors[MYERR_42S22].sqlstate,"42S22");
}

MYERROR::MYERROR(myodbc_errid errid, const char* errtext, SQLINTEGER errcode,
  const char* prefix)
{
  std::string errmsg;

  errmsg = (errtext ? errtext : myodbc3_errors[errid].message);
  native_error = errcode ? (myodbc_errid)errcode : errid + MYODBC_ERROR_CODE_START;

  retcode = myodbc3_errors[errid].retcode;
  sqlstate = myodbc3_errors[errid].sqlstate;
  message = prefix + errmsg;
}

MYERROR::MYERROR(const char* state, const char* msg, SQLINTEGER errcode,
  const char* prefix)
{
  sqlstate = state ? state : "";
  message = std::string(prefix ? prefix : MYODBC_ERROR_PREFIX) +
    (msg ? msg : "");
  native_error = errcode;
  retcode = SQL_ERROR;
}

/*
  @type    : myodbc3 internal
  @purpose : sets the descriptor level errors
*/

SQLRETURN set_desc_error(DESC *        desc,
                         char *        state,
                         const char *  message,
                         uint          errcode)
{
  desc->error = MYERROR(state, message, errcode, MYODBC_ERROR_PREFIX);
  return SQL_ERROR;
}


/*
  @type    : myodbc3 internal
  @purpose : translates SQL error to ODBC error
*/

void translate_error(char *save_state, myodbc_errid errid, uint mysql_err)
{
    char *state= myodbc3_errors[errid].sqlstate;

    switch (mysql_err)
    {
        case ER_WRONG_VALUE_COUNT:
            state= "21S01";
            break;
        case ER_DUP_ENTRY:
        case ER_DUP_KEY:
            state= "23000";
            break;
        case ER_NO_DB_ERROR:
            state= "3D000";
            break;
        case ER_PARSE_ERROR:
#ifdef ER_SP_DOES_NOT_EXIST
        case ER_SP_DOES_NOT_EXIST:
#endif
            state= myodbc3_errors[MYERR_42000].sqlstate;
            break;
        case ER_TABLE_EXISTS_ERROR:
            state= myodbc3_errors[MYERR_42S01].sqlstate;
            break;
        case ER_FILE_NOT_FOUND:
        case ER_NO_SUCH_TABLE:
        case ER_CANT_OPEN_FILE:
        case ER_BAD_TABLE_ERROR:
            state= myodbc3_errors[MYERR_42S02].sqlstate;
            break;
        case ER_NO_SUCH_INDEX:
        case ER_CANT_DROP_FIELD_OR_KEY:
            state= myodbc3_errors[MYERR_42S12].sqlstate;
            break;
        case ER_DUP_FIELDNAME:
            state= myodbc3_errors[MYERR_42S21].sqlstate;
            break;
        case ER_BAD_FIELD_ERROR:
            state= myodbc3_errors[MYERR_42S22].sqlstate;
            break;
        case CR_SERVER_HANDSHAKE_ERR:
        case CR_CONNECTION_ERROR:
        case CR_SERVER_GONE_ERROR:
        case CR_SERVER_LOST:
#if MYSQL_VERSION_ID > 80023
        case ER_CLIENT_INTERACTION_TIMEOUT:
#endif
            state= "08S01";
            break;
        case ER_MUST_CHANGE_PASSWORD_LOGIN:
        case CR_AUTH_PLUGIN_CANNOT_LOAD_ERROR:
            state= "08004"; /* Server rejected the connection */
                            /* The data source rejected the establishment of the
                               connection for implementation-defined reasons. */
            break;
        default: break;
    }
    myodbc_stpmov(save_state, state);
}


/*
  @type    : myodbc3 internal
  @purpose : sets the error information to environment handle.
*/

SQLRETURN set_env_error(ENV *env, myodbc_errid errid, const char *errtext,
                        SQLINTEGER errcode)
{
  env->error = MYERROR(errid, errtext, errcode, MYODBC_ERROR_PREFIX);
  return env->error.retcode;
}


/*
  @type    : myodbc3 internal
  @purpose : sets the error information to connection handle.
*/

SQLRETURN set_conn_error(DBC *dbc, myodbc_errid errid, const char *errtext,
                         SQLINTEGER errcode)
{
  dbc->error = MYERROR(errid, errtext, errcode, MYODBC_ERROR_PREFIX);
  return dbc->error.retcode;
}


/*
  @type    : myodbc3 internal
  @purpose : sets a myodbc_malloc() failure on a MYSQL* connection
*/
void set_mem_error(MYSQL *mysql)
{
  mysql->net.last_errno= CR_OUT_OF_MEMORY;
  myodbc_stpmov(mysql->net.last_error, "Memory allocation failed");
  myodbc_stpmov(mysql->net.sqlstate, "HY001");
}


/**
  Handle a connection-related error.

  @param[in]  stmt  Statement
*/
SQLRETURN handle_connection_error(STMT *stmt)
{
  unsigned int err= mysql_errno(stmt->dbc->mysql);
  switch (err) {
  case 0:  /* no error */
    return SQL_SUCCESS;
  case CR_SERVER_GONE_ERROR:
  case CR_SERVER_LOST:
#if MYSQL_VERSION_ID > 80023
  case ER_CLIENT_INTERACTION_TIMEOUT:
#endif
    return stmt->set_error("08S01", mysql_error(stmt->dbc->mysql), err);
  case CR_OUT_OF_MEMORY:
    return stmt->set_error("HY001", mysql_error(stmt->dbc->mysql), err);
  case CR_COMMANDS_OUT_OF_SYNC:
  case CR_UNKNOWN_ERROR:
  default:
    return stmt->set_error("HY000", mysql_error(stmt->dbc->mysql), err);
  }
}


/*
  Little helper to check if error code means that we have lost connection
  @param[in]  errcode  code of the last error
*/
my_bool is_connection_lost(uint errcode)
{
  if (errcode==CR_SERVER_GONE_ERROR || errcode==CR_SERVER_LOST
#if MYSQL_VERSION_ID > 80023
    || errcode==ER_CLIENT_INTERACTION_TIMEOUT
#endif
  )
  {
    return 1;
  }

  return 0;
}

/*
  @type    : myodbc3 internal
  @purpose : sets the error information to appropriate handle.
  it also sets the SQLSTATE based on the ODBC VERSION
*/

SQLRETURN set_handle_error(SQLSMALLINT HandleType, SQLHANDLE handle,
                           myodbc_errid errid, const char *errtext,
                           SQLINTEGER errcode)
{
  switch ( HandleType )
  {
    case SQL_HANDLE_ENV:
      return set_env_error((ENV*)handle, errid, errtext, errcode);
    case SQL_HANDLE_DBC:
      return set_conn_error((DBC*)handle, errid, errtext, errcode);
    case SQL_HANDLE_STMT:
    {
      STMT *stmt = (STMT*)handle;
      stmt->error = MYERROR(errid, errtext, errcode, stmt->dbc->st_error_prefix);
      return stmt->error.retcode;
    }
    case SQL_HANDLE_DESC:
    {
      DESC* desc = (DESC*)handle;
      desc->error = MYERROR(errid, errtext, errcode, desc->stmt->dbc->st_error_prefix);
      return desc->error.retcode;
    }
    default:
      return SQL_INVALID_HANDLE;
  }
}


/**
*/
SQLRETURN SQL_API
MySQLGetDiagRec(SQLSMALLINT handle_type, SQLHANDLE handle, SQLSMALLINT record,
                SQLCHAR **sqlstate, SQLINTEGER *native, SQLCHAR **message)
{
  MYERROR *error;
  SQLINTEGER tmp_native;

  if (!native)
    native= &tmp_native;

  if (!handle || record <= 0)
    return SQL_ERROR;

  /*
    Currently we are not supporting error list, so
    if RecNumber > 1, return no data found
  */
  if (record > 1)
    return SQL_NO_DATA_FOUND;

  if (handle_type == SQL_HANDLE_STMT)
    error= &((STMT *)handle)->error;
  else if (handle_type == SQL_HANDLE_DBC)
    error= &((DBC *)handle)->error;
  else if (handle_type == SQL_HANDLE_ENV)
    error= &((ENV *)handle)->error;
  else if (handle_type == SQL_HANDLE_DESC)
    error= &((DESC *)handle)->error;
  else
    return SQL_INVALID_HANDLE;

  if (error->message.empty())
  {
    *message= (SQLCHAR *)"";
    *sqlstate= (SQLCHAR *)"00000";
    *native= 0;
    return SQL_NO_DATA_FOUND;
  }

  *message= (SQLCHAR *)error->message.c_str();
  *sqlstate= (SQLCHAR *)error->sqlstate.c_str();
  *native= error->native_error;

  return SQL_SUCCESS;
}


bool is_odbc3_subclass(std::string sqlstate)
{
  char *states[]= { "01S00", "01S01", "01S02", "01S06", "01S07", "07S01",
    "08S01", "21S01", "21S02", "25S01", "25S02", "25S03", "42S01", "42S02",
    "42S11", "42S12", "42S21", "42S22", "HY095", "HY097", "HY098", "HY099",
    "HY100", "HY101", "HY105", "HY107", "HY109", "HY110", "HY111", "HYT00",
    "HYT01", "IM001", "IM002", "IM003", "IM004", "IM005", "IM006", "IM007",
    "IM008", "IM010", "IM011", "IM012"};
  size_t i;

  if (sqlstate.empty())
    return false;

  for (i= 0; i < sizeof(states) / sizeof(states[0]); ++i)
    if (sqlstate.compare(states[i]) == 0)
      return true;

  return false;
}


/*
  @type    : ODBC 3.0 API
  @purpose : returns the current value of a field of a record of the
  diagnostic data structure (associated with a specified handle)
  that contains error, warning, and status information
*/
SQLRETURN SQL_API
MySQLGetDiagField(SQLSMALLINT handle_type, SQLHANDLE handle, SQLSMALLINT record,
                  SQLSMALLINT identifier, SQLCHAR **char_value,
                  SQLPOINTER num_value)
{
  SQLLEN num;

  /* Handle may not be these types, but this saves lots of casts below. */
  STMT *stmt= (STMT *)handle;
  DBC *dbc= (DBC *)handle;
  DESC *desc= (DESC *)handle;
  MYERROR *error;

  if (!num_value)
    num_value= &num;

  if (!handle)
    return SQL_ERROR;

  if (handle_type == SQL_HANDLE_DESC)
    error= &desc->error;
  else if (handle_type == SQL_HANDLE_STMT)
    error= &stmt->error;
  else if (handle_type == SQL_HANDLE_DBC)
    error= &dbc->error;
  else if (handle_type == SQL_HANDLE_ENV)
    error= &((ENV *)handle)->error;
  else
    return SQL_ERROR;

  if (record > 1)
    return SQL_NO_DATA_FOUND;

  switch (identifier)
  {
  /*  Header fields */
  case SQL_DIAG_CURSOR_ROW_COUNT:
    if (handle_type != SQL_HANDLE_STMT)
      return SQL_ERROR;
    if (!stmt->result)
      *(SQLLEN *)num_value= 0;
    else
      *(SQLLEN *)num_value= (SQLLEN) mysql_num_rows(stmt->result);
    return SQL_SUCCESS;

  case SQL_DIAG_DYNAMIC_FUNCTION:
    if (handle_type != SQL_HANDLE_STMT)
      return SQL_ERROR;
    *char_value= (SQLCHAR *)"";
    return SQL_SUCCESS;

  case SQL_DIAG_DYNAMIC_FUNCTION_CODE:
    if (handle_type != SQL_HANDLE_STMT)
      return SQL_ERROR;
    *(SQLINTEGER *)num_value= 0;
    return SQL_SUCCESS;

  case SQL_DIAG_NUMBER:
    *(SQLINTEGER *)num_value= 1;
    return SQL_SUCCESS;

  case SQL_DIAG_RETURNCODE:
    *(SQLRETURN *)num_value= error->retcode;
    return SQL_SUCCESS;

  case SQL_DIAG_ROW_COUNT:
    if (handle_type != SQL_HANDLE_STMT)
      return SQL_ERROR;
    *(SQLLEN *)num_value= (SQLLEN)stmt->affected_rows;
    return SQL_SUCCESS;

  /* Record fields */
  case SQL_DIAG_CLASS_ORIGIN:
    {
      if (record <= 0)
        return SQL_ERROR;
      auto &sqlstate = error->sqlstate;

      if (!sqlstate.empty() && sqlstate[0] == 'I' && sqlstate[1] == 'M')
        *char_value= (SQLCHAR *)"ODBC 3.0";
      else
        *char_value= (SQLCHAR *)"ISO 9075";
    }
    return SQL_SUCCESS;

  case SQL_DIAG_COLUMN_NUMBER:
    if (record <= 0)
      return SQL_ERROR;
    *(SQLINTEGER *)num_value= SQL_COLUMN_NUMBER_UNKNOWN;
    return SQL_SUCCESS;

  case SQL_DIAG_CONNECTION_NAME:
  {
    DataSource *ds;
    if (record <= 0)
      return SQL_ERROR;

    if (handle_type == SQL_HANDLE_DESC)
      ds= desc->stmt->dbc->ds;
    else if (handle_type == SQL_HANDLE_STMT)
      ds= stmt->dbc->ds;
    else if (handle_type == SQL_HANDLE_DBC)
      ds= dbc->ds;
    else
      *char_value= (SQLCHAR *)"";

    if (ds)
      *char_value= (SQLCHAR *)ds->name8;
    return SQL_SUCCESS;
  }

  case SQL_DIAG_MESSAGE_TEXT:
    if (record <= 0)
      return SQL_ERROR;
    *char_value = (SQLCHAR *)error->message.c_str();
    return SQL_SUCCESS;

  case SQL_DIAG_NATIVE:
    *(SQLINTEGER *)num_value= error->native_error;
    return SQL_SUCCESS;

  case SQL_DIAG_ROW_NUMBER:
    if (record <= 0)
      return SQL_ERROR;
    *(SQLLEN *)num_value= SQL_ROW_NUMBER_UNKNOWN;
    return SQL_SUCCESS;

  case SQL_DIAG_SERVER_NAME:
  {
    DataSource *ds;
    if (record <= 0)
      return SQL_ERROR;
    if (handle_type == SQL_HANDLE_DESC)
      ds= desc->stmt->dbc->ds;
    else if (handle_type == SQL_HANDLE_STMT)
      ds= stmt->dbc->ds;
    else if (handle_type == SQL_HANDLE_DBC)
      ds= dbc->ds;
    else
      *char_value= (SQLCHAR *)"";

    if (ds)
      *char_value= (SQLCHAR *)ds->server8;

    return SQL_SUCCESS;
  }

  case SQL_DIAG_SQLSTATE:
    if (record <= 0)
      return SQL_ERROR;
    *char_value= (SQLCHAR *)error->sqlstate.c_str();
    return SQL_SUCCESS;

  case SQL_DIAG_SUBCLASS_ORIGIN:
    if (record <= 0)
      return SQL_ERROR;
    {
      if (record <= 0)
        return SQL_ERROR;

      if (is_odbc3_subclass((char*)error->sqlstate.c_str()))
        *char_value= (SQLCHAR *)"ODBC 3.0";
      else
        *char_value= (SQLCHAR *)"ISO 9075";
    }
    return SQL_SUCCESS;

  default:
    return SQL_ERROR;
  }
}
