driver/error.cc (441 lines of code) (raw):
// Modifications Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Copyright (c) 2000, 2024, Oracle and/or its affiliates.
//
// 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 designed to work 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 either included with
// the program or referenced in the documentation.
//
// Without limiting anything contained in the foregoing, this file,
// which is part of Connector/ODBC, is also subject to the
// Universal FOSS Exception, version 1.0, a copy of which can be found at
// https://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},
{"08S02","Communication link changed", SQL_ERROR},
{"08007","Connection failure during transaction", 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 a myodbc_malloc() failure on a MYSQL* connection
*/
void set_mem_error(CONNECTION_PROXY* connection_proxy)
{
connection_proxy->set_last_error_code(CR_OUT_OF_MEMORY);
myodbc_stpmov(connection_proxy->get_last_error(), "Memory allocation failed");
myodbc_stpmov(connection_proxy->get_sqlstate(), "HY001");
}
/**
Handle a connection-related error.
@param[in] stmt Statement
*/
SQLRETURN handle_connection_error(STMT *stmt)
{
unsigned int err= stmt->dbc->connection_proxy->error_code();
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
const char *error_code, *error_msg;
if (stmt->dbc->fh->trigger_failover_if_needed("08S01", error_code, error_msg))
return stmt->set_error(error_code, error_msg, 0);
else
return stmt->set_error("08S01", stmt->dbc->connection_proxy->error(), err);
case CR_OUT_OF_MEMORY:
return stmt->set_error("HY001", stmt->dbc->connection_proxy->error(), err);
case CR_COMMANDS_OUT_OF_SYNC:
case CR_UNKNOWN_ERROR:
default:
return stmt->set_error("HY000", stmt->dbc->connection_proxy->error(), 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 ((DBC*)handle)->set_error(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", "08S02", "08007", "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= #
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) stmt->dbc->connection_proxy->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 = nullptr;
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 = ds->opt_DSN;
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 = nullptr;
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= ds->opt_SERVER;
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;
}
}