driver/utility.cc (2,787 lines of code) (raw):
// Copyright (c) 2007, 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 utility.c
@brief Utility functions
*/
#include "driver.h"
#include "errmsg.h"
#include <ctype.h>
#include <iostream>
#include <map>
#define DATETIME_DIGITS 14
const SQLULEN sql_select_unlimited= (SQLULEN)-1;
/**
Execute a SQL statement with setting sql_select_limit for each
execution as SQL_ATTR_MAX_ROWS applies to all result sets on
the statement and not connection.
@param[in] dbc The database connection
@param[in] query The query to execute
@param[in] query_length The length of query to execute
@param[in] req_lock The flag if dbc->lock thread lock should be used
when executing a query
*/
SQLRETURN exec_stmt_query(STMT *stmt, const char *query,
SQLULEN query_length, my_bool req_lock)
{
SQLRETURN rc;
if(!SQL_SUCCEEDED(rc= set_sql_select_limit(stmt->dbc,
stmt->stmt_options.max_rows, req_lock)))
{
/* if setting sql_select_limit fails, the query will probably fail anyway too */
return rc;
}
stmt->buf_set_pos(0);
return stmt->dbc->execute_query(query, query_length, req_lock);
}
SQLRETURN exec_stmt_query_std(STMT *stmt, const std::string &query,
bool req_lock)
{
SQLRETURN rc;
if(!SQL_SUCCEEDED(rc= set_sql_select_limit(stmt->dbc,
stmt->stmt_options.max_rows, req_lock)))
{
/* if setting sql_select_limit fails, the query will probably fail anyway too */
return rc;
}
stmt->buf_set_pos(0);
return stmt->dbc->execute_query(query.c_str(), query.size(), req_lock);
}
/**
Link a list of fields to the current statement result.
@todo This is a terrible idea. We need to purge this.
@param[in] stmt The statement to modify
@param[in] fields The fields to attach to the statement
@param[in] field_count The number of fields
*/
void myodbc_link_fields(STMT *stmt, MYSQL_FIELD *fields, uint field_count)
{
MYSQL_RES *result;
assert(stmt);
LOCK_DBC(stmt->dbc);
result= stmt->result;
result->fields= fields;
result->field_count= field_count;
result->current_field= 0;
fix_result_types(stmt);
}
/**
Fills STMT's lengths array for given row. Makes use of myodbc_link_fields a bit
less terrible.
@param[in,out] stmt The statement to modify
@param[in] fix_rules Describes how to calculate lengths. For each element value
N > 0 - length is taken of field #N from original results
(counting from 1)
N <=0 - constant length (-N)
@param[in] row Row for which to fix lengths
@param[in] field_count The number of fields
*/
void fix_row_lengths(STMT *stmt, const long* fix_rules, uint row, uint field_count)
{
unsigned long* orig_lengths, *row_lengths;
uint i;
if (!stmt->lengths)
return;
row_lengths = stmt->lengths.get() + row * field_count;
orig_lengths= mysql_fetch_lengths(stmt->result);
for (i= 0; i < field_count; ++i)
{
if (fix_rules[i] > 0)
row_lengths[i]= orig_lengths[fix_rules[i] - 1];
else
row_lengths[i]= -fix_rules[i];
}
}
/**
Figure out the ODBC result types for each column in the result set.
@param[in] stmt The statement with result types to be fixed.
*/
void fix_result_types(STMT *stmt)
{
uint i;
MYSQL_RES *result= stmt->result;
DESCREC *irrec;
MYSQL_FIELD *field;
int capint32= stmt->dbc->ds.opt_COLUMN_SIZE_S32 ? 1 : 0;
stmt->state= ST_EXECUTED; /* Mark set found */
/* Populate the IRD records */
size_t f_count = stmt->field_count();
for (i= 0; i < f_count; ++i)
{
irrec= desc_get_rec(stmt->ird, i, TRUE);
/* TODO function for this */
field= result->fields + i;
irrec->row.field= field;
irrec->type= get_sql_data_type(stmt, field, NULL);
irrec->concise_type= get_sql_data_type(stmt, field,
(char *)irrec->row.type_name);
switch (irrec->concise_type)
{
case SQL_DATE:
case SQL_TYPE_DATE:
case SQL_TIME:
case SQL_TYPE_TIME:
case SQL_TIMESTAMP:
case SQL_TYPE_TIMESTAMP:
irrec->type= SQL_DATETIME;
break;
default:
irrec->type= irrec->concise_type;
break;
}
irrec->datetime_interval_code=
get_dticode_from_concise_type(irrec->concise_type);
irrec->type_name= (SQLCHAR *) irrec->row.type_name;
irrec->length= get_column_size(stmt, field);
irrec->octet_length= get_transfer_octet_length(stmt, field);
irrec->display_size= get_display_size(stmt, field);
/* According ODBC specs(http://msdn.microsoft.com/en-us/library/ms713558%28v=VS.85%29.aspx)
"SQL_DESC_OCTET_LENGTH ... For variable-length character or binary types,
this is the maximum length in bytes. This value does not include the null
terminator" Thus there is no need to add 1 to octet_length for char types */
irrec->precision= 0;
/* Set precision for all non-char/blob types */
switch (irrec->type)
{
case SQL_BINARY:
case SQL_BIT:
case SQL_CHAR:
case SQL_WCHAR:
case SQL_VARBINARY:
case SQL_VARCHAR:
case SQL_WVARCHAR:
case SQL_LONGVARBINARY:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
break;
default:
irrec->precision= (SQLSMALLINT) irrec->length;
break;
}
irrec->scale= myodbc_max(0, get_decimal_digits(stmt, field));
if ((field->flags & NOT_NULL_FLAG) &&
!(field->type == MYSQL_TYPE_TIMESTAMP) &&
!(field->flags & AUTO_INCREMENT_FLAG))
irrec->nullable= SQL_NO_NULLS;
else
irrec->nullable= SQL_NULLABLE;
irrec->table_name= (SQLCHAR *)field->table;
irrec->name= (SQLCHAR *)field->name;
irrec->label= (SQLCHAR *)field->name;
if (field->flags & AUTO_INCREMENT_FLAG)
irrec->auto_unique_value= SQL_TRUE;
else
irrec->auto_unique_value= SQL_FALSE;
/* We need support from server, when aliasing is there */
irrec->base_column_name= (SQLCHAR *)field->org_name;
irrec->base_table_name= (SQLCHAR *)field->org_table;
if (field->flags & BINARY_FLAG) /* TODO this doesn't cut it anymore */
irrec->case_sensitive= SQL_TRUE;
else
irrec->case_sensitive= SQL_FALSE;
if (field->db && *field->db)
{
irrec->catalog_name= (SQLCHAR *)field->db;
}
else
{
irrec->catalog_name= (SQLCHAR *)(stmt->dbc->database.c_str());
}
irrec->fixed_prec_scale= SQL_FALSE;
switch (field->type)
{
case MYSQL_TYPE_LONG_BLOB:
case MYSQL_TYPE_TINY_BLOB:
case MYSQL_TYPE_MEDIUM_BLOB:
case MYSQL_TYPE_BLOB:
case MYSQL_TYPE_VAR_STRING:
case MYSQL_TYPE_STRING:
case MYSQL_TYPE_JSON:
if (field->charsetnr == BINARY_CHARSET_NUMBER)
{
irrec->literal_prefix= (SQLCHAR *) "0x";
irrec->literal_suffix= (SQLCHAR *) "";
// The charset number must be only changed for JSON
if (field->type == MYSQL_TYPE_JSON)
field->charsetnr = UTF8_CHARSET_NUMBER;
break;
}
/* FALLTHROUGH */
case MYSQL_TYPE_DATE:
case MYSQL_TYPE_DATETIME:
case MYSQL_TYPE_NEWDATE:
case MYSQL_TYPE_TIMESTAMP:
case MYSQL_TYPE_TIME:
case MYSQL_TYPE_YEAR:
irrec->literal_prefix= (SQLCHAR *) "'";
irrec->literal_suffix= (SQLCHAR *) "'";
break;
case MYSQL_TYPE_VECTOR:
irrec->literal_prefix = (SQLCHAR *) "";
irrec->literal_suffix = (SQLCHAR *) "";
break;
default:
irrec->literal_prefix= (SQLCHAR *) "";
irrec->literal_suffix= (SQLCHAR *) "";
}
switch (field->type) {
case MYSQL_TYPE_SHORT:
case MYSQL_TYPE_LONG:
case MYSQL_TYPE_LONGLONG:
case MYSQL_TYPE_INT24:
case MYSQL_TYPE_TINY:
case MYSQL_TYPE_DECIMAL:
irrec->num_prec_radix= 10;
break;
/* overwrite irrec->precision set above */
case MYSQL_TYPE_FLOAT:
irrec->num_prec_radix= 2;
irrec->precision= 23;
break;
case MYSQL_TYPE_DOUBLE:
irrec->num_prec_radix= 2;
irrec->precision= 53;
break;
default:
irrec->num_prec_radix= 0;
break;
}
irrec->schema_name= (SQLCHAR *) "";
/*
We limit BLOB/TEXT types to SQL_PRED_CHAR due an oversight in ADO
causing problems with updatable cursors.
*/
switch (irrec->concise_type)
{
case SQL_LONGVARBINARY:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
irrec->searchable= SQL_PRED_CHAR;
break;
default:
irrec->searchable= SQL_SEARCHABLE;
break;
}
irrec->unnamed= SQL_NAMED;
if (field->flags & UNSIGNED_FLAG)
irrec->is_unsigned= SQL_TRUE;
else
irrec->is_unsigned= SQL_FALSE;
if (field->table && *field->table)
irrec->updatable= SQL_ATTR_READWRITE_UNKNOWN;
else
irrec->updatable= SQL_ATTR_READONLY;
}
}
/**
Change a string with a length to a NUL-terminated string.
@param[in,out] to A buffer to write the string into, which must be at
at least length + 1 bytes long.
@param[in] from A pointer to the beginning of the source string.
@param[in] length The length of the string, or SQL_NTS if it is
already NUL-terminated.
@return A pointer to a NUL-terminated string.
*/
char *fix_str(char *to, const char *from, int length)
{
if ( !from )
return "";
if ( length == SQL_NTS )
return (char *)from;
myodbc::strmake(to,from,length);
return to;
}
/*
Copy a field to a byte string.
@param[in] stmt Pointer to statement
@param[out] result Buffer for result
@param[in] result_bytes Size of result buffer (in bytes)
@param[out] avail_bytes Pointer to buffer for storing number of bytes
available as result
@param[in] field Field being stored
@param[in] src Source data for result
@param[in] src_bytes Length of source data (in bytes)
@return Standard ODBC result code
*/
SQLRETURN
copy_binary_result(STMT *stmt,
SQLCHAR *result, SQLLEN result_bytes, SQLLEN *avail_bytes,
MYSQL_FIELD *field __attribute__((unused)),
char *src, unsigned long src_bytes)
{
SQLRETURN rc= SQL_SUCCESS;
ulong copy_bytes;
if (!result_bytes)
result= 0; /* Don't copy anything! */
assert(stmt);
/* Apply max length to source data, if one was specified. */
if (stmt->stmt_options.max_length &&
src_bytes > stmt->stmt_options.max_length)
src_bytes = (unsigned long)stmt->stmt_options.max_length;
/* Initialize the source offset */
if (!stmt->getdata.source)
stmt->getdata.source= src;
else
{
src_bytes -= (unsigned long)(stmt->getdata.source - src);
src= stmt->getdata.source;
/* If we've already retrieved everything, return SQL_NO_DATA_FOUND */
if (src_bytes == 0)
return SQL_NO_DATA_FOUND;
}
copy_bytes= myodbc_min((unsigned long)result_bytes, src_bytes);
if (result && stmt->stmt_options.retrieve_data)
memcpy(result, src, copy_bytes);
if (avail_bytes && stmt->stmt_options.retrieve_data)
*avail_bytes= src_bytes;
stmt->getdata.source+= copy_bytes;
if (src_bytes > (unsigned long)result_bytes)
{
stmt->set_error("01004", NULL, 0);
rc= SQL_SUCCESS_WITH_INFO;
}
return rc;
}
/*
Copy a field to an ANSI result string.
@param[in] stmt Pointer to statement
@param[out] result Buffer for result
@param[in] result_bytes Size of result buffer (in bytes)
@param[out] avail_bytes Pointer to buffer for storing number of bytes
available as result
@param[in] field Field being stored
@param[in] src Source data for result
@param[in] src_bytes Length of source data (in bytes)
@return Standard ODBC result code
*/
SQLRETURN
copy_ansi_result(STMT *stmt,
SQLCHAR *result, SQLLEN result_bytes, SQLLEN *avail_bytes,
MYSQL_FIELD *field, char *src, unsigned long src_bytes)
{
SQLRETURN rc= SQL_SUCCESS;
if (!result_bytes)
result= 0; /* Don't copy anything! */
SQLLEN bytes;
if (!avail_bytes)
avail_bytes= &bytes;
if (!result_bytes && !stmt->getdata.source)
{
*avail_bytes= src_bytes;
stmt->set_error("01004", NULL, 0);
return SQL_SUCCESS_WITH_INFO;
}
if (result_bytes)
--result_bytes;
rc= copy_binary_result(stmt, result, result_bytes, avail_bytes,
field, src, src_bytes);
if (SQL_SUCCEEDED(rc) && result && stmt->stmt_options.retrieve_data)
result[myodbc_min(*avail_bytes, result_bytes)]= '\0';
return rc;
}
/**
Copy a result from the server into a buffer as a SQL_C_WCHAR.
@param[in] stmt Pointer to statement
@param[out] result Buffer for result
@param[in] result_len Size of result buffer (in characters)
@param[out] avail_bytes Pointer to buffer for storing amount of data
available before this call
@param[in] field Field being stored
@param[in] src Source data for result
@param[in] src_bytes Length of source data (in bytes)
@return Standard ODBC result code
*/
SQLRETURN
copy_wchar_result(STMT *stmt,
SQLWCHAR *result, SQLINTEGER result_len, SQLLEN *avail_bytes,
MYSQL_FIELD *field, char *src, long src_bytes)
{
SQLRETURN rc= SQL_SUCCESS;
char *src_end;
SQLWCHAR *result_end;
ulong used_chars= 0, error_count= 0;
myodbc::CHARSET_INFO *from_cs = utf8_charset_info;
if (!result_len)
result= NULL; /* Don't copy anything! */
result_end= result + result_len - 1;
if (result == result_end)
{
*result= 0;
result= 0;
}
/* Apply max length to source data, if one was specified. */
if (stmt->stmt_options.max_length &&
(ulong)src_bytes > stmt->stmt_options.max_length)
src_bytes = (long)stmt->stmt_options.max_length;
src_end= src + src_bytes;
/* Initialize the source data */
if (!stmt->getdata.source)
stmt->getdata.source= src;
else
src= stmt->getdata.source;
/* If we've already retrieved everything, return SQL_NO_DATA_FOUND */
if (stmt->getdata.dst_bytes != (ulong)~0L &&
stmt->getdata.dst_offset >= stmt->getdata.dst_bytes)
return SQL_NO_DATA_FOUND;
/* We may have a leftover char from the last call. */
if (stmt->getdata.latest_bytes)
{
if (stmt->stmt_options.retrieve_data)
memcpy(result, stmt->getdata.latest, sizeof(SQLWCHAR));
++result;
if (result == result_end)
{
if (stmt->stmt_options.retrieve_data)
*result= 0;
result= NULL;
}
used_chars+= 1;
stmt->getdata.latest_bytes= 0;
}
while (src < src_end)
{
/* Find the conversion functions. */
auto mb_wc = from_cs->cset->mb_wc;
auto wc_mb = utf16_charset_info->cset->wc_mb;
myodbc::my_wc_t wc = 0;
UTF16 ubuf[5] = {0, 0, 0, 0, 0};
int to_cnvres;
int cnvres= (*mb_wc)(from_cs, &wc, (uchar *)src, (uchar *)src_end);
if (cnvres == MY_CS_ILSEQ)
{
++error_count;
cnvres= 1;
wc= '?';
}
else if (cnvres < 0 && cnvres > MY_CS_TOOSMALL)
{
++error_count;
cnvres= abs(cnvres);
wc= '?';
}
else if (cnvres < 0)
return stmt->set_error("HY000",
"Unknown failure when converting character "
"from server character set.", 0);
convert_to_out:
// SQLWCHAR data should be UTF-16 on all platforms
to_cnvres = (*wc_mb)(utf16_charset_info,
wc, (uchar *)ubuf, (uchar *)ubuf + sizeof(ubuf));
// Get the number of wide chars written
size_t wchars_written = to_cnvres / 2;
if (wchars_written > 0)
{
src+= cnvres;
if (result)
{
if (stmt->stmt_options.retrieve_data)
*result = ubuf[0];
result++;
}
used_chars += (ulong)wchars_written;
if (wchars_written > 1)
{
if (result && result != result_end)
{
if (stmt->stmt_options.retrieve_data)
*result = ubuf[1];
result++;
}
else if (result)
{
*((SQLWCHAR *)stmt->getdata.latest) = ubuf[1];
stmt->getdata.latest_bytes = sizeof(SQLWCHAR);
stmt->getdata.latest_used = 0;
if (stmt->stmt_options.retrieve_data)
*result = 0;
result = NULL;
if (stmt->getdata.dst_bytes != (ulong)~0L)
{
stmt->getdata.source+= cnvres;
break;
}
}
else
{
continue;
}
}
if (result)
stmt->getdata.source+= cnvres;
if (result && result == result_end)
{
if (stmt->stmt_options.retrieve_data)
*result= 0;
result= NULL;
}
}
else if (stmt->getdata.latest_bytes == MY_CS_ILUNI && wc != '?')
{
++error_count;
wc= '?';
goto convert_to_out;
}
else
return stmt->set_error("HY000",
"Unknown failure when converting character "
"to result character set.", 0);
}
if (result && stmt->stmt_options.retrieve_data)
*result= 0;
if (result_len && stmt->getdata.dst_bytes == (ulong)~0L)
{
stmt->getdata.dst_bytes= used_chars * sizeof(SQLWCHAR);
stmt->getdata.dst_offset= 0;
}
if (avail_bytes && stmt->stmt_options.retrieve_data)
{
if (result_len)
*avail_bytes= stmt->getdata.dst_bytes - stmt->getdata.dst_offset;
else
*avail_bytes= used_chars * sizeof(SQLWCHAR);
}
stmt->getdata.dst_offset+= myodbc_min((ulong)(result_len ?
result_len - 1 : 0),
used_chars) * sizeof(SQLWCHAR);
/* Did we truncate the data? */
if (!result_len || stmt->getdata.dst_bytes > stmt->getdata.dst_offset)
{
stmt->set_error("01004", NULL, 0);
rc= SQL_SUCCESS_WITH_INFO;
}
/* Did we encounter any character conversion problems? */
if (error_count)
{
stmt->set_error("22018", NULL, 0);
rc= SQL_SUCCESS_WITH_INFO;
}
return rc;
}
/*
@type : myodbc internal
@purpose : is used when converting binary data to hexadecimal
*/
template <typename T>
SQLRETURN copy_binhex_result(STMT *stmt,
T *rgbValue, SQLINTEGER cbValueMax,
SQLLEN *pcbValue,
char *src, ulong src_length)
{
/** @todo padding of BINARY */
T *dst = (T*) rgbValue;
ulong length;
ulong max_length = (ulong)stmt->stmt_options.max_length;
ulong *offset = &stmt->getdata.src_offset;
T NEAR _dig_vec[] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
if (!cbValueMax)
{
/* Don't copy anything! */
dst = 0;
}
if (max_length) /* If limit on char lengths */
{
set_if_smaller(cbValueMax, (long) max_length + 1);
set_if_smaller(src_length, (max_length + 1) / 2);
}
if (*offset == (ulong) ~0L)
{
*offset = 0; /* First call */
}
else if (*offset >= src_length)
{
return SQL_NO_DATA_FOUND;
}
src += *offset;
src_length -= *offset;
length = cbValueMax ? (ulong)(cbValueMax - 1) / 2 : 0;
length = myodbc_min(src_length, length);
(*offset) += length; /* Fix for next call */
if (pcbValue && stmt->stmt_options.retrieve_data)
*pcbValue = src_length * 2 * sizeof(T);
/* Bind allows null pointers */
if (dst && stmt->stmt_options.retrieve_data)
{
ulong i;
for (i= 0; i < length; ++i)
{
*dst ++= _dig_vec[(uchar) *src >> 4];
*dst ++= _dig_vec[(uchar) *src++ & 15];
}
*dst = 0;
}
// All data is received when offset is not before
// the end of the source
if ( *offset * sizeof(T) >= src_length )
return SQL_SUCCESS;
stmt->set_error(MYERR_01004, NULL, 0);
return SQL_SUCCESS_WITH_INFO;
}
// Need to declare template specializations to avoid linker errors
template
SQLRETURN copy_binhex_result<SQLCHAR>(STMT* stmt,
SQLCHAR* rgbValue, SQLINTEGER cbValueMax,
SQLLEN* pcbValue, char* src, ulong src_length);
template
SQLRETURN copy_binhex_result<SQLWCHAR>(STMT* stmt,
SQLWCHAR* rgbValue, SQLINTEGER cbValueMax,
SQLLEN* pcbValue, char* src, ulong src_length);
/*
@type : myodbc internal
@purpose : is used when converting a bit a SQL_C_CHAR
*/
template<typename T>
SQLRETURN do_copy_bit_result(STMT *stmt,
T *result, SQLLEN result_bytes,
SQLLEN *avail_bytes,
MYSQL_FIELD *field __attribute__((unused)),
char *src, unsigned long src_bytes)
{
// We need 2 chars, otherwise Don't copy anything! */
if (result_bytes < 2)
result = 0;
/* Apply max length to source data, if one was specified. */
if (stmt->stmt_options.max_length &&
src_bytes > stmt->stmt_options.max_length)
src_bytes = (unsigned long)stmt->stmt_options.max_length;
/* Initialize the source offset */
if (!stmt->getdata.source)
{
stmt->getdata.source= src;
}
else
{
src_bytes -= (unsigned long)(stmt->getdata.source - src);
src= stmt->getdata.source;
/* If we've already retrieved everything, return SQL_NO_DATA_FOUND */
if (src_bytes == 0)
{
return SQL_NO_DATA_FOUND;
}
}
if (result && stmt->stmt_options.retrieve_data)
{
result[0] = *src ? '1' : '0';
result[1] = '\0';
}
if (avail_bytes && stmt->stmt_options.retrieve_data)
{
*avail_bytes= sizeof(T);
}
stmt->getdata.source++;
return SQL_SUCCESS;
}
SQLRETURN copy_bit_result(STMT *stmt,
SQLCHAR *result, SQLLEN result_bytes, SQLLEN *avail_bytes,
MYSQL_FIELD *field __attribute__((unused)),
char *src, unsigned long src_bytes)
{
return do_copy_bit_result<SQLCHAR>(stmt, result, result_bytes, avail_bytes, field,
src, src_bytes);
}
SQLRETURN wcopy_bit_result(STMT *stmt,
SQLWCHAR *result, SQLLEN result_bytes, SQLLEN *avail_bytes,
MYSQL_FIELD *field __attribute__((unused)),
char *src, unsigned long src_bytes)
{
return do_copy_bit_result<SQLWCHAR>(stmt, result, result_bytes, avail_bytes, field,
src, src_bytes);
}
std::map<std::string, int> sql_data_types_map = {
{ "bit", SQL_BIT },
{ "decimal", SQL_DECIMAL },
{ "char", SQL_CHAR },
{ "tinyint", SQL_TINYINT },
{ "smallint", SQL_SMALLINT },
{ "mediumint", SQL_INTEGER },
{ "int", SQL_INTEGER },
{ "bigint", SQL_BIGINT },
{ "float", SQL_REAL },
{ "double", SQL_DOUBLE },
{ "year", SQL_SMALLINT },
{ "timestamp", SQL_TIMESTAMP },
{ "datetime", SQL_TIMESTAMP },
{ "date", SQL_TYPE_DATE },
{ "time", SQL_TIME },
{ "binary", SQL_BINARY },
{ "varbinary", SQL_VARBINARY },
{ "vector", SQL_VARBINARY },
{ "varchar", SQL_VARCHAR },
{ "tinyblob", SQL_LONGVARBINARY },
{ "tinytext", SQL_LONGVARCHAR },
{ "mediumblob", SQL_LONGVARBINARY },
{ "mediumtext", SQL_LONGVARCHAR },
{ "blob", SQL_LONGVARBINARY },
{ "text", SQL_LONGVARCHAR },
{ "longblob", SQL_LONGVARBINARY },
{ "longtext", SQL_LONGVARCHAR },
{ "enum", SQL_CHAR },
{ "set", SQL_CHAR },
{ "geometry", SQL_LONGVARBINARY },
{ "JSON", SQL_LONGVARCHAR },
{ "json", SQL_LONGVARCHAR }
};
SQLSMALLINT get_sql_data_type_from_str(const char *mysql_type_name)
{
try
{
return sql_data_types_map.at(mysql_type_name);
}
catch(const std::out_of_range&)
{
return SQL_UNKNOWN_TYPE;
}
// Keep compiler happy
return SQL_UNKNOWN_TYPE;
}
SQLSMALLINT compute_sql_data_type(STMT *stmt, SQLSMALLINT sql_type,
char octet_length, size_t col_size)
{
switch(sql_type)
{
case SQL_TIMESTAMP:
if (stmt->dbc->env->odbc_ver == SQL_OV_ODBC3)
sql_type = SQL_TYPE_TIMESTAMP;
break;
case SQL_TYPE_DATE:
if (stmt->dbc->env->odbc_ver < SQL_OV_ODBC3)
sql_type = SQL_DATE;
break;
case SQL_TIME:
if (stmt->dbc->env->odbc_ver == SQL_OV_ODBC3)
sql_type = SQL_TYPE_TIME;
break;
case SQL_CHAR:
if (octet_length > '1' && stmt->dbc->unicode)
sql_type = SQL_WCHAR;
break;
case SQL_VARCHAR:
if (octet_length > '1' && stmt->dbc->unicode)
sql_type = SQL_WVARCHAR;
break;
case SQL_LONGVARCHAR:
if (octet_length > '1' && stmt->dbc->unicode)
sql_type = SQL_WLONGVARCHAR;
break;
case SQL_BIT:
if (col_size > 1)
sql_type = SQL_BINARY;
break;
}
return sql_type;
}
/**
Get the SQL data type and (optionally) type name for a MYSQL_FIELD.
@param[in] stmt
@param[in] field
@param[out] buff
@return The SQL data type.
*/
SQLSMALLINT get_sql_data_type(STMT *stmt, MYSQL_FIELD *field, char *buff)
{
my_bool field_is_binary= (field->charsetnr == BINARY_CHARSET_NUMBER ? 1 : 0) &&
((field->org_table_length > 0 ? 1 : 0) ||
!stmt->dbc->ds.opt_NO_BINARY_RESULT);
switch (field->type) {
case MYSQL_TYPE_BIT:
if (buff)
(void)myodbc_stpmov(buff, "bit");
/*
MySQL's BIT type can have more than one bit, in which case we treat
it as a BINARY field.
*/
return (field->length > 1) ? SQL_BINARY : SQL_BIT;
case MYSQL_TYPE_DECIMAL:
case MYSQL_TYPE_NEWDECIMAL:
if (buff)
(void)myodbc_stpmov(buff, "decimal");
return SQL_DECIMAL;
case MYSQL_TYPE_TINY:
/* MYSQL_TYPE_TINY could either be a TINYINT or a single CHAR. */
if (buff)
{
buff= myodbc_stpmov(buff, (field->flags & NUM_FLAG) ? "tinyint" : "char");
if (field->flags & UNSIGNED_FLAG)
(void)myodbc_stpmov(buff, " unsigned");
}
return (field->flags & NUM_FLAG) ? SQL_TINYINT : SQL_CHAR;
case MYSQL_TYPE_SHORT:
if (buff)
{
buff= myodbc_stpmov(buff, "smallint");
if (field->flags & UNSIGNED_FLAG)
(void)myodbc_stpmov(buff, " unsigned");
}
return SQL_SMALLINT;
case MYSQL_TYPE_INT24:
if (buff)
{
buff= myodbc_stpmov(buff, "mediumint");
if (field->flags & UNSIGNED_FLAG)
(void)myodbc_stpmov(buff, " unsigned");
}
return SQL_INTEGER;
case MYSQL_TYPE_LONG:
if (buff)
{
buff= myodbc_stpmov(buff, "integer");
if (field->flags & UNSIGNED_FLAG)
(void)myodbc_stpmov(buff, " unsigned");
}
return SQL_INTEGER;
case MYSQL_TYPE_LONGLONG:
if (buff)
{
if (stmt->dbc->ds.opt_NO_BIGINT)
buff= myodbc_stpmov(buff, "int");
else
buff= myodbc_stpmov(buff, "bigint");
if (field->flags & UNSIGNED_FLAG)
(void)myodbc_stpmov(buff, " unsigned");
}
if (stmt->dbc->ds.opt_NO_BIGINT)
return SQL_INTEGER;
return SQL_BIGINT;
case MYSQL_TYPE_FLOAT:
if (buff)
{
buff= myodbc_stpmov(buff, "float");
if (field->flags & UNSIGNED_FLAG)
(void)myodbc_stpmov(buff, " unsigned");
}
return SQL_REAL;
case MYSQL_TYPE_DOUBLE:
if (buff)
{
buff= myodbc_stpmov(buff, "double");
if (field->flags & UNSIGNED_FLAG)
(void)myodbc_stpmov(buff, " unsigned");
}
return SQL_DOUBLE;
case MYSQL_TYPE_NULL:
if (buff)
(void)myodbc_stpmov(buff, "null");
return SQL_VARCHAR;
case MYSQL_TYPE_YEAR:
if (buff)
(void)myodbc_stpmov(buff, "year");
return SQL_SMALLINT;
case MYSQL_TYPE_TIMESTAMP:
if (buff)
(void)myodbc_stpmov(buff, "timestamp");
if (stmt->dbc->env->odbc_ver == SQL_OV_ODBC3)
return SQL_TYPE_TIMESTAMP;
return SQL_TIMESTAMP;
case MYSQL_TYPE_DATETIME:
if (buff)
(void)myodbc_stpmov(buff, "datetime");
if (stmt->dbc->env->odbc_ver == SQL_OV_ODBC3)
return SQL_TYPE_TIMESTAMP;
return SQL_TIMESTAMP;
case MYSQL_TYPE_NEWDATE:
case MYSQL_TYPE_DATE:
if (buff)
(void)myodbc_stpmov(buff, "date");
if (stmt->dbc->env->odbc_ver == SQL_OV_ODBC3)
return SQL_TYPE_DATE;
return SQL_DATE;
case MYSQL_TYPE_TIME:
if (buff)
(void)myodbc_stpmov(buff, "time");
if (stmt->dbc->env->odbc_ver == SQL_OV_ODBC3)
return SQL_TYPE_TIME;
return SQL_TIME;
case MYSQL_TYPE_STRING:
if (buff)
(void)myodbc_stpmov(buff, field_is_binary ? "binary" : "char");
// Return the wide type only if driver is unicode and field charset
// is multibyte.
return field_is_binary ? SQL_BINARY :
(stmt->dbc->unicode ? SQL_WCHAR : SQL_CHAR);
/*
MYSQL_TYPE_VARCHAR is never actually sent, this just silences
a compiler warning.
*/
case MYSQL_TYPE_VARCHAR:
case MYSQL_TYPE_VAR_STRING:
if (buff)
(void)myodbc_stpmov(buff, field_is_binary ? "varbinary" : "varchar");
return field_is_binary ? SQL_VARBINARY :
(stmt->dbc->unicode && get_charset_maxlen(field->charsetnr) > 1 ?
SQL_WVARCHAR : SQL_VARCHAR);
case MYSQL_TYPE_TINY_BLOB:
if (buff)
(void)myodbc_stpmov(buff, field_is_binary ? "tinyblob" : "tinytext");
return field_is_binary ? SQL_LONGVARBINARY :
(stmt->dbc->unicode && get_charset_maxlen(field->charsetnr) > 1 ?
SQL_WLONGVARCHAR : SQL_LONGVARCHAR);
case MYSQL_TYPE_BLOB:
if (buff)
{
switch(field->length)
{
case 255:
(void)myodbc_stpmov(buff, field_is_binary ? "tinyblob" : "tinytext");
break;
case 16777215:
(void)myodbc_stpmov(buff, field_is_binary ? "mediumblob" : "mediumtext");
break;
case 4294967295UL:
(void)myodbc_stpmov(buff, field_is_binary ? "longblob" : "longtext");
break;
default:
(void)myodbc_stpmov(buff, field_is_binary ? "blob" : "text");
}
}
return field_is_binary ? SQL_LONGVARBINARY :
(stmt->dbc->unicode && get_charset_maxlen(field->charsetnr) > 1 ?
SQL_WLONGVARCHAR : SQL_LONGVARCHAR);
case MYSQL_TYPE_MEDIUM_BLOB:
if (buff)
(void)myodbc_stpmov(buff, field_is_binary ? "mediumblob" : "mediumtext");
return field_is_binary ? SQL_LONGVARBINARY :
(stmt->dbc->unicode && get_charset_maxlen(field->charsetnr) > 1 ?
SQL_WLONGVARCHAR : SQL_LONGVARCHAR);
case MYSQL_TYPE_LONG_BLOB:
if (buff)
(void)myodbc_stpmov(buff, field_is_binary ? "longblob" : "longtext");
return field_is_binary ? SQL_LONGVARBINARY :
(stmt->dbc->unicode && get_charset_maxlen(field->charsetnr) > 1 ?
SQL_WLONGVARCHAR : SQL_LONGVARCHAR);
case MYSQL_TYPE_ENUM:
if (buff)
(void)myodbc_stpmov(buff, "enum");
return SQL_CHAR;
case MYSQL_TYPE_SET:
if (buff)
(void)myodbc_stpmov(buff, "set");
return SQL_CHAR;
case MYSQL_TYPE_GEOMETRY:
if (buff)
(void)myodbc_stpmov(buff, "geometry");
return SQL_LONGVARBINARY;
case MYSQL_TYPE_JSON:
if (buff)
(void)myodbc_stpmov(buff, "json");
return stmt->dbc->unicode ? SQL_WLONGVARCHAR : SQL_LONGVARCHAR;
case MYSQL_TYPE_VECTOR:
if (buff)
(void)myodbc_stpmov(buff, "vector");
return SQL_VARBINARY;
}
if (buff)
*buff= '\0';
return SQL_UNKNOWN_TYPE;
}
/**
Fill the display size buffer accordingly to size of SQLLEN
@param[in,out] buff
@param[in] stmt
@param[in] field
@return void
*/
SQLLEN fill_display_size_buff(char *buff, STMT *stmt, MYSQL_FIELD *field)
{
/* See comment for fill_transfer_oct_len_buff()*/
SQLLEN size= get_display_size(stmt, field);
sprintf(buff,size == SQL_NO_TOTAL ? "%d" : (sizeof(SQLLEN) == 4 ? "%lu" : "%lld"), size);
return size;
}
/**
Fill the transfer octet length buffer accordingly to size of SQLLEN
@param[in,out] buff
@param[in] stmt
@param[in] field
@return void
*/
SQLLEN fill_transfer_oct_len_buff(char *buff, STMT *stmt, MYSQL_FIELD *field)
{
/* The only possible negative value get_transfer_octet_length can return is SQL_NO_TOTAL
But it can return value which is greater that biggest signed integer(%ld).
Thus for other values we use %lu. %lld should fit
all (currently) possible in mysql values.
*/
SQLLEN len= get_transfer_octet_length(stmt, field);
sprintf(buff, len == SQL_NO_TOTAL ? "%d" : (sizeof(SQLLEN) == 4 ? "%lu" : "%lld"), len );
return len;
}
/**
Fill the column size buffer accordingly to size of SQLULEN
@param[in,out] buff
@param[in] stmt
@param[in] field
@return void
*/
SQLULEN fill_column_size_buff(char *buff, STMT *stmt, MYSQL_FIELD *field)
{
SQLULEN size= get_column_size(stmt, field);
sprintf(buff, (size== SQL_NO_TOTAL ? "%d" :
(sizeof(SQLULEN) == 4 ? "%lu" : "%llu")), size);
return size;
}
/**
Capping length value if connection option is set
*/
static SQLLEN cap_length(STMT *stmt, unsigned long real_length)
{
if (stmt->dbc->ds.opt_COLUMN_SIZE_S32 != 0 && real_length > INT_MAX32)
return INT_MAX32;
return real_length;
}
/**
Getting column size from string number
*/
SQLULEN get_column_size_from_str(STMT *stmt, const char *size_str)
{
SQLULEN length = size_str ?
(SQLULEN)std::strtoll(size_str, nullptr, 10) : 0;
return cap_length(stmt, (unsigned long)length);
}
/**
Get the column size (in characters) of a field, as defined at:
http://msdn2.microsoft.com/en-us/library/ms711786.aspx
@param[in] stmt
@param[in] field
@return The column size of the field
*/
SQLULEN get_column_size(STMT *stmt, MYSQL_FIELD *field)
{
SQLULEN length= field->length;
/* Work around a bug in some versions of the server. */
if (field->max_length > field->length)
length= field->max_length;
length= cap_length(stmt, (unsigned long)length);
switch (field->type) {
case MYSQL_TYPE_TINY:
return (field->flags & NUM_FLAG) ? 3 : 1;
case MYSQL_TYPE_SHORT:
return 5;
case MYSQL_TYPE_LONG:
return 10;
case MYSQL_TYPE_FLOAT:
return 7;
case MYSQL_TYPE_DOUBLE:
return 15;
case MYSQL_TYPE_NULL:
return 0;
case MYSQL_TYPE_LONGLONG:
if (stmt->dbc->ds.opt_NO_BIGINT)
return 10; /* same as MYSQL_TYPE_LONG */
else
return (field->flags & UNSIGNED_FLAG) ? 20 : 19;
case MYSQL_TYPE_INT24:
return 8;
case MYSQL_TYPE_DATE:
return 10;
case MYSQL_TYPE_TIME:
return 8;
case MYSQL_TYPE_TIMESTAMP:
case MYSQL_TYPE_DATETIME:
case MYSQL_TYPE_NEWDATE:
return 19;
case MYSQL_TYPE_YEAR:
return 4;
case MYSQL_TYPE_DECIMAL:
case MYSQL_TYPE_NEWDECIMAL:
return (length -
(!(field->flags & UNSIGNED_FLAG) ? 1: 0) - /* sign? */
(field->decimals ? 1 : 0)); /* decimal point? */
case MYSQL_TYPE_BIT:
/*
We treat a BIT(n) as a SQL_BIT if n == 1, otherwise we treat it
as a SQL_BINARY, so length is (bits + 7) / 8.
*/
if (length == 1)
return 1;
return (length + 7) / 8;
case MYSQL_TYPE_ENUM:
case MYSQL_TYPE_SET:
case MYSQL_TYPE_VARCHAR:
case MYSQL_TYPE_VAR_STRING:
case MYSQL_TYPE_STRING:
case MYSQL_TYPE_TINY_BLOB:
case MYSQL_TYPE_MEDIUM_BLOB:
case MYSQL_TYPE_LONG_BLOB:
case MYSQL_TYPE_BLOB:
case MYSQL_TYPE_GEOMETRY:
// For LONGTEXT we must return 4G or 2G if it was capped.
if (length >= INT32_MAX)
return length;
// For BINARY charset the maxlen is 1, so the result
// will be the byte length.
return length / get_charset_maxlen(field->charsetnr);
case MYSQL_TYPE_JSON:
return UINT32_MAX / 4; // Because JSON is always UTF8MB4
case MYSQL_TYPE_VECTOR:
return length / 4; // Length should be the number of elements in VECTOR
}
return SQL_NO_TOTAL;
}
/**
Get the decimal digits of a field, as defined at:
http://msdn2.microsoft.com/en-us/library/ms709314.aspx
@param[in] stmt
@param[in] field
@return The decimal digits, or @c SQL_NO_TOTAL where it makes no sense
The function has to return SQLSMALLINT, since it corresponds to SQL_DESC_SCALE
or SQL_DESC_PRECISION for some data types.
*/
SQLSMALLINT get_decimal_digits(STMT *stmt __attribute__((unused)),
MYSQL_FIELD *field)
{
switch (field->type) {
case MYSQL_TYPE_DECIMAL:
case MYSQL_TYPE_NEWDECIMAL:
return field->decimals;
/* All exact numeric types. */
case MYSQL_TYPE_TINY:
case MYSQL_TYPE_SHORT:
case MYSQL_TYPE_LONG:
case MYSQL_TYPE_LONGLONG:
case MYSQL_TYPE_INT24:
case MYSQL_TYPE_YEAR:
case MYSQL_TYPE_TIME:
case MYSQL_TYPE_TIMESTAMP:
case MYSQL_TYPE_DATETIME:
return 0;
/* We treat MYSQL_TYPE_BIT as an exact numeric type only for BIT(1). */
case MYSQL_TYPE_BIT:
if (field->length == 1)
return 0;
default:
/*
This value is only used in some catalog functions. It's co-erced
to zero for all descriptor use.
*/
return SQL_NO_TOTAL;
}
}
/**
Get the transfer octet length of a field, as defined at:
http://msdn2.microsoft.com/en-us/library/ms713979.aspx
@param[in] stmt
@param[in] field
@return The transfer octet length
*/
SQLLEN get_transfer_octet_length(STMT *stmt, MYSQL_FIELD *field)
{
int capint32= stmt->dbc->ds.opt_COLUMN_SIZE_S32 ? 1 : 0;
SQLLEN length;
/* cap at INT_MAX32 due to signed value */
if (field->length > INT_MAX32)
length= INT_MAX32;
else
length= field->length;
switch (field->type) {
case MYSQL_TYPE_TINY:
return 1;
case MYSQL_TYPE_SHORT:
return 2;
case MYSQL_TYPE_INT24:
return 3;
case MYSQL_TYPE_LONG:
return 4;
case MYSQL_TYPE_FLOAT:
return 4;
case MYSQL_TYPE_DOUBLE:
return 8;
case MYSQL_TYPE_NULL:
return 1;
case MYSQL_TYPE_LONGLONG:
return 20;
case MYSQL_TYPE_DATE:
return sizeof(SQL_DATE_STRUCT);
case MYSQL_TYPE_TIME:
return sizeof(SQL_TIME_STRUCT);
case MYSQL_TYPE_TIMESTAMP:
case MYSQL_TYPE_DATETIME:
case MYSQL_TYPE_NEWDATE:
return sizeof(SQL_TIMESTAMP_STRUCT);
case MYSQL_TYPE_YEAR:
return 1;
case MYSQL_TYPE_DECIMAL:
case MYSQL_TYPE_NEWDECIMAL:
return field->length;
case MYSQL_TYPE_BIT:
/*
We treat a BIT(n) as a SQL_BIT if n == 1, otherwise we treat it
as a SQL_BINARY, so length is (bits + 7) / 8. field->length has
the number of bits.
*/
return (field->length + 7) / 8;
case MYSQL_TYPE_STRING:
if (stmt->dbc->ds.opt_PAD_SPACE)
{
unsigned int csmaxlen = get_charset_maxlen(field->charsetnr);
if (!csmaxlen)
return SQL_NO_TOTAL;
/*
We need to find out the real length using the database length and
the current mode (ANSI or UNICODE)
*/
length= (field->max_length > field->length ? field->max_length : field->length) / csmaxlen;
return length;
}
/* FALLTHROUGH */
case MYSQL_TYPE_ENUM:
case MYSQL_TYPE_SET:
case MYSQL_TYPE_VARCHAR:
case MYSQL_TYPE_VAR_STRING:
case MYSQL_TYPE_TINY_BLOB:
case MYSQL_TYPE_MEDIUM_BLOB:
case MYSQL_TYPE_LONG_BLOB:
case MYSQL_TYPE_BLOB:
case MYSQL_TYPE_GEOMETRY:
case MYSQL_TYPE_JSON:
{
if (capint32 && length > INT_MAX32)
length= INT_MAX32;
return length;
case MYSQL_TYPE_VECTOR:
return length;
}
}
return SQL_NO_TOTAL;
}
/**
Get the display size of a field, as defined at:
http://msdn2.microsoft.com/en-us/library/ms713974.aspx
@param[in] stmt
@param[in] field
@return The display size
*/
SQLLEN get_display_size(STMT *stmt __attribute__((unused)),MYSQL_FIELD *field)
{
int capint32 = stmt->dbc->ds.opt_COLUMN_SIZE_S32 ? 1 : 0;
unsigned int mbmaxlen = get_charset_maxlen(field->charsetnr);
switch (field->type) {
case MYSQL_TYPE_TINY:
return 3 + (field->flags & UNSIGNED_FLAG ? 1 : 0);
case MYSQL_TYPE_SHORT:
return 5 + (field->flags & UNSIGNED_FLAG ? 1 : 0);
case MYSQL_TYPE_INT24:
return 8 + (field->flags & UNSIGNED_FLAG ? 1 : 0);
case MYSQL_TYPE_LONG:
return 10 + (field->flags & UNSIGNED_FLAG ? 1 : 0);
case MYSQL_TYPE_FLOAT:
return 14;
case MYSQL_TYPE_DOUBLE:
return 24;
case MYSQL_TYPE_NULL:
return 1;
case MYSQL_TYPE_LONGLONG:
return 20;
case MYSQL_TYPE_DATE:
return 10;
case MYSQL_TYPE_TIME:
return 8;
case MYSQL_TYPE_TIMESTAMP:
case MYSQL_TYPE_DATETIME:
case MYSQL_TYPE_NEWDATE:
return 19;
case MYSQL_TYPE_YEAR:
return 4;
case MYSQL_TYPE_DECIMAL:
case MYSQL_TYPE_NEWDECIMAL:
return field->length;
case MYSQL_TYPE_BIT:
/*
We treat a BIT(n) as a SQL_BIT if n == 1, otherwise we treat it
as a SQL_BINARY, so display length is (bits + 7) / 8 * 2.
field->length has the number of bits.
*/
if (field->length == 1)
return 1;
return (field->length + 7) / 8 * 2;
case MYSQL_TYPE_TINY_BLOB:
case MYSQL_TYPE_MEDIUM_BLOB:
case MYSQL_TYPE_LONG_BLOB:
case MYSQL_TYPE_BLOB:
// For these types the storage is in bytes and it is fixed,
// not specified by the user.
// The driver must give the maximum possible display size.
mbmaxlen = 1;
// Fall through
case MYSQL_TYPE_ENUM:
case MYSQL_TYPE_SET:
case MYSQL_TYPE_VARCHAR:
case MYSQL_TYPE_VAR_STRING:
case MYSQL_TYPE_STRING:
case MYSQL_TYPE_GEOMETRY:
{
unsigned long length;
if (field->charsetnr == BINARY_CHARSET_NUMBER)
length= field->length * 2;
else
length= field->length / mbmaxlen;
if (capint32 && length > INT_MAX32)
length= INT_MAX32;
return length;
}
case MYSQL_TYPE_JSON:
return UINT_MAX32 / 4; // 4 is the character size in UTF8MB4
case MYSQL_TYPE_VECTOR:
return 15 * (field->length / 4) + 1;
}
return SQL_NO_TOTAL;
}
/*
Map the concise type (value or param) to the correct datetime or
interval code.
See SQLSetDescField()/SQL_DESC_DATETIME_INTERVAL_CODE docs for details.
*/
SQLSMALLINT
get_dticode_from_concise_type(SQLSMALLINT concise_type)
{
/* figure out SQL_DESC_DATETIME_INTERVAL_CODE from SQL_DESC_CONCISE_TYPE */
switch (concise_type)
{
case SQL_C_TYPE_DATE:
return SQL_CODE_DATE;
case SQL_C_TYPE_TIME:
return SQL_CODE_TIME;
case SQL_C_TIMESTAMP:
case SQL_C_TYPE_TIMESTAMP:
return SQL_CODE_TIMESTAMP;
case SQL_C_INTERVAL_DAY:
return SQL_CODE_DAY;
case SQL_C_INTERVAL_DAY_TO_HOUR:
return SQL_CODE_DAY_TO_HOUR;
case SQL_C_INTERVAL_DAY_TO_MINUTE:
return SQL_CODE_DAY_TO_MINUTE;
case SQL_C_INTERVAL_DAY_TO_SECOND:
return SQL_CODE_DAY_TO_SECOND;
case SQL_C_INTERVAL_HOUR:
return SQL_CODE_HOUR;
case SQL_C_INTERVAL_HOUR_TO_MINUTE:
return SQL_CODE_HOUR_TO_MINUTE;
case SQL_C_INTERVAL_HOUR_TO_SECOND:
return SQL_CODE_HOUR_TO_SECOND;
case SQL_C_INTERVAL_MINUTE:
return SQL_CODE_MINUTE;
case SQL_C_INTERVAL_MINUTE_TO_SECOND:
return SQL_CODE_MINUTE_TO_SECOND;
case SQL_C_INTERVAL_MONTH:
return SQL_CODE_MONTH;
case SQL_C_INTERVAL_SECOND:
return SQL_CODE_SECOND;
case SQL_C_INTERVAL_YEAR:
return SQL_CODE_YEAR;
case SQL_C_INTERVAL_YEAR_TO_MONTH:
return SQL_CODE_YEAR_TO_MONTH;
default:
return 0;
}
}
/*
Map the SQL_DESC_DATETIME_INTERVAL_CODE to the SQL_DESC_CONCISE_TYPE
for datetime types.
Constant returned is valid for both param and value types.
*/
SQLSMALLINT get_concise_type_from_datetime_code(SQLSMALLINT dticode)
{
switch (dticode)
{
case SQL_CODE_DATE:
return SQL_C_TYPE_DATE;
case SQL_CODE_TIME:
return SQL_C_TYPE_DATE;
case SQL_CODE_TIMESTAMP:
return SQL_C_TYPE_TIMESTAMP;
default:
return 0;
}
}
/*
Map the SQL_DESC_DATETIME_INTERVAL_CODE to the SQL_DESC_CONCISE_TYPE
for interval types.
Constant returned is valid for both param and value types.
*/
SQLSMALLINT get_concise_type_from_interval_code(SQLSMALLINT dticode)
{
switch (dticode)
{
case SQL_CODE_DAY:
return SQL_C_INTERVAL_DAY;
case SQL_CODE_DAY_TO_HOUR:
return SQL_C_INTERVAL_DAY_TO_HOUR;
case SQL_CODE_DAY_TO_MINUTE:
return SQL_C_INTERVAL_DAY_TO_MINUTE;
case SQL_CODE_DAY_TO_SECOND:
return SQL_C_INTERVAL_DAY_TO_SECOND;
case SQL_CODE_HOUR:
return SQL_C_INTERVAL_HOUR;
case SQL_CODE_HOUR_TO_MINUTE:
return SQL_C_INTERVAL_HOUR_TO_MINUTE;
case SQL_CODE_HOUR_TO_SECOND:
return SQL_C_INTERVAL_HOUR_TO_SECOND;
case SQL_CODE_MINUTE:
return SQL_C_INTERVAL_MINUTE;
case SQL_CODE_MINUTE_TO_SECOND:
return SQL_C_INTERVAL_MINUTE_TO_SECOND;
case SQL_CODE_MONTH:
return SQL_C_INTERVAL_MONTH;
case SQL_CODE_SECOND:
return SQL_C_INTERVAL_SECOND;
case SQL_CODE_YEAR:
return SQL_C_INTERVAL_YEAR;
case SQL_CODE_YEAR_TO_MONTH:
return SQL_C_INTERVAL_YEAR_TO_MONTH;
default:
return 0;
}
}
/*
Map the concise type to a (possibly) more general type.
*/
SQLSMALLINT get_type_from_concise_type(SQLSMALLINT concise_type)
{
/* set SQL_DESC_TYPE from SQL_DESC_CONCISE_TYPE */
switch (concise_type)
{
/* datetime data types */
case SQL_C_TYPE_DATE:
case SQL_C_TYPE_TIME:
case SQL_C_TYPE_TIMESTAMP:
return SQL_DATETIME;
/* interval data types */
case SQL_C_INTERVAL_YEAR:
case SQL_C_INTERVAL_MONTH:
case SQL_C_INTERVAL_DAY:
case SQL_C_INTERVAL_HOUR:
case SQL_C_INTERVAL_MINUTE:
case SQL_C_INTERVAL_SECOND:
case SQL_C_INTERVAL_YEAR_TO_MONTH:
case SQL_C_INTERVAL_DAY_TO_HOUR:
case SQL_C_INTERVAL_DAY_TO_MINUTE:
case SQL_C_INTERVAL_DAY_TO_SECOND:
case SQL_C_INTERVAL_HOUR_TO_MINUTE:
case SQL_C_INTERVAL_HOUR_TO_SECOND:
case SQL_C_INTERVAL_MINUTE_TO_SECOND:
return SQL_INTERVAL;
/* else, set same */
default:
return concise_type;
}
}
/*
@type : myodbc internal
@purpose : returns internal type to C type
*/
int unireg_to_c_datatype(MYSQL_FIELD *field)
{
switch ( field->type )
{
case MYSQL_TYPE_BIT:
/*
MySQL's BIT type can have more than one bit, in which case we
treat it as a BINARY field.
*/
return (field->length > 1) ? SQL_C_BINARY : SQL_C_BIT;
case MYSQL_TYPE_TINY:
return SQL_C_TINYINT;
case MYSQL_TYPE_YEAR:
case MYSQL_TYPE_SHORT:
return SQL_C_SHORT;
case MYSQL_TYPE_INT24:
case MYSQL_TYPE_LONG:
return SQL_C_LONG;
case MYSQL_TYPE_FLOAT:
return SQL_C_FLOAT;
case MYSQL_TYPE_DOUBLE:
return SQL_C_DOUBLE;
case MYSQL_TYPE_TIMESTAMP:
case MYSQL_TYPE_DATETIME:
return SQL_C_TIMESTAMP;
case MYSQL_TYPE_NEWDATE:
case MYSQL_TYPE_DATE:
return SQL_C_DATE;
case MYSQL_TYPE_TIME:
return SQL_C_TIME;
case MYSQL_TYPE_BLOB:
case MYSQL_TYPE_TINY_BLOB:
case MYSQL_TYPE_MEDIUM_BLOB:
case MYSQL_TYPE_LONG_BLOB:
case MYSQL_TYPE_JSON:
case MYSQL_TYPE_VECTOR:
return SQL_C_BINARY;
case MYSQL_TYPE_LONGLONG: /* Must be returned as char */
default:
return SQL_C_CHAR;
}
}
/*
@type : myodbc internal
@purpose : returns default C type for a given SQL type
*/
int default_c_type(int sql_data_type)
{
switch ( sql_data_type )
{
case SQL_CHAR:
case SQL_VARCHAR:
case SQL_LONGVARCHAR:
case SQL_DECIMAL:
case SQL_NUMERIC:
default:
return SQL_C_CHAR;
case SQL_BIGINT:
return SQL_C_SBIGINT;
case SQL_BIT:
return SQL_C_BIT;
case SQL_TINYINT:
return SQL_C_TINYINT;
case SQL_SMALLINT:
return SQL_C_SHORT;
case SQL_INTEGER:
return SQL_C_LONG;
case SQL_REAL:
case SQL_FLOAT:
return SQL_C_FLOAT;
case SQL_DOUBLE:
return SQL_C_DOUBLE;
case SQL_BINARY:
case SQL_VARBINARY:
case SQL_LONGVARBINARY:
return SQL_C_BINARY;
case SQL_DATE:
case SQL_TYPE_DATE:
return SQL_C_DATE;
case SQL_TIME:
case SQL_TYPE_TIME:
return SQL_C_TIME;
case SQL_TIMESTAMP:
case SQL_TYPE_TIMESTAMP:
return SQL_C_TIMESTAMP;
}
}
/*
@type : myodbc internal
@purpose : returns bind length
*/
ulong bind_length(int sql_data_type,ulong length)
{
switch ( sql_data_type )
{
case SQL_C_BIT:
case SQL_C_TINYINT:
case SQL_C_STINYINT:
case SQL_C_UTINYINT:
return 1;
case SQL_C_SHORT:
case SQL_C_SSHORT:
case SQL_C_USHORT:
return 2;
case SQL_C_LONG:
case SQL_C_SLONG:
case SQL_C_ULONG:
return sizeof(SQLINTEGER);
case SQL_C_FLOAT:
return sizeof(float);
case SQL_C_DOUBLE:
return sizeof(double);
case SQL_C_DATE:
case SQL_C_TYPE_DATE:
return sizeof(DATE_STRUCT);
case SQL_C_TIME:
case SQL_C_TYPE_TIME:
return sizeof(TIME_STRUCT);
case SQL_C_TIMESTAMP:
case SQL_C_TYPE_TIMESTAMP:
return sizeof(TIMESTAMP_STRUCT);
case SQL_C_SBIGINT:
case SQL_C_UBIGINT:
return sizeof(longlong);
case SQL_C_NUMERIC:
return sizeof(SQL_NUMERIC_STRUCT);
default: /* For CHAR, VARCHAR, BLOB, DEFAULT...*/
return length;
}
}
/**
Get bookmark value from SQL_ATTR_FETCH_BOOKMARK_PTR buffer
@param[in] fCType ODBC C type to return data as
@param[out] rgbValue Pointer to buffer for returning data
*/
SQLLEN get_bookmark_value(SQLSMALLINT fCType, SQLPOINTER rgbValue)
{
switch (fCType)
{
case SQL_C_CHAR:
case SQL_C_BINARY:
return atol((const char*) rgbValue);
case SQL_C_WCHAR:
return sqlwchartoul((SQLWCHAR *)rgbValue);
case SQL_C_TINYINT:
case SQL_C_STINYINT:
case SQL_C_UTINYINT:
case SQL_C_SHORT:
case SQL_C_SSHORT:
case SQL_C_USHORT:
case SQL_C_LONG:
case SQL_C_SLONG:
case SQL_C_ULONG:
case SQL_C_FLOAT:
case SQL_C_DOUBLE:
case SQL_C_SBIGINT:
case SQL_C_UBIGINT:
return *((SQLLEN *) rgbValue);
}
return 0;
}
/*
@type : myodbc internal
@purpose : convert a possible string to a timestamp value
*/
int str_to_ts(SQL_TIMESTAMP_STRUCT *ts, const char *str, int len, int zeroToMin,
BOOL dont_use_set_locale)
{
uint year, length;
char buff[DATETIME_DIGITS + 1], *to;
const char *end;
SQL_TIMESTAMP_STRUCT tmp_timestamp;
SQLUINTEGER fraction;
if ( !ts )
{
ts= (SQL_TIMESTAMP_STRUCT *) &tmp_timestamp;
}
/* SQL_NTS is (naturally) negative and is caught as well */
if (len < 0)
{
len = (int)strlen(str);
}
/* We don't wan to change value in the out parameter directly
before we know that string is a good datetime */
end= get_fractional_part(str, len, dont_use_set_locale, &fraction);
if (end == NULL || end > str + len)
{
end= str + len;
}
for ( to= buff; str < end; ++str )
{
if ( isdigit(*str) )
{
if (to < buff+sizeof(buff)-1)
{
*to++= *str;
}
else
{
/* We have too many numbers in the string and we not gonna tolerate it */
return SQLTS_BAD_DATE;
}
}
}
length= (uint) (to-buff);
if ( length == 6 || length == 12 ) /* YYMMDD or YYMMDDHHMMSS */
{
memmove(buff+2, buff, length);
if ( buff[0] <= '6' )
{
buff[0]='2';
buff[1]='0';
}
else
{
buff[0]='1';
buff[1]='9';
}
length+= 2;
to+= 2;
}
if (length < DATETIME_DIGITS)
{
myodbc::strfill(buff + length, DATETIME_DIGITS - length, '0');
}
else
{
*to= 0;
}
year= (digit(buff[0])*1000+digit(buff[1])*100+digit(buff[2])*10+digit(buff[3]));
if (!strncmp(&buff[4], "00", 2) || !strncmp(&buff[6], "00", 2))
{
if (!zeroToMin) /* Don't convert invalid */
return SQLTS_NULL_DATE;
/* convert invalid to min allowed */
if (!strncmp(&buff[4], "00", 2))
buff[5]= '1';
if (!strncmp(&buff[6], "00", 2))
buff[7]= '1';
}
ts->year= year;
ts->month= digit(buff[4])*10+digit(buff[5]);
ts->day= digit(buff[6])*10+digit(buff[7]);
ts->hour= digit(buff[8])*10+digit(buff[9]);
ts->minute= digit(buff[10])*10+digit(buff[11]);
ts->second= digit(buff[12])*10+digit(buff[13]);
ts->fraction= fraction;
return 0;
}
/*
@type : myodbc internal
@purpose : convert a possible string to a time value
*/
my_bool str_to_time_st(SQL_TIME_STRUCT *ts, const char *str)
{
char buff[24],*to, *tokens[3] = {0, 0, 0};
int num= 0, int_hour=0, int_min= 0, int_sec= 0;
SQL_TIME_STRUCT tmp_time;
if ( !ts )
ts= (SQL_TIME_STRUCT *) &tmp_time;
/* remember the position of the first numeric string */
tokens[0]= buff;
for ( to= buff ; *str && to < buff+sizeof(buff)-1 ; ++str )
{
if (isdigit(*str))
*to++= *str;
else if (num < 2)
{
/*
terminate the string and remember the beginning of the
new one only if the time component number is not out of
range
*/
*to++= 0;
tokens[++num]= to;
}
else
/* We can leave the loop now */
break;
}
/* Put the final termination character */
*to= 0;
int_hour= tokens[0] ? atoi(tokens[0]) : 0;
int_min= tokens[1] ? atoi(tokens[1]) : 0;
int_sec= tokens[2] ? atoi(tokens[2]) : 0;
/* Convert seconds into minutes if necessary */
if (int_sec > 59)
{
int_min+= int_sec / 60;
int_sec= int_sec % 60;
}
/* Convert minutes into hours if necessary */
if (int_min > 59)
{
int_hour+= int_min / 60;
int_min= int_min % 60;
}
ts->hour = (SQLUSMALLINT)(int_hour < 65536 ? int_hour : 65535);
ts->minute = (SQLUSMALLINT)int_min;
ts->second = (SQLUSMALLINT)int_sec;
return 0;
}
/*
@type : myodbc internal
@purpose : convert a possible string to a data value. if
zeroToMin is specified, YEAR-00-00 dates will be
converted to the min valid ODBC date
*/
my_bool str_to_date(SQL_DATE_STRUCT *rgbValue, const char *str,
uint length, int zeroToMin)
{
uint field_length,year_length,digits,i,date[3];
const char *pos;
const char *end= str+length;
for ( ; !isdigit(*str) && str != end ; ++str ) ;
/*
Calculate first number of digits.
If length= 4, 8 or >= 14 then year is of format YYYY
(YYYY-MM-DD, YYYYMMDD)
*/
for ( pos= str; pos != end && isdigit(*pos) ; ++pos ) ;
digits= (uint) (pos-str);
year_length= (digits == 4 || digits == 8 || digits >= 14) ? 4 : 2;
field_length= year_length-1;
for ( i= 0 ; i < 3 && str != end; ++i )
{
uint tmp_value= (uint) (uchar) (*str++ - '0');
while ( str != end && isdigit(str[0]) && field_length-- )
{
tmp_value= tmp_value*10 + (uint) (uchar) (*str - '0');
++str;
}
date[i]= tmp_value;
while ( str != end && !isdigit(*str) )
++str;
field_length= 1; /* Rest fields can only be 2 */
}
if (i <= 1 || (i > 1 && !date[1]) || (i > 2 && !date[2]))
{
if (!zeroToMin) /* Convert? */
return 1;
rgbValue->year= date[0];
rgbValue->month= (i > 1 && date[1]) ? date[1] : 1;
rgbValue->day= (i > 2 && date[2]) ? date[2] : 1;
}
else
{
while ( i < 3 )
date[i++]= 1;
rgbValue->year= date[0];
rgbValue->month= date[1];
rgbValue->day= date[2];
}
return 0;
}
/*
@type : myodbc internal
@purpose : convert a time string to a (ulong) value.
At least following formats are recogniced
HHMMSS HHMM HH HH.MM.SS {t HH:MM:SS }
@return : HHMMSS
*/
ulong str_to_time_as_long(const char *str, uint length)
{
uint i,date[3];
const char *end= str+length;
if ( length == 0 )
return 0;
for ( ; !isdigit(*str) && str != end ; ++str ) --length;
for ( i= 0 ; i < 3 && str != end; ++i )
{
uint tmp_value= (uint) (uchar) (*str++ - '0');
--length;
while ( str != end && isdigit(str[0]) )
{
tmp_value= tmp_value*10 + (uint) (uchar) (*str - '0');
++str;
--length;
}
date[i]= tmp_value;
while ( str != end && !isdigit(*str) )
{
++str;
--length;
}
}
if ( length && str != end )
return str_to_time_as_long(str, length);/* timestamp format */
if ( date[0] > 10000L || i < 3 ) /* Properly handle HHMMSS format */
return(ulong) date[0];
return(ulong) date[0] * 10000L + (ulong) (date[1]*100L+date[2]);
}
/*
@type : myodbc internal
@purpose : if there was a long time since last question, check that
the server is up with mysql_ping (to force a reconnect)
*/
int check_if_server_is_alive( DBC *dbc )
{
time_t seconds= (time_t) time( (time_t*)0 );
int result= 0;
if ( (ulong)(seconds - dbc->last_query_time) >= CHECK_IF_ALIVE )
{
if ( mysql_ping( dbc->mysql ) )
{
/* BUG: 14639
A. The 4.1 doc says when mysql_ping() fails we can get one
of the following errors from mysql_errno();
CR_COMMANDS_OUT_OF_SYNC
CR_SERVER_GONE_ERROR
CR_UNKNOWN_ERROR
But if you do a mysql_ping() after bringing down the server
you get CR_SERVER_LOST.
PAH - 9.MAR.06
*/
if (is_connection_lost(mysql_errno( dbc->mysql )))
result = 1;
}
}
dbc->last_query_time = seconds;
return result;
}
/*
@type : myodbc3 internal
@purpose : appends quoted string to dynamic string
*/
bool myodbc_append_quoted_name_std(std::string &str, const char *name)
{
size_t namelen = strlen(name);
str.reserve(str.length() + namelen + 4);
str.append(1, '`').append(name).append(1, '`');
return false;
}
/*
@type : myodbc3 internal
@purpose : reset the db name to current_database()
*/
int reget_current_catalog(DBC *dbc)
{
dbc->database.clear();
if (dbc->execute_query("select database()", SQL_NTS, true))
{
return 1;
}
else
{
MYSQL_RES *res;
MYSQL_ROW row;
if ( (res= mysql_store_result(dbc->mysql)) &&
(row= mysql_fetch_row(res)) )
{
/* if (cmp_database(row[0], dbc->database)) */
{
if ( row[0] )
{
dbc->database = row[0];
}
}
}
mysql_free_result(res);
}
return 0;
}
/*
@type : myodbc internal
@purpose : compare strings without regarding to case
*/
int myodbc_strcasecmp(const char *s, const char *t)
{
if (!s && !t)
{
return 0;
}
if (!s || !t)
{
return 1;
}
while (toupper((uchar) *s) == toupper((uchar) *t++))
if (!*s++)
return 0;
return((int) toupper((uchar) s[0]) - (int) toupper((uchar) t[-1]));
}
/*
@type : myodbc internal
@purpose : compare strings without regarding to case
*/
int myodbc_casecmp(const char *s, const char *t, uint len)
{
if (!s && !t)
{
return 0;
}
if ((!s && t) || (s && !t))
return 1;
if (!s || !t)
{
return (int)len + 1;
}
while (len-- != 0 && toupper(*s++) == toupper(*t++))
;
return (int)len + 1;
}
/*
@type : myodbc3 internal
@purpose : logs the queries sent to server
*/
void query_print(FILE *log_file,char *query)
{
if ( log_file && query )
{
/*
because of bug 68201 we bring the result of time() call
to 64-bits in any case
*/
long long time_now= time(NULL);
fprintf(log_file, "%lld:%s;\n", time_now, query);
}
}
FILE *init_query_log(void)
{
FILE *query_log;
#ifdef _WIN32
char filename[MAX_PATH];
size_t buffsize;
getenv_s(&buffsize, filename, sizeof(filename), "TEMP");
if (buffsize)
{
sprintf(filename + buffsize - 1, "\\%s", DRIVER_QUERY_LOGFILE);
}
else
{
sprintf(filename, "c:\\%s", DRIVER_QUERY_LOGFILE);
}
if ( (query_log= fopen(filename, "a+")) )
#else
if ( (query_log= fopen(DRIVER_QUERY_LOGFILE, "a+")) )
#endif
{
fprintf(query_log,"-- Query logging\n");
fprintf(query_log,"--\n");
fprintf(query_log,"-- Driver name: %s Version: %s\n",DRIVER_NAME,
DRIVER_VERSION);
#ifdef HAVE_LOCALTIME_R
{
time_t now= time(NULL);
struct tm start;
localtime_r(&now,&start);
fprintf(query_log,"-- Timestamp: %02d%02d%02d %2d:%02d:%02d\n",
start.tm_year % 100,
start.tm_mon+1,
start.tm_mday,
start.tm_hour,
start.tm_min,
start.tm_sec);
}
#endif /* HAVE_LOCALTIME_R */
fprintf(query_log,"\n");
}
return query_log;
}
void end_query_log(FILE *query_log)
{
if ( query_log )
{
fclose(query_log);
query_log= 0;
}
}
my_bool is_minimum_version(const char *server_version,const char *version)
{
/*
Variables have to be initialized if we don't want to get random
values after sscanf
*/
uint major1= 0, major2= 0, minor1= 0, minor2= 0, build1= 0, build2= 0;
sscanf(server_version, "%u.%u.%u", &major1, &minor1, &build1);
sscanf(version, "%u.%u.%u", &major2, &minor2, &build2);
if ( major1 > major2 ||
major1 == major2 && (minor1 > minor2 ||
minor1 == minor2 && build1 >= build2))
{
return TRUE;
}
return FALSE;
}
/*
Escapes a string that may contain wildcard characters (%, _) and other
problematic characters (", ', \n, etc) to produce a valid SQL string literal to be enclosed
in single quotes (the quotes are not added).
If escape_id is true then treats input string as an identifier name and produces literal to be
enclosed in backquotes -- in that case the only escaping that happens is doubling the backquote
character if it is part of the identifier name.
Note: If server flag SERVER_STATUS_NO_BACKSLASH_ESCAPES is enabled then no backslash escaping
can be used and the only escaping that happens is doubling single quote characters.
Note: This function is analogous to mysql_real_escape_string() but can also escape wildcard
characters.
@param[in] mysql Pointer to MYSQL structure
@param[out] to Buffer for escaped string
@param[in] to_length Length of destination buffer, or 0 for "big enough"
@param[in] from The string to escape
@param[in] length The length of the string to escape
@param[in] escape_id If true then escape an identifier, otherwise a string literal
@param[in] esc_wildcard Escaping wildcard (true) _ and % or allowing
wildcard characters (false) unescaped
*/
ulong myodbc_escape_string(STMT *stmt,
char *to, ulong to_length,
const char *from, ulong length,
bool escape_id,
bool esc_wildcard)
{
const char *to_start= to;
const char *end, *to_end=to_start + (to_length ? to_length-1 : 2*length);
my_bool overflow= FALSE;
myodbc::CHARSET_INFO *charset_info= stmt->dbc->cxn_charset_info;
/*
The character to be escaped by doubling it -- for identifiers it is the backquote, for string
literals it is the single quote.
*/
char double_char = (escape_id ? '`' : '\'');
/*
Whether to use backslash escapes. Note that this is not the case when escaping an identifier.
*/
bool use_backslash
= (!escape_id &&
!(stmt->dbc->mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES));
my_bool use_mb_flag= use_mb(charset_info);
for (end= from + length; from < end; ++from)
{
char escape= 0;
int tmp_length;
// Handle multi-byte sequences
if (use_mb_flag && (tmp_length= my_ismbchar(charset_info, from, end)))
{
if (to + tmp_length > to_end)
{
overflow= TRUE;
break;
}
while (tmp_length--)
*to++= *from++;
--from;
continue;
}
if (double_char && *from == double_char)
escape = double_char;
/*
If the next character appears to begin a multi-byte character, we
escape that first byte of that apparent multi-byte character. (The
character just looks like a multi-byte character -- if it were actually
a multi-byte character, it would have been passed through in the test
above.)
Without this check, we can create a problem by converting an invalid
multi-byte character into a valid one. For example, 0xbf27 is not
a valid GBK character, but 0xbf5c is. (0x27 = ', 0x5c = \)
*/
else if (use_mb_flag && (tmp_length= my_mbcharlen(charset_info, *from)) > 1)
escape= *from;
/*
Note: If use_backslash is false then we will not set `escape` character and the logic
below will copy input to output as is.
*/
else if (use_backslash)
switch (*from)
{
case 0: // Must be escaped for 'mysql'
escape= '0';
break;
case '\n': // Must be escaped for logs
escape= 'n';
break;
case '\r':
escape= 'r';
break;
case '\\':
case '\'':
case '"': // Better safe than sorry
escape= *from;
break;
case '\032': // This gives problems on Win32
escape= 'Z';
break;
case '_': // Escaping of the pattern characters
case '%':
if (esc_wildcard)
escape = *from;
break;
}
if (escape)
{
if (to + 2 > to_end)
{
overflow= TRUE;
break;
}
*to++= (escape == double_char ? escape : '\\');
*to++= escape;
}
else
{
if (to + 1 > to_end)
{
overflow= TRUE;
break;
}
*to++= *from;
}
} // for
*to= 0;
return overflow ? (ulong)~0 : (ulong) (to - to_start);
}
/**
Scale an int[] representing SQL_C_NUMERIC
@param[in] ary Array in little endian form
@param[in] s Scale
*/
void sqlnum_scale(unsigned *ary, int s)
{
/* multiply out all pieces */
while (s--)
{
ary[0] *= 10;
ary[1] *= 10;
ary[2] *= 10;
ary[3] *= 10;
ary[4] *= 10;
ary[5] *= 10;
ary[6] *= 10;
ary[7] *= 10;
}
}
/**
Unscale an int[] representing SQL_C_NUMERIC. This
leaves the last element (0) with the value of the
last digit.
@param[in] ary Array in little endian form
*/
static void sqlnum_unscale_le(unsigned *ary)
{
int i;
for (i= 7; i > 0; --i)
{
ary[i - 1] += (ary[i] % 10) << 16;
ary[i] /= 10;
}
}
/**
Unscale an int[] representing SQL_C_NUMERIC. This
leaves the last element (7) with the value of the
last digit.
@param[in] ary Array in big endian form
*/
static void sqlnum_unscale_be(unsigned *ary, int start)
{
int i;
for (i= start; i < 7; ++i)
{
ary[i + 1] += (ary[i] % 10) << 16;
ary[i] /= 10;
}
}
/**
Perform the carry to get all elements below 2^16.
Should be called right after sqlnum_scale().
@param[in] ary Array in little endian form
*/
static void sqlnum_carry(unsigned *ary)
{
int i;
/* carry over rest of structure */
for (i= 0; i < 7; ++i)
{
ary[i+1] += ary[i] >> 16;
ary[i] &= 0xffff;
}
}
/**
Retrieve a SQL_NUMERIC_STRUCT from a string. The requested scale
and precesion are first read from sqlnum, and then updated values
are written back at the end.
@param[in] numstr String representation of number to convert
@param[in] sqlnum Destination struct
@param[in] overflow_ptr Whether or not whole-number overflow occurred.
This indicates failure, and the result of sqlnum
is undefined.
*/
void sqlnum_from_str(const char *numstr, SQL_NUMERIC_STRUCT *sqlnum,
int *overflow_ptr)
{
/*
We use 16 bits of each integer to convert the
current segment of the number leaving extra bits
to multiply/carry
*/
unsigned build_up[8], tmp_prec_calc[8];
/* current segment as integer */
unsigned int curnum;
/* current segment digits copied for strtoul() */
char curdigs[5];
/* number of digits in current segment */
int usedig;
int i;
int len;
char *decpt= strchr((char*)numstr, '.');
int overflow= 0;
SQLSCHAR reqscale= sqlnum->scale;
SQLCHAR reqprec= sqlnum->precision;
memset(&sqlnum->val, 0, sizeof(sqlnum->val));
memset(build_up, 0, sizeof(build_up));
/* handle sign */
if (!(sqlnum->sign= !(*numstr == '-')))
++numstr;
len= (int) strlen(numstr);
sqlnum->precision= len;
sqlnum->scale= 0;
/* process digits in groups of <=4 */
for (i= 0; i < len; i += usedig)
{
if (i + 4 < len)
usedig= 4;
else
usedig= len - i;
/*
if we have the decimal point, ignore it by setting it to the
last char (will be ignored by strtoul)
*/
if (decpt && decpt >= numstr + i && decpt < numstr + i + usedig)
{
usedig = (int) (decpt - (numstr + i) + 1);
sqlnum->scale= len - (i + usedig);
--sqlnum->precision;
decpt= NULL;
}
if (overflow)
goto end;
/* grab just this piece, and convert to int */
memcpy(curdigs, numstr + i, usedig);
curdigs[usedig]= 0;
curnum= strtoul(curdigs, NULL, 10);
if (curdigs[usedig - 1] == '.')
sqlnum_scale(build_up, usedig - 1);
else
sqlnum_scale(build_up, usedig);
/* add the current number */
build_up[0] += curnum;
sqlnum_carry(build_up);
if (build_up[7] & ~0xffff)
overflow= 1;
}
/* scale up to SQL_DESC_SCALE */
if (reqscale > 0 && reqscale > sqlnum->scale)
{
while (reqscale > sqlnum->scale)
{
sqlnum_scale(build_up, 1);
sqlnum_carry(build_up);
++sqlnum->scale;
}
}
/* scale back, truncating decimals */
else if (reqscale < sqlnum->scale)
{
while (reqscale < sqlnum->scale && sqlnum->scale > 0)
{
sqlnum_unscale_le(build_up);
// Value 2 of overflow indicates truncation, not critical
if (build_up[0] % 10)
overflow = 2;
build_up[0] /= 10;
--sqlnum->precision;
--sqlnum->scale;
}
}
/* scale back whole numbers while there's no significant digits */
if (reqscale < 0)
{
memcpy(tmp_prec_calc, build_up, sizeof(build_up));
while (reqscale < sqlnum->scale)
{
sqlnum_unscale_le(tmp_prec_calc);
if (tmp_prec_calc[0] % 10)
{
overflow= 1;
goto end;
}
sqlnum_unscale_le(build_up);
tmp_prec_calc[0] /= 10;
build_up[0] /= 10;
--sqlnum->precision;
--sqlnum->scale;
}
}
/* calculate minimum precision */
memcpy(tmp_prec_calc, build_up, sizeof(build_up));
{
SQLCHAR temp_precision = sqlnum->precision;
do
{
sqlnum_unscale_le(tmp_prec_calc);
i= tmp_prec_calc[0] % 10;
tmp_prec_calc[0] /= 10;
if (i == 0)
--temp_precision;
} while (i == 0 && temp_precision > 0);
/* detect precision overflow */
if (temp_precision > reqprec)
overflow= 1;
}
/* compress results into SQL_NUMERIC_STRUCT.val */
for (i= 0; i < 8; ++i)
{
int elem= 2 * i;
sqlnum->val[elem]= build_up[i] & 0xff;
sqlnum->val[elem+1]= (build_up[i] >> 8) & 0xff;
}
end:
if (overflow_ptr)
*overflow_ptr= overflow;
}
/**
Convert a SQL_NUMERIC_STRUCT to a string. Only val and sign are
read from the struct. precision and scale will be updated on the
struct with the final values used in the conversion.
@param[in] sqlnum Source struct
@param[in] numstr Buffer to convert into string. Note that you
MUST use numbegin to read the result string.
This should point to the LAST byte available.
(We fill in digits backwards.)
@param[in,out] numbegin String pointer that will be set to the start of
the result string.
@param[in] reqprec Requested precision
@param[in] reqscale Requested scale
@param[in] truncptr Pointer to set the truncation type encountered.
If SQLNUM_TRUNC_WHOLE, this indicates a failure
and the contents of numstr are undefined and
numbegin will not be written to.
*/
void sqlnum_to_str(SQL_NUMERIC_STRUCT *sqlnum, SQLCHAR *numstr,
SQLCHAR **numbegin, SQLCHAR reqprec, SQLSCHAR reqscale,
int *truncptr)
{
unsigned expanded[8];
int i, j;
int max_space= 0;
int calcprec= 0;
int trunc= 0; /* truncation indicator */
*numstr--= 0;
/*
it's expected to have enough space
(~at least min(39, max(prec, scale+2)) + 3)
*/
/*
expand the packed sqlnum->val so we have space to divide through
expansion happens into an array in big-endian form
*/
for (i= 0; i < 8; ++i)
expanded[7 - i]= (sqlnum->val[(2 * i) + 1] << 8) | sqlnum->val[2 * i];
/* max digits = 39 = log_10(2^128)+1 */
for (j= 0; j < 39; ++j)
{
/* skip empty prefix */
while (!expanded[max_space])
++max_space;
/* if only the last piece has a value, it's the end */
if (max_space >= 7)
{
i= 7;
if (!expanded[7])
{
/* special case for zero, we'll end immediately */
if (!*(numstr + 1))
{
*numstr--= '0';
calcprec= 1;
}
break;
}
}
else
{
/* extract the next digit */
sqlnum_unscale_be(expanded, max_space);
}
*numstr--= '0' + (expanded[7] % 10);
expanded[7] /= 10;
++calcprec;
if (j == reqscale - 1)
*numstr--= '.';
}
sqlnum->scale= reqscale;
/* add <- dec pt */
if (calcprec < reqscale)
{
while (calcprec < reqscale)
{
*numstr--= '0';
--reqscale;
}
*numstr--= '.';
*numstr--= '0';
}
/* handle fractional truncation */
if (calcprec > reqprec && reqscale > 0)
{
SQLCHAR *end= numstr + strlen((char *)numstr) - 1;
while (calcprec > reqprec && reqscale)
{
*end--= 0;
--calcprec;
--reqscale;
}
if (calcprec > reqprec && reqscale == 0)
{
trunc= SQLNUM_TRUNC_WHOLE;
goto end;
}
if (*end == '.')
{
*end--= '\0';
}
else
{
/* move the dec pt-- ??? */
/*
char c2, c= numstr[calcprec - reqscale];
numstr[calcprec - reqscale]= '.';
while (reqscale)
{
c2= numstr[calcprec + 1 - reqscale];
numstr[calcprec + 1 - reqscale]= c;
c= c2;
--reqscale;
}
*/
}
trunc= SQLNUM_TRUNC_FRAC;
}
/* add zeros for negative scale */
if (reqscale < 0)
{
int i;
reqscale *= -1;
for (i= 1; i <= calcprec; ++i)
*(numstr + i - reqscale)= *(numstr + i);
numstr -= reqscale;
memset(numstr + calcprec + 1, '0', reqscale);
}
sqlnum->precision= calcprec;
/* finish up, handle auxilary fix-ups */
if (!sqlnum->sign)
{
*numstr--= '-';
}
++numstr;
*numbegin= numstr;
end:
if (truncptr)
*truncptr= trunc;
}
/**
Adjust a pointer based on bind offset and bind type.
@param[in] ptr The base pointer
@param[in] bind_offset_ptr The bind offset ptr (can be NULL).
(SQL_ATTR_PARAM_BIND_OFFSET_PTR, SQL_ATTR_ROW_BIND_OFFSET_PTR,
SQL_DESC_BIND_OFFSET_PTR)
@param[in] bind_type The bind type. Should be SQL_BIND_BY_COLUMN (0) or
the length of a row for row-wise binding. (SQL_ATTR_PARAM_BIND_TYPE,
SQL_ATTR_ROW_BIND_TYPE, SQL_DESC_BIND_TYPE)
@param[in] default_size The column size if bind type = SQL_BIND_BY_COLUMN.
@param[in] row The row number.
@return The base pointer with the offset added. If the base pointer is
NULL, NULL is returned.
*/
void *ptr_offset_adjust(void *ptr, SQLULEN *bind_offset_ptr,
SQLINTEGER bind_type, SQLINTEGER default_size,
SQLULEN row)
{
size_t offset= 0;
if (bind_offset_ptr)
offset= (size_t) *bind_offset_ptr;
if (bind_type == SQL_BIND_BY_COLUMN)
offset+= default_size * row;
else
offset+= bind_type * row;
return ptr ? ((SQLCHAR *) ptr) + offset : NULL;
}
/**
Sets the value of @@sql_select_limit
@param[in] dbc dbc handler
@param[in] new_value Value to set @@sql_select_limit.
@param[in] req_lock The flag if dbc->lock thread lock should be used
when executing a query
Returns new_value if operation was successful, -1 otherwise
*/
SQLRETURN set_sql_select_limit(DBC *dbc, SQLULEN lim_value, my_bool req_lock)
{
char query[44];
SQLRETURN rc;
/* Both 0 and max(SQLULEN) value mean no limit and sql_select_limit to DEFAULT */
if (lim_value == dbc->sql_select_limit
|| lim_value == sql_select_unlimited && dbc->sql_select_limit == 0)
return SQL_SUCCESS;
if (lim_value > 0 && lim_value < sql_select_unlimited)
myodbc_snprintf(query, sizeof(query), "set @@sql_select_limit=%lu",
(unsigned long)lim_value);
else
{
strcpy(query, "set @@sql_select_limit=DEFAULT");
lim_value= 0;
}
if (SQL_SUCCEEDED(rc = dbc->execute_query(query, SQL_NTS, req_lock)))
{
dbc->sql_select_limit= lim_value;
}
return rc;
}
/**
Detects the parameter type.
@param[in] proc procedure parameter string
@param[in] len param string length
@param[out] ptype pointer where to write the param type
Returns position in the param string after parameter type
*/
char *proc_get_param_type(char *proc, int len, SQLSMALLINT *ptype)
{
while (isspace(*proc) && (len--))
++proc;
if (len >= 6 && !myodbc_casecmp(proc, "INOUT ", 6))
{
*ptype= (SQLSMALLINT) SQL_PARAM_INPUT_OUTPUT;
return proc + 6;
}
if (len >= 4 && !myodbc_casecmp(proc, "OUT ", 4))
{
*ptype= (SQLSMALLINT) SQL_PARAM_OUTPUT;
return proc + 4;
}
if (len >= 3 && !myodbc_casecmp(proc, "IN ", 3))
{
*ptype= (SQLSMALLINT) SQL_PARAM_INPUT;
return proc + 3;
}
*ptype= (SQLSMALLINT)SQL_PARAM_INPUT;
return proc;
}
/**
Detects the parameter name
@param[in] proc procedure parameter string
@param[in] len param string length
@param[out] cname pointer where to write the param name
Returns position in the param string after parameter name
*/
char* proc_get_param_name(char *proc, int len, char *cname)
{
char quote_symbol= '\0';
while (isspace(*proc) && (len--))
++proc;
/* can be '"' if ANSI_QUOTE is enabled */
if (*proc == '`' || *proc == '"')
{
quote_symbol= *proc;
++proc;
}
while ((len--) && (quote_symbol != '\0' ? *proc != quote_symbol : !isspace(*proc)))
*(cname++)= *(proc++);
return quote_symbol ? proc + 1 : proc;
}
/**
Detects the parameter data type
@param[in] proc procedure parameter string
@param[in] len param string length
@param[out] cname pointer where to write the param type name
Returns position in the param string after parameter type name
*/
char* proc_get_param_dbtype(char *proc, int len, char *ptype)
{
char *trim_str, *start_pos= ptype;
while (isspace(*proc) && (len--))
++proc;
while (*proc && (len--) )
*(ptype++)= *(proc++);
/* remove the character set definition */
if (trim_str= strstr( myodbc_strlwr(start_pos, (size_t)-1),
" charset "))
{
ptype= trim_str;
(*ptype)= 0;
}
/* trim spaces from the end */
ptype-=1;
while (isspace(*(ptype)))
{
*ptype= 0;
--ptype;
}
return proc;
}
SQLTypeMap SQL_TYPE_MAP_values[]=
{
/* SQL_CHAR= 1 */
{(SQLCHAR*)"char", 4, SQL_CHAR, MYSQL_TYPE_STRING, 0, 0},
{(SQLCHAR*)"enum", 4, SQL_CHAR, MYSQL_TYPE_STRING, 0, 0},
{(SQLCHAR*)"set", 3, SQL_CHAR, MYSQL_TYPE_STRING, 0, 0},
/* SQL_BIT= -7 */
{(SQLCHAR*)"bit", 3, SQL_BIT, MYSQL_TYPE_BIT, 1, 1},
{(SQLCHAR*)"bool", 4, SQL_BIT, MYSQL_TYPE_BIT, 1, 1},
/* SQL_TINY= -6 */
{(SQLCHAR*)"tinyint", 7, SQL_TINYINT, MYSQL_TYPE_TINY, 1, 1},
/* SQL_BIGINT= -5 */
{(SQLCHAR*)"bigint", 6, SQL_BIGINT, MYSQL_TYPE_LONGLONG, 20, 1},
/* SQL_LONGVARBINARY= -4 */
{(SQLCHAR*)"long varbinary", 14, SQL_LONGVARBINARY, MYSQL_TYPE_MEDIUM_BLOB, 16777215, 1},
{(SQLCHAR*)"blob", 4, SQL_LONGVARBINARY, MYSQL_TYPE_BLOB, 65535, 1},
{(SQLCHAR*)"longblob", 8, SQL_LONGVARBINARY, MYSQL_TYPE_LONG_BLOB, 4294967295UL, 1},
{(SQLCHAR*)"tinyblob", 8, SQL_LONGVARBINARY, MYSQL_TYPE_TINY_BLOB, 255, 1},
{(SQLCHAR*)"mediumblob", 10, SQL_LONGVARBINARY, MYSQL_TYPE_MEDIUM_BLOB, 16777215,1 },
/* SQL_VARBINARY= -3 */
{(SQLCHAR*)"varbinary", 9, SQL_VARBINARY, MYSQL_TYPE_VAR_STRING, 0, 1},
{(SQLCHAR*)"vector", 9, SQL_VARBINARY, MYSQL_TYPE_VECTOR, 16382 * 4, 1},
/* SQL_BINARY= -2 */
{(SQLCHAR*)"binary", 6, SQL_BINARY, MYSQL_TYPE_STRING, 0, 1},
/* SQL_LONGVARCHAR= -1 */
{(SQLCHAR*)"long varchar", 12, SQL_LONGVARCHAR, MYSQL_TYPE_MEDIUM_BLOB, 16777215, 0},
{(SQLCHAR*)"text", 4, SQL_LONGVARCHAR, MYSQL_TYPE_BLOB, 65535, 0},
{(SQLCHAR*)"mediumtext", 10, SQL_LONGVARCHAR, MYSQL_TYPE_MEDIUM_BLOB, 16777215, 0},
{(SQLCHAR*)"longtext", 8, SQL_LONGVARCHAR, MYSQL_TYPE_LONG_BLOB, 4294967295UL, 0},
{(SQLCHAR*)"tinytext", 8, SQL_LONGVARCHAR, MYSQL_TYPE_TINY_BLOB, 255, 0},
/* SQL_NUMERIC= 2 */
{(SQLCHAR*)"numeric", 7, SQL_NUMERIC, MYSQL_TYPE_DECIMAL, 0, 1},
/* SQL_DECIMAL= 3 */
{(SQLCHAR*)"decimal", 7, SQL_DECIMAL, MYSQL_TYPE_DECIMAL, 0, 1},
/* SQL_INTEGER= 4 */
{(SQLCHAR*)"int", 3, SQL_INTEGER, MYSQL_TYPE_LONG, 10, 1},
{(SQLCHAR*)"mediumint", 9, SQL_INTEGER, MYSQL_TYPE_INT24, 8, 1},
/* SQL_SMALLINT= 5 */
{(SQLCHAR*)"smallint", 8, SQL_SMALLINT, MYSQL_TYPE_SHORT, 5, 1},
/* SQL_REAL= 7 */
{(SQLCHAR*)"float", 5, SQL_REAL, MYSQL_TYPE_FLOAT, 7, 1},
/* SQL_DOUBLE= 8 */
{(SQLCHAR*)"double", 6, SQL_DOUBLE, MYSQL_TYPE_DOUBLE, 15, 1},
/* SQL_DATETIME= 9 */
{(SQLCHAR*)"datetime", 8, SQL_TYPE_TIMESTAMP, MYSQL_TYPE_DATETIME, 19, 1},
/* SQL_VARCHAR= 12 */
{(SQLCHAR*)"varchar", 7, SQL_VARCHAR, MYSQL_TYPE_VARCHAR, 0, 0},
/* SQL_TYPE_DATE= 91 */
{(SQLCHAR*)"date", 4, SQL_TYPE_DATE, MYSQL_TYPE_DATE, 10, 1},
/* YEAR - SQL_SMALLINT */
{(SQLCHAR*)"year", 4, SQL_SMALLINT, MYSQL_TYPE_YEAR, 2, 1},
/* SQL_TYPE_TIMESTAMP= 93 */
{(SQLCHAR*)"timestamp", 9, SQL_TYPE_TIMESTAMP, MYSQL_TYPE_TIMESTAMP, 19, 1},
/* SQL_TYPE_TIME= 92 */
{(SQLCHAR*)"time", 4, SQL_TYPE_TIME, MYSQL_TYPE_TIME, 8, 1}
};
enum enum_field_types map_sql2mysql_type(SQLSMALLINT sql_type)
{
for (auto &elem : SQL_TYPE_MAP_values)
{
if (elem.sql_type == sql_type)
{
return (enum_field_types)elem.mysql_type;
}
}
return MYSQL_TYPE_BLOB;
}
/**
Gets the parameter index in the type map array
@param[in] ptype procedure parameter type name
@param[in] len param string length
Returns position in the param string after parameter type name
*/
int proc_get_param_sql_type_index(const char *ptype, int len)
{
int i = 0;
for (auto &elem : SQL_TYPE_MAP_values)
{
if (len >= elem.name_length &&
(!myodbc_casecmp(ptype, (const char*)elem.type_name,
elem.name_length)))
return i;
++i;
}
return 0; /* "char" */
}
/**
Gets the parameter info array from the map using index
@param[in] index index in the param info array
Pointer to the structure that contains parameter info
*/
SQLTypeMap *proc_get_param_map_by_index(int index)
{
return &SQL_TYPE_MAP_values[index];
}
/**
Parses parameter size and decimal digits
@param[in] ptype parameter type name
@param[in] len type string length
@param[out] dec pointer where to write decimal digits
Returns parsed size
*/
SQLUINTEGER proc_parse_sizes(SQLCHAR *ptype, int len, SQLSMALLINT *dec)
{
int parsed= 0;
SQLUINTEGER param_size= 0;
if (ptype == NULL)
{
/* That shouldn't happen though */
return 0;
}
while ((len > 0) && (*ptype!= ')') && (parsed < 2))
{
int n_index= 0;
char number_to_parse[16]= "\0";
/* skip all non-digit characters */
while (!isdigit(*ptype) && (len-- >= 0) && (*ptype!= ')'))
++ptype;
/* add digit characters to the buffer for parsing */
while (isdigit(*ptype) && (len-- >= 0))
{
number_to_parse[n_index++]= *ptype;
++ptype;
}
/* 1st number is column size, 2nd is decimal digits */
if (!parsed)
param_size= atoi(number_to_parse);
else
*dec= (SQLSMALLINT)atoi(number_to_parse);
++parsed;
}
return param_size;
}
/**
Determines the length of ENUM/SET
@param[in] ptype parameter type name
@param[in] len type string length
@param[in] is_enum flag to treat string as ENUM
instead of SET
Returns size of ENUM/SET
*/
SQLUINTEGER proc_parse_enum_set(SQLCHAR *ptype, int len, BOOL is_enum)
{
SQLUINTEGER total_len= 0, elem_num= 0, max_len= 0, cur_len= 0;
char quote_symbol= '\0';
/* theoretically ')' can be inside quotes as part of enum value */
while ((len > 0) && (quote_symbol != '\0' || *ptype!= ')'))
{
if (*ptype == quote_symbol)
{
quote_symbol= '\0';
max_len= myodbc_max(cur_len, max_len);
}
else if (*ptype == '\'' || *ptype == '"')
{
quote_symbol= *ptype;
cur_len= 0;
++elem_num;
}
else if (quote_symbol)
{
++cur_len;
++total_len;
}
++ptype;
--len;
}
return is_enum ? max_len : total_len + elem_num - 1;
}
/**
Returns parameter size and decimal digits
@param[in] ptype parameter type name
@param[in] len type string length
@param[in] sql_type_index index in the param info array
@param[out] dec pointer where to write decimal digits
Returns parameter size
*/
SQLUINTEGER proc_get_param_size(SQLCHAR *ptype, int len, int sql_type_index, SQLSMALLINT *dec)
{
SQLUINTEGER param_size= SQL_TYPE_MAP_values[sql_type_index].type_length;
SQLCHAR *start_pos= (SQLCHAR*)strchr((const char*)ptype, '(');
SQLCHAR *end_pos= (SQLCHAR*)strrchr((const char*)ptype, ')');
/* no decimal digits by default */
*dec= SQL_NO_TOTAL;
switch (SQL_TYPE_MAP_values[sql_type_index].mysql_type)
{
/* these type sizes need to be parsed */
case MYSQL_TYPE_DECIMAL:
param_size = proc_parse_sizes(start_pos, (int)(end_pos - start_pos), dec);
if(!param_size)
param_size= 10; /* by default */
break;
case MYSQL_TYPE_YEAR:
*dec= 0;
param_size= proc_parse_sizes(start_pos, (int)(end_pos - start_pos), dec);
if(!param_size)
param_size= 4; /* by default */
break;
case MYSQL_TYPE_VARCHAR:
case MYSQL_TYPE_VAR_STRING:
case MYSQL_TYPE_STRING:
if (!myodbc_strcasecmp((const char*)(SQL_TYPE_MAP_values[sql_type_index].type_name), "set"))
{
param_size = proc_parse_enum_set(start_pos, (int)(end_pos - start_pos), FALSE);
}
else if (!myodbc_strcasecmp((const char*)(SQL_TYPE_MAP_values[sql_type_index].type_name), "enum"))
{
param_size = proc_parse_enum_set(start_pos, (int)(end_pos - start_pos), TRUE);
}
else /* just normal character type */
{
param_size = proc_parse_sizes(start_pos, (int)(end_pos - start_pos), dec);
if (param_size == 0 &&
SQL_TYPE_MAP_values[sql_type_index].sql_type == SQL_BINARY)
param_size= 1;
}
break;
case MYSQL_TYPE_BIT:
param_size = proc_parse_sizes(start_pos, (int)(end_pos - start_pos), dec);
/* fall through*/
case MYSQL_TYPE_DATETIME:
case MYSQL_TYPE_TINY:
case MYSQL_TYPE_SHORT:
case MYSQL_TYPE_INT24:
case MYSQL_TYPE_LONG:
case MYSQL_TYPE_LONGLONG:
*dec= 0;
break;
}
return param_size;
}
/**
Gets parameter columns size
@param[in] stmt statement
@param[in] sql_type_index index in the param info array
@param[in] col_size parameter size
@param[in] decimal_digits write decimal digits
@param[in] flags field flags
Returns parameter octet length
*/
SQLLEN proc_get_param_col_len(STMT *stmt, int sql_type_index, SQLULEN col_size,
SQLSMALLINT decimal_digits, unsigned int flags, char * str_buff)
{
MYSQL_FIELD temp_fld;
temp_fld.length= (unsigned long)col_size +
(SQL_TYPE_MAP_values[sql_type_index].mysql_type == MYSQL_TYPE_DECIMAL ?
1 + (flags & UNSIGNED_FLAG ? 0 : 1) : 0); /* add 1for sign, if needed, and 1 for decimal point */
temp_fld.max_length = (unsigned long)col_size;
temp_fld.decimals= decimal_digits;
// Handle binary flag and binary charset number when required
temp_fld.flags= flags + (SQL_TYPE_MAP_values[sql_type_index].binary ? BINARY_FLAG : 0);
temp_fld.type= (enum_field_types)(SQL_TYPE_MAP_values[sql_type_index].mysql_type);
if (temp_fld.type == MYSQL_TYPE_STRING ||
temp_fld.type == MYSQL_TYPE_VARCHAR ||
temp_fld.type == MYSQL_TYPE_LONG_BLOB ||
SQL_TYPE_MAP_values[sql_type_index].binary)
{
// The types with specifiers for character lengths are CHAR(N) and VARCHAR(N).
// Instead of trying to deduce their character length it should take N
// as it is. This is done by a trick of setting BINARY charset.
// For LONGTEXT we also should take its max length without trying to
// divide by character byte size.
temp_fld.charsetnr = BINARY_CHARSET_NUMBER;
}
else
{
temp_fld.charsetnr = stmt->dbc->cxn_charset_info->number;
}
if (temp_fld.type == MYSQL_TYPE_TINY_BLOB ||
temp_fld.type == MYSQL_TYPE_BLOB ||
temp_fld.type == MYSQL_TYPE_MEDIUM_BLOB)
{
// We need to mimic the behavior of mysqlclient, which is
// multiplying the length by the max character length.
temp_fld.length *= get_charset_maxlen(temp_fld.charsetnr);
}
if (str_buff != NULL)
{
return fill_column_size_buff(str_buff, stmt, &temp_fld);
}
else
{
return get_column_size( stmt, &temp_fld);
}
}
/**
Gets parameter octet length
@param[in] stmt statement
@param[in] sql_type_index index in the param info array
@param[in] col_size parameter size
@param[in] decimal_digits write decimal digits
@param[in] flags field flags
Returns parameter octet length
*/
SQLLEN proc_get_param_octet_len(STMT *stmt, int sql_type_index, SQLULEN col_size,
SQLSMALLINT decimal_digits, unsigned int flags, char * str_buff)
{
MYSQL_FIELD temp_fld;
temp_fld.length= (unsigned long)col_size +
(SQL_TYPE_MAP_values[sql_type_index].mysql_type == MYSQL_TYPE_DECIMAL ?
1 + (flags & UNSIGNED_FLAG ? 0 : 1) : 0); /* add 1for sign, if needed, and 1 for decimal point */
temp_fld.max_length = (unsigned long)col_size;
temp_fld.decimals= decimal_digits;
// Handle binary flag and binary charset number when required
temp_fld.flags= flags + (SQL_TYPE_MAP_values[sql_type_index].binary ? BINARY_FLAG : 0);
temp_fld.charsetnr =
SQL_TYPE_MAP_values[sql_type_index].binary ?
BINARY_CHARSET_NUMBER : stmt->dbc->cxn_charset_info->number;
temp_fld.type= (enum_field_types)(SQL_TYPE_MAP_values[sql_type_index].mysql_type);
if (str_buff != NULL)
{
return fill_transfer_oct_len_buff(str_buff, stmt, &temp_fld);
}
else
{
return get_transfer_octet_length(stmt, &temp_fld);
}
}
/**
tokenize the string by putting \0 bytes to separate params
@param[in] str parameters string
@param[out] params_num number of detected parameters
Returns pointer to the first param
*/
char *proc_param_tokenize(char *str, int *params_num)
{
BOOL bracket_open= 0;
char *str_begin= str, quote_symbol='\0';
int len = (int)strlen(str);
*params_num= 0;
/* if no params at all */
while (len > 0 && isspace(*str))
{
++str;
--len;
}
if (len && *str && *str != ')')
*params_num= 1;
while (len > 0)
{
/* Making sure that a bracket is not inside quotes. that's possible for SET
or ENUM values */
if (quote_symbol == '\0')
{
if (!bracket_open && *str == ',')
{
*str= '\0';
++(*params_num);
}
else if (*str == '(')
{
bracket_open= 1;
}
else if (*str == ')')
{
bracket_open= 0;
}
else if (*str == '"' || *str == '\'')
{
quote_symbol= *str;
}
}
else if( *str == quote_symbol)
{
quote_symbol= '\0';
}
++str;
--len;
}
return str_begin;
}
/**
goes to the next token in \0-terminated string sequence
@param[in] str parameters string
@param[in] str_end end of the sequence
Returns pointer to the next token in sequence
*/
char *proc_param_next_token(char *str, char *str_end)
{
size_t end_token = strlen(str);
/* return the next string after \0 byte */
if (str + end_token + 1 < str_end)
return (char*)(str + end_token + 1);
return 0;
}
/**
Sets row_count in STMT's MYSQL_RES and affected rows property MYSQL object. Primary use is to set
number of affected rows for constructed resulsets. Setting mysql.affected_rows
is required for SQLRowCount to return correct data for such resultsets.
*/
void set_row_count(STMT *stmt, my_ulonglong rows)
{
if (stmt != NULL && stmt->result != NULL)
{
stmt->result->row_count= rows;
stmt->dbc->mysql->affected_rows= rows;
}
}
/**
Gets fractional time of a second from datetime or time string.
@param[in] value (date)time string
@param[in] len length of value buffer
@param[in] dont_use_set_locale use dot as decimal part separator
@param[out] fraction buffer where to put fractional part
in nanoseconds
Returns pointer to decimal point in the string
*/
const char *
get_fractional_part(const char * str, int len, BOOL dont_use_set_locale,
SQLUINTEGER * fraction)
{
const char *decptr= NULL, *end;
size_t decpoint_len= 1;
if (len < 0)
{
len = (int)strlen(str);
}
end= str + len;
if (dont_use_set_locale)
{
decptr= strchr(str, '.');
}
else
{
decpoint_len = decimal_point.length();
const char *locale_decimal_point = decimal_point.c_str();
while (*str && str < end)
{
if (str[0] == locale_decimal_point[0] &&
myodbc::is_prefix(str, locale_decimal_point))
{
decptr= str;
break;
}
++str;
}
}
/* If decimal point is the last character - we don't have fractional part */
if (decptr && decptr < end - decpoint_len)
{
char buff[10], *ptr;
myodbc::strfill(buff, sizeof(buff)-1, '0');
str= decptr + decpoint_len;
for (ptr= buff; str < end && ptr < buff + sizeof(buff); ++ptr)
{
/* there actually should not be anything that is not a digit... */
if (isdigit(*str))
{
*ptr= *str++;
}
}
buff[9]= 0;
*fraction= atoi(buff);
}
else
{
*fraction= 0;
decptr= NULL;
}
return decptr;
}
/* Convert MySQL timestamp to full ANSI timestamp format. */
char * complete_timestamp(const char * value, ulong length, char buff[21])
{
char *pos;
uint i;
if (length == 6 || length == 10 || length == 12)
{
/* For two-digit year, < 60 is considered after Y2K */
if (value[0] <= '6')
{
buff[0]= '2';
buff[1]= '0';
}
else
{
buff[0]= '1';
buff[1]= '9';
}
}
else
{
buff[0]= value[0];
buff[1]= value[1];
value+= 2;
length-= 2;
}
buff[2]= *value++;
buff[3]= *value++;
buff[4]= '-';
if (value[0] == '0' && value[1] == '0')
{
/* Month was 0, which ODBC can't handle. */
return NULL;
}
pos= buff+5;
length&= 30; /* Ensure that length is ok */
for (i= 1, length-= 2; (int)length > 0; length-= 2, ++i)
{
*pos++= *value++;
*pos++= *value++;
*pos++= i < 2 ? '-' : (i == 2) ? ' ' : ':';
}
for ( ; pos != buff + 20; ++i)
{
*pos++= '0';
*pos++= '0';
*pos++= i < 2 ? '-' : (i == 2) ? ' ' : ':';
}
return buff;
}
tempBuf::tempBuf(size_t size) : buf(nullptr), buf_len(0), cur_pos(0)
{
if (size)
extend_buffer(size);
}
tempBuf::tempBuf(const tempBuf& b)
{
*this = b;
}
tempBuf::tempBuf(const char* src, size_t len) {
add_to_buffer(src, len);
}
char *tempBuf::extend_buffer(size_t len)
{
if (cur_pos > buf_len)
throw "Position is outside of buffer";
if (len > buf_len - cur_pos)
{
buf = (char*)realloc(buf, buf_len + len);
// TODO: smarter processing for Out-of-Memory
if (buf == NULL)
throw "Not enough memory for buffering";
buf_len += len;
}
return buf + cur_pos; // Return position in the new buffer
}
char *tempBuf::extend_buffer(char *to, size_t len)
{
cur_pos = to - buf;
return extend_buffer(len);
}
char *tempBuf::add_to_buffer(const char *from, size_t len)
{
if (cur_pos > buf_len)
throw "Position is outside of buffer";
size_t extend_by = buf_len - cur_pos >= len ? 0 :
buf_len - cur_pos + len;
extend_buffer(extend_by);
memcpy(buf + cur_pos, from, len);
cur_pos += len;
return buf + cur_pos;
}
char *tempBuf::add_to_buffer(char *to, const char *from, size_t len)
{
cur_pos = to - buf;
if (cur_pos > buf_len)
throw "Position is outside of buffer";
return add_to_buffer(from, len);
}
void tempBuf::remove_trail_zeroes()
{
while (cur_pos && buf[cur_pos-1] == '\0') --cur_pos;
}
void tempBuf::reset()
{
cur_pos = 0;
}
tempBuf::~tempBuf()
{
if (buf_len && buf)
{
free(buf);
}
}
tempBuf::operator bool()
{
return buf != nullptr;
}
void tempBuf::operator=(const tempBuf& b)
{
buf_len = 0;
cur_pos = 0;
buf = nullptr;
if (b.buf_len)
{
extend_buffer(b.buf_len);
memcpy(buf, b.buf, b.cur_pos);
}
// setting to the same position as the original
cur_pos = b.cur_pos;
}
/*
Get the offset and row numbers from a string with LIMIT
@param[in] cs charset
@param[in] query query
@param[in] query_end end of query
@param[out] offs_out output buffer for offset
@param[out] rows_out output buffer for rows
@return the position where LIMIT OFFS, ROWS is ending
*/
const char* get_limit_numbers(myodbc::CHARSET_INFO* cs, const char *query, const char * query_end,
unsigned long long *offs_out, unsigned int *rows_out)
{
char digit_buf[30];
int index_pos = 0;
// Skip spaces after LIMIT
while ((query_end > query) && myodbc_isspace(cs, query, query_end))
++query;
// Collect all numbers for the offset
while ((query_end > query) && myodbc_isnum(cs, query, query_end))
{
digit_buf[index_pos] = *query;
++index_pos;
++query;
}
if (!index_pos)
{
// Something went wrong, the limit numbers are not found
return query;
}
digit_buf[index_pos] = '\0';
*offs_out = (unsigned long long)atoll(digit_buf);
// Find the row number for "LIMIT offset, row_number"
while ((query_end > query) && !myodbc_isnum(cs, query, query_end))
++query;
if (query == query_end)
{
// It was "LIMIT row_number" without offset
// What we thought was offset is in fact the number of rows
*rows_out = (unsigned long)*offs_out;
*offs_out = 0;
return query;
}
index_pos = 0; // reset index to use with another number
// Collect all numbers for the row number
while ((query_end > query) && myodbc_isnum(cs, query, query_end))
{
digit_buf[index_pos] = *query;
++index_pos;
++query;
}
digit_buf[index_pos] = '\0';
*rows_out = (unsigned long)atol(digit_buf);
return query;
}
/*
Check if SELECT query requests the row locking
@param[in] cs charset
@param[in] query query
@param[in] query_end query end
@param[in] is_share flag to check the share mode otherwise for update
@return position of "FOR UPDATE" or "LOCK IN SHARE MODE" inside a query.
Otherwise returns NULL.
*/
const char* check_row_locking(myodbc::CHARSET_INFO* cs, const char * query, const char * query_end, BOOL is_share_mode)
{
const char *before_token= query_end;
const char *token= NULL;
int i = 0;
const char *for_update[2] = { "UPDATE", "FOR" };
const char *lock_in_share_mode[4] = { "MODE", "SHARE", "IN", "LOCK" };
const char **check = for_update;
int index_max = 2;
if (is_share_mode)
{
check = lock_in_share_mode;
index_max = 4;
}
for (i = 0; i < index_max; ++i)
{
token = mystr_get_prev_token(cs, &before_token, query);
if (myodbc_casecmp(token, check[i], (uint)strlen(check[i])))
return NULL;
}
return token;
}
MY_LIMIT_CLAUSE find_position4limit(myodbc::CHARSET_INFO* cs, const char *query, const char * query_end)
{
MY_LIMIT_CLAUSE result(0,0,NULL,NULL);
char *limit_pos = NULL;
result.begin= result.end= query_end;
assert(query && query_end && query_end >= query);
if ((limit_pos = (char*)find_token(cs, query, query_end, "LIMIT")))
{
// Found LIMIT in the query
result.end = get_limit_numbers(cs, limit_pos + 5, query_end,
&result.offset, &result.row_count);
// We will start again from the position of LIMIT to simplify the logic
result.begin = limit_pos;
}
else // No LIMIT in SELECT
{
const char *locking_pos = NULL;
if ((locking_pos = check_row_locking(cs, query, query_end, FALSE)) ||
(locking_pos = check_row_locking(cs, query, query_end, TRUE)))
{
// FOR UPDATE or LOCK IN SHARE MODE was detected
result.begin= result.end = (char*)locking_pos - 1; // With a previous space
}
else
{
while(query_end > query && (!*query_end ||
myodbc_isspace(cs, query_end, result.end)))
{
--query_end;
}
if (*query_end==';')
{
result.begin= result.end= query_end;
}
}
}
return result;
}
BOOL myodbc_isspace(myodbc::CHARSET_INFO* cs, const char * begin, const char *end)
{
int ctype;
cs->cset->ctype(cs, &ctype, (const uchar*) begin, (const uchar*) end);
return ctype & _MY_SPC;
}
BOOL myodbc_isnum(myodbc::CHARSET_INFO* cs, const char * begin, const char *end)
{
int ctype;
cs->cset->ctype(cs, &ctype, (const uchar*)begin, (const uchar*)end);
return ctype & _MY_NMR;
}
int got_out_parameters(STMT *stmt)
{
uint i;
DESCREC *iprec;
int result= NO_OUT_PARAMETERS;
for(i= 0; i < stmt->param_count; ++i)
{
iprec= desc_get_rec(stmt->ipd, i, '\0');
if (iprec)
{
if(iprec->parameter_type == SQL_PARAM_INPUT_OUTPUT
|| iprec->parameter_type == SQL_PARAM_OUTPUT)
{
result|= GOT_OUT_PARAMETERS;
}
#ifndef USE_IODBC
else if (iprec->parameter_type == SQL_PARAM_INPUT_OUTPUT_STREAM
|| iprec->parameter_type == SQL_PARAM_OUTPUT_STREAM)
{
result|= GOT_OUT_STREAM_PARAMETERS;
}
#endif
}
}
return result;
}
int get_session_variable(STMT *stmt, const char *var, char *result)
{
char buff[255+4*NAME_CHAR_LEN], *to;
MYSQL_RES *res;
MYSQL_ROW row;
if (var)
{
to= myodbc_stpmov(buff, "SHOW SESSION VARIABLES LIKE '");
to= myodbc_stpmov(to, var);
to= myodbc_stpmov(to, "'");
*to= '\0';
if (!SQL_SUCCEEDED(stmt->dbc->execute_query(buff, SQL_NTS, TRUE)))
{
return 0;
}
res= mysql_store_result(stmt->dbc->mysql);
if (!res)
return 0;
row= mysql_fetch_row(res);
if (row)
{
strcpy(result, row[1]);
mysql_free_result(res);
return (int)strlen(result);
}
mysql_free_result(res);
}
return 0;
}
/**
Sets the value of @@max_execution_time
@param[in] stmt stmt handler
@param[in] new_value Value to set @@max_execution_time.
Returns new_value if operation was successful, -1 otherwise
*/
SQLRETURN set_query_timeout(STMT *stmt, SQLULEN new_value)
{
char query[44];
SQLRETURN rc= SQL_SUCCESS;
if (new_value == stmt->stmt_options.query_timeout ||
!is_minimum_version(stmt->dbc->mysql->server_version, "5.7.8"))
{
/* Do nothing if setting same timeout or MySQL server older than 5.7.8 */
return SQL_SUCCESS;
}
if (new_value > 0)
{
unsigned long long msec_value= (unsigned long long)new_value * 1000;
myodbc_snprintf(query, sizeof(query),
"set @@max_execution_time=%llu", msec_value);
}
else
{
strcpy(query, "set @@max_execution_time=DEFAULT");
new_value= 0;
}
if (SQL_SUCCEEDED(rc = stmt->dbc->execute_query(query, SQL_NTS, true)))
{
stmt->stmt_options.query_timeout= new_value;
}
return rc;
}
SQLULEN get_query_timeout(STMT *stmt)
{
SQLULEN query_timeout= SQL_QUERY_TIMEOUT_DEFAULT; /* 0 */
if (is_minimum_version(stmt->dbc->mysql->server_version, "5.7.8"))
{
/* Be cautious with very long values even if they don't make sense */
char query_timeout_char[32]= {0};
uint length= get_session_variable(stmt, "MAX_EXECUTION_TIME",
(char*)query_timeout_char);
/* Terminate the string just in case */
query_timeout_char[length]= 0;
/* convert */
query_timeout= (SQLULEN)(atol(query_timeout_char) / 1000);
}
return query_timeout;
}
const char get_identifier_quote(STMT *stmt)
{
const char tick= '`', quote= '"', empty= ' ';
if (is_minimum_version(stmt->dbc->mysql->server_version, "3.23.06"))
{
/*
The full list of all SQL modes takes over 512 symbols, so we reserve
some for the future
*/
char sql_mode[2048]= " ";
/*
The token finder skips the leading space and starts
with the first non-space value. Thus (sql_mode+1).
*/
uint length= get_session_variable(stmt, "SQL_MODE", (char*)(sql_mode+1));
const char *end= sql_mode + length;
if (find_first_token(stmt->dbc->cxn_charset_info, sql_mode, end, "ANSI_QUOTES"))
{
return quote;
}
else
{
return tick;
}
}
return empty;
}