/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. The MySQL Connector/ODBC is licensed under the terms of the GPLv2 , like most MySQL Connectors. There are special exceptions to the terms and conditions of the GPLv2 as it is applied to this software, see the FLOSS License Exception . This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. 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 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 "stringutil.h" #include const SQLULEN sql_select_unlimited= (SQLULEN)-1; #if MYSQL_VERSION_ID >= 40100 # undef USE_MB #endif /** Execute a SQL statement. @param[in] dbc The database connection @param[in] query The query to execute */ SQLRETURN odbc_stmt(DBC FAR *dbc, const char *query) { SQLRETURN result= SQL_SUCCESS; pthread_mutex_lock(&dbc->lock); if ( check_if_server_is_alive(dbc) || mysql_real_query(&dbc->mysql,query,strlen(query)) ) { result= set_conn_error(dbc,MYERR_S1000,mysql_error(&dbc->mysql), mysql_errno(&dbc->mysql)); } pthread_mutex_unlock(&dbc->lock); return result; } /** 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 mysql_link_fields(STMT *stmt, MYSQL_FIELD *fields, uint field_count) { MYSQL_RES *result; pthread_mutex_lock(&stmt->dbc->lock); result= stmt->result; result->fields= fields; result->field_count= field_count; result->current_field= 0; fix_result_types(stmt); pthread_mutex_unlock(&stmt->dbc->lock); } /** Fills STMT's lengths array for given row. Makes use of mysql_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 == NULL) return; row_lengths= stmt->lengths + 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; stmt->state= ST_EXECUTED; /* Mark set found */ if ( (stmt->odbc_types= (SQLSMALLINT*) my_malloc(sizeof(SQLSMALLINT)*result->field_count, MYF(0))) ) { for ( i= 0 ; i < result->field_count ; i++ ) { MYSQL_FIELD *field= result->fields+i; stmt->odbc_types[i]= (SQLSMALLINT) unireg_to_c_datatype(field); } } /* Fix default values for bound columns Normally there isn't any bound columns at this stage ! */ if ( stmt->bind ) { if ( stmt->bound_columns < result->field_count ) { if ( !(stmt->bind= (BIND*) my_realloc((char*) stmt->bind, sizeof(BIND) * result->field_count, MYF(MY_FREE_ON_ERROR))) ) { /* We should in principle give an error here */ stmt->bound_columns= 0; return; } bzero((stmt->bind+stmt->bound_columns), (result->field_count -stmt->bound_columns)*sizeof(BIND)); stmt->bound_columns= result->field_count; } /* Fix default types and pointers to fields */ mysql_field_seek(result,0); for ( i= 0; i < result->field_count ; i++ ) { if ( stmt->bind[i].fCType == SQL_C_DEFAULT ) stmt->bind[i].fCType= stmt->odbc_types[i]; stmt->bind[i].field= mysql_fetch_field(result); } } } /** 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; strmake(to,from,length); return to; } /* @type : myodbc internal @purpose : duplicate the string */ char *dupp_str(char *from,int length) { char *to; if ( !from ) return my_strdup("",MYF(MY_WME)); if ( length == SQL_NTS ) length= strlen(from); if ( (to= my_malloc(length+1,MYF(MY_WME))) ) { memcpy(to,from,length); to[length]= 0; } return to; } /* @type : myodbc internal @purpose : copies the string data to rgbValue buffer. If rgbValue is NULL, then returns warning with full length, else copies the cbValueMax length from 'src' and returns it. */ SQLRETURN copy_str_data(SQLSMALLINT HandleType, SQLHANDLE Handle, SQLCHAR FAR *rgbValue, SQLSMALLINT cbValueMax, SQLSMALLINT FAR *pcbValue,char FAR *src) { SQLSMALLINT dummy; if ( !pcbValue ) pcbValue= &dummy; if ( cbValueMax == SQL_NTS ) cbValueMax= *pcbValue= strlen(src); else if ( cbValueMax < 0 ) return set_handle_error(HandleType,Handle,MYERR_S1090,NULL,0); else { cbValueMax= cbValueMax ? cbValueMax - 1 : 0; *pcbValue= strlen(src); } if ( rgbValue ) strmake((char*) rgbValue, src, cbValueMax); if ( myodbc_min(*pcbValue , cbValueMax) != *pcbValue ) return SQL_SUCCESS_WITH_INFO; return SQL_SUCCESS; } /* @type : myodbc internal @purpose : returns (possibly truncated) results if result is truncated the result length contains length of the truncted result */ SQLRETURN copy_lresult(SQLSMALLINT HandleType, SQLHANDLE Handle, SQLCHAR FAR *rgbValue, SQLINTEGER cbValueMax, SQLLEN *pcbValue,char *src,long src_length, long max_length,long fill_length,ulong *offset, my_bool binary_data) { char *dst= (char*) rgbValue; ulong length; SQLINTEGER arg_length; if ( src && src_length == SQL_NTS ) src_length= strlen(src); arg_length= cbValueMax; if ( cbValueMax && !binary_data ) /* If not length check */ cbValueMax--; /* Room for end null */ else if ( !cbValueMax ) dst= 0; /* Don't copy anything! */ if ( max_length ) /* If limit on char lengths */ { set_if_smaller(cbValueMax,(long) max_length); set_if_smaller(src_length,max_length); set_if_smaller(fill_length,max_length); } if ( HandleType == SQL_HANDLE_DBC ) { if ( fill_length < src_length || !Handle || !(((DBC FAR*)Handle)->flag & FLAG_PAD_SPACE) ) fill_length= src_length; } else { if ( fill_length < src_length || !Handle || !(((STMT FAR*)Handle)->dbc->flag & FLAG_PAD_SPACE) ) fill_length= src_length; } if (arg_length && *offset == (ulong) ~0L) { /* This is first call. Even if data has zero size, we don't return SQL_NO_DATA_FOUND. */ *offset= 0; } else if (*offset != (ulong) ~0L && *offset >= (ulong) fill_length) { /* If not the first call, and we have no data left, must be a call after we gave the last data. We always return SQL_NO_DATA_FOUND in this case, even if size is 0. */ return SQL_NO_DATA_FOUND; } if (*offset != (ulong) ~0L) { src+= *offset; src_length-= (long) *offset; fill_length-= *offset; } length= myodbc_min(fill_length, cbValueMax); (*offset)+= length; /* Fix for next call */ if ( pcbValue ) *pcbValue= fill_length; if ( dst ) /* Bind allows null pointers */ { ulong copy_length= ((long) src_length >= (long) length ? length : ((long) src_length >= 0 ? src_length : 0L)); memcpy(dst,src,copy_length); bfill(dst+copy_length,length-copy_length,' '); if ( !binary_data || length != (ulong) cbValueMax ) dst[length]= 0; } if ( arg_length && cbValueMax >= fill_length ) return SQL_SUCCESS; set_handle_error(HandleType,Handle,MYERR_01004,NULL,0); return SQL_SUCCESS_WITH_INFO; } /* @type : myodbc internal @purpose : is used when converting a binary string to a SQL_C_CHAR */ SQLRETURN copy_binary_result( SQLSMALLINT HandleType, SQLHANDLE Handle, SQLCHAR FAR * rgbValue, SQLINTEGER cbValueMax, SQLLEN * pcbValue, char * src, ulong src_length, ulong max_length, ulong * offset ) { char *dst= (char*) rgbValue; ulong length; #if MYSQL_VERSION_ID >= 40100 char NEAR _dig_vec[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; #endif if ( !cbValueMax ) dst= 0; /* Don't copy anything! */ 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 ) *pcbValue= src_length*2; if ( dst ) /* Bind allows null pointers */ { ulong i; for ( i= 0 ; i < length ; i++ ) { *dst++= _dig_vec[(uchar) *src >> 4]; *dst++= _dig_vec[(uchar) *src++ & 15]; } *dst= 0; } if ( (ulong) cbValueMax > length*2 ) return SQL_SUCCESS; set_handle_error(HandleType,Handle,MYERR_01004,NULL,0); return SQL_SUCCESS_WITH_INFO; } /** 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= test(field->charsetnr == 63) && (test(field->org_table_length > 0) || ((stmt->dbc->flag & FLAG_NO_BINARY_RESULT) == 0)); switch (field->type) { case MYSQL_TYPE_BIT: if (buff) (void)strmov(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)strmov(buff, "decimal"); return SQL_DECIMAL; case MYSQL_TYPE_TINY: /* MYSQL_TYPE_TINY could either be a TINYINT or a single CHAR. */ if (buff) { buff= strmov(buff, (field->flags & NUM_FLAG) ? "tinyint" : "char"); if (field->flags & UNSIGNED_FLAG) (void)strmov(buff, " unsigned"); } return (field->flags & (unsigned int)NUM_FLAG) ? SQL_TINYINT : SQL_CHAR; case MYSQL_TYPE_SHORT: if (buff) { buff= strmov(buff, "smallint"); if (field->flags & UNSIGNED_FLAG) (void)strmov(buff, " unsigned"); } return SQL_SMALLINT; case MYSQL_TYPE_INT24: if (buff) { buff= strmov(buff, "mediumint"); if (field->flags & UNSIGNED_FLAG) (void)strmov(buff, " unsigned"); } return SQL_INTEGER; case MYSQL_TYPE_LONG: if (buff) { buff= strmov(buff, "integer"); if (field->flags & UNSIGNED_FLAG) (void)strmov(buff, " unsigned"); } return SQL_INTEGER; case MYSQL_TYPE_LONGLONG: if (buff) { if (stmt->dbc->flag & FLAG_NO_BIGINT) buff= strmov(buff, "int"); else buff= strmov(buff, "bigint"); if (field->flags & UNSIGNED_FLAG) (void)strmov(buff, " unsigned"); } if (stmt->dbc->flag & FLAG_NO_BIGINT) return SQL_INTEGER; return SQL_BIGINT; case MYSQL_TYPE_FLOAT: if (buff) { buff= strmov(buff, "float"); if (field->flags & UNSIGNED_FLAG) (void)strmov(buff, " unsigned"); } return SQL_REAL; case MYSQL_TYPE_DOUBLE: if (buff) { buff= strmov(buff, "double"); if (field->flags & UNSIGNED_FLAG) (void)strmov(buff, " unsigned"); } return SQL_DOUBLE; case MYSQL_TYPE_NULL: if (buff) (void)strmov(buff, "null"); return SQL_VARCHAR; case MYSQL_TYPE_YEAR: if (buff) (void)strmov(buff, "year"); return SQL_SMALLINT; case MYSQL_TYPE_TIMESTAMP: if (buff) (void)strmov(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)strmov(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)strmov(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)strmov(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)strmov(buff, field_is_binary ? "binary" : "char"); return field_is_binary ? SQL_BINARY : 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)strmov(buff, field_is_binary ? "varbinary" : "varchar"); return field_is_binary ? SQL_VARBINARY : SQL_VARCHAR; case MYSQL_TYPE_TINY_BLOB: if (buff) (void)strmov(buff, field_is_binary ? "tinyblob" : "tinytext"); return field_is_binary ? SQL_LONGVARBINARY : SQL_LONGVARCHAR; case MYSQL_TYPE_BLOB: if (buff) (void)strmov(buff, field_is_binary ? "blob" : "text"); return field_is_binary ? SQL_LONGVARBINARY : SQL_LONGVARCHAR; case MYSQL_TYPE_MEDIUM_BLOB: if (buff) (void)strmov(buff, field_is_binary ? "mediumblob" : "mediumtext"); return field_is_binary ? SQL_LONGVARBINARY : SQL_LONGVARCHAR; case MYSQL_TYPE_LONG_BLOB: if (buff) (void)strmov(buff, field_is_binary ? "longblob" : "longtext"); return field_is_binary ? SQL_LONGVARBINARY : SQL_LONGVARCHAR; case MYSQL_TYPE_ENUM: if (buff) (void)strmov(buff, "enum"); return SQL_CHAR; case MYSQL_TYPE_SET: if (buff) (void)strmov(buff, "set"); return SQL_CHAR; case MYSQL_TYPE_GEOMETRY: if (buff) (void)strmov(buff, "geometry"); return SQL_LONGVARBINARY; } 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 @param[in] actual If true, field->max_length is used instead of field->length, to retrieve the actual length of data in the field @return void */ SQLULEN fill_column_size_buff(char *buff, STMT *stmt, MYSQL_FIELD *field, my_bool actual) { SQLULEN size= get_column_size(stmt, field, actual); sprintf(buff, (sizeof(SQLULEN) == 4 ? "%ld" : "%llu"), size); return size; } /** 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 @param[in] actual If true, field->max_length is used instead of field->length, to retrieve the actual length of data in the field @return The column size of the field */ SQLULEN get_column_size(STMT *stmt, MYSQL_FIELD *field, my_bool actual) { CHARSET_INFO *charset= get_charset(field->charsetnr, MYF(0)); unsigned int mbmaxlen= charset ? charset->mbmaxlen : 1; SQLULEN length= (actual || field->max_length > field->length ) ? field->max_length : field->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->flag & FLAG_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 - test(!(field->flags & UNSIGNED_FLAG)) - /* sign? */ test(field->decimals)); /* 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: if (field->charsetnr != 63) length= length / mbmaxlen; return (stmt->dbc->flag & FLAG_COLUMN_SIZE_S32) ? (length > INT_MAX32 ? INT_MAX32 : length) : length; } 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 */ 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: 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 __attribute__((unused)), MYSQL_FIELD *field) { 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_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: return field->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) { CHARSET_INFO *charset= get_charset(field->charsetnr, MYF(0)); unsigned int mbmaxlen= charset ? charset->mbmaxlen : 1; switch (field->type) { case MYSQL_TYPE_TINY: return 3 + test(field->flags & UNSIGNED_FLAG); case MYSQL_TYPE_SHORT: return 5 + test(field->flags & UNSIGNED_FLAG); case MYSQL_TYPE_INT24: return 8 + test(field->flags & UNSIGNED_FLAG); case MYSQL_TYPE_LONG: return 10 + test(field->flags & UNSIGNED_FLAG); 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_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: if (field->charsetnr == 63) return field->length * 2; else return field->length / mbmaxlen; } return SQL_NO_TOTAL; } /* @type : myodbc internal @purpose : returns internal type to C type */ int unireg_to_c_datatype(MYSQL_FIELD *field) { switch ( field->type ) { case MYSQL_TYPE_LONGLONG: /* Must be returned as char */ default: return SQL_C_CHAR; 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: return SQL_C_BINARY; } } /* @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 ) { default: /* For CHAR, VARCHAR, BLOB...*/ return length; 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); } } /* @type : myodbc internal @purpose : convert a possible string to a timestamp value */ my_bool str_to_ts(SQL_TIMESTAMP_STRUCT *ts, const char *str, int zeroToMin) { uint year, length; char buff[15],*to; SQL_TIMESTAMP_STRUCT tmp_timestamp; if ( !ts ) ts= (SQL_TIMESTAMP_STRUCT *) &tmp_timestamp; for ( to= buff ; *str && to < buff+sizeof(buff)-1 ; ++str ) { if ( isdigit(*str) ) *to++= *str; } length= (uint) (to-buff); if ( length == 6 || length == 12 ) /* YYMMDD or YYMMDDHHMMSS */ { memmove(to+2, to, length); if ( buff[0] <= '6' ) { buff[0]='2'; buff[1]='0'; } else { buff[0]='1'; buff[1]='9'; } length+= 2; to+= 2; } if ( length < 14 ) strfill(to,14 - 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 1; /* 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= 0; 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[12],*to; SQL_TIME_STRUCT tmp_time; if ( !ts ) ts= (SQL_TIME_STRUCT *) &tmp_time; for ( to= buff ; *str && to < buff+sizeof(buff)-1 ; ++str ) { if ( isdigit(*str) ) *to++= *str; } ts->hour= digit(buff[0])*10+digit(buff[1]); ts->minute= digit(buff[2])*10+digit(buff[3]); ts->second= digit(buff[4])*10+digit(buff[5]); 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 FAR *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 ( mysql_errno( &dbc->mysql ) == CR_SERVER_LOST ) result = 1; } } dbc->last_query_time = seconds; return result; } /* @type : myodbc3 internal @purpose : appends quoted string to dynamic string */ my_bool dynstr_append_quoted_name(DYNAMIC_STRING *str, const char *name) { uint tmp= strlen(name); char *pos; if ( dynstr_realloc(str,tmp+3) ) return 1; pos= str->str+str->length; *pos='`'; memcpy(pos+1,name,tmp); pos[tmp+1]='`'; pos[tmp+2]= 0; /* Safety */ str->length+= tmp+2; return 0; } /* @type : myodbc3 internal @purpose : reset the db name to current_database() */ my_bool reget_current_catalog(DBC FAR *dbc) { x_free(dbc->database); dbc->database= NULL; if ( odbc_stmt(dbc, "select database()") ) { 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 = my_strdup(row[0], MYF(MY_WME)); else dbc->database = NULL; } } 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) { #ifdef USE_MB if ( use_mb(default_charset_info) ) { register uint32 l; register const char *end= s+strlen(s); while ( s= 0 ) return TRUE; return FALSE; } /** Escapes a string that may contain wildcard characters (%, _) and other problematic characters (", ', \n, etc). Like mysql_real_escape_string() but also including % and _. Can be used with an identified by passing escape_id. @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 Escaping an identified that will be quoted */ ulong myodbc_escape_string(MYSQL *mysql __attribute__((unused)), char *to, ulong to_length, const char *from, ulong length, int escape_id) { const char *to_start= to; const char *end, *to_end=to_start + (to_length ? to_length-1 : 2*length); my_bool overflow= FALSE; #ifdef USE_MB CHARSET_INFO *charset_info= mysql->charset; my_bool use_mb_flag= use_mb(charset_info); #endif for (end= from + length; from < end; ++from) { char escape= 0; #ifdef USE_MB int tmp_length; 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 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 = \) */ if (use_mb_flag && (tmp_length= my_mbcharlen(charset_info, *from)) > 1) escape= *from; else #endif 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 */ case '_': case '%': escape= *from; break; case '\032': /* This gives problems on Win32 */ escape= 'Z'; break; } /* if escaping an id, only handle back-tick */ if (escape_id) { if (*from == '`') escape= *from; else escape= 0; } if (escape) { if (to + 2 > to_end) { overflow= TRUE; break; } *to++= '\\'; *to++= escape; } else { if (to + 1 > to_end) { overflow= TRUE; break; } *to++= *from; } } *to= 0; return overflow ? (ulong)~0 : (ulong) (to - to_start); } /** Detect if a statement is a SELECT statement. */ int is_select_statement(SQLCHAR *query) { /* Skip leading spaces */ while (query && isspace(*query)) ++query; return myodbc_casecmp((char *)query, "SELECT", 6) == 0; } /** Sets the value of @@sql_select_limit @param[in] dbc dbc handler @param[in] new_value Value to set @@sql_select_limit. Returns new_value if operation was successful, -1 otherwise */ SQLRETURN set_sql_select_limit(DBC FAR *dbc, SQLULEN new_value) { char query[44]; SQLRETURN rc; /* Both 0 and max(SQLULEN) value mean no limit and sql_select_limit to DEFAULT */ if (new_value == dbc->sql_select_limit || new_value == sql_select_unlimited && dbc->sql_select_limit == 0) return SQL_SUCCESS; if (new_value > 0 && new_value < sql_select_unlimited) sprintf(query, "set @@sql_select_limit=%lu", (unsigned long)new_value); else { strcpy(query, "set @@sql_select_limit=DEFAULT"); new_value= 0; } if (SQL_SUCCEEDED(rc= odbc_stmt(dbc, query))) { dbc->sql_select_limit= new_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 */ SQLCHAR *proc_get_param_type(SQLCHAR *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 */ SQLCHAR* proc_get_param_name(SQLCHAR *proc, int len, SQLCHAR *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 */ SQLCHAR* proc_get_param_dbtype(SQLCHAR *proc, int len, SQLCHAR *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, 0), " charset ")) { ptype= trim_str; (*ptype)= 0; } /* trim spaces from the end */ ptype-=1; while (isspace(*(ptype))) { *ptype= 0; --ptype; } return proc; } #define TYPE_MAP_SIZE 32 SQLTypeMap SQL_TYPE_MAP_values[TYPE_MAP_SIZE]= { /* SQL_BIT= -7 */ {"bit", 3, SQL_BIT, MYSQL_TYPE_BIT, 1, 1}, {"bool", 4, SQL_BIT, MYSQL_TYPE_BIT, 1, 1}, /* SQL_TINY= -6 */ {"tinyint", 7, SQL_TINYINT, MYSQL_TYPE_TINY, 1, 1}, /* SQL_BIGINT= -5 */ {"bigint", 6, SQL_BIGINT, MYSQL_TYPE_LONGLONG, 20, 1}, /* SQL_LONGVARBINARY= -4 */ {"long varbinary", 14, SQL_LONGVARBINARY, MYSQL_TYPE_MEDIUM_BLOB, 16777215, 1}, {"blob", 4, SQL_LONGVARBINARY, MYSQL_TYPE_BLOB, 65535, 1}, {"longblob", 8, SQL_LONGVARBINARY, MYSQL_TYPE_LONG_BLOB, 4294967295UL, 1}, {"tinyblob", 8, SQL_LONGVARBINARY, MYSQL_TYPE_TINY_BLOB, 255, 1}, {"mediumblob", 10, SQL_LONGVARBINARY, MYSQL_TYPE_MEDIUM_BLOB, 16777215,1 }, /* SQL_VARBINARY= -3 */ {"varbinary", 9, SQL_VARBINARY, MYSQL_TYPE_VAR_STRING, 0, 1}, /* SQL_BINARY= -2 */ {"binary", 6, SQL_BINARY, MYSQL_TYPE_STRING, 0, 1}, /* SQL_LONGVARCHAR= -1 */ {"long varchar", 12, SQL_LONGVARCHAR, MYSQL_TYPE_MEDIUM_BLOB, 16777215, 0}, {"text", 4, SQL_LONGVARCHAR, MYSQL_TYPE_BLOB, 65535, 0}, {"mediumtext", 10, SQL_LONGVARCHAR, MYSQL_TYPE_MEDIUM_BLOB, 16777215, 0}, {"longtext", 8, SQL_LONGVARCHAR, MYSQL_TYPE_LONG_BLOB, 4294967295UL, 0}, {"tinytext", 8, SQL_LONGVARCHAR, MYSQL_TYPE_TINY_BLOB, 255, 0}, /* SQL_CHAR= 1 */ {"char", 4, SQL_CHAR, MYSQL_TYPE_STRING, 0, 0}, {"enum", 4, SQL_CHAR, MYSQL_TYPE_STRING, 0, 0}, {"set", 3, SQL_CHAR, MYSQL_TYPE_STRING, 0, 0}, /* SQL_NUMERIC= 2 */ {"numeric", 7, SQL_NUMERIC, MYSQL_TYPE_DECIMAL, 0, 1}, /* SQL_DECIMAL= 3 */ {"decimal", 7, SQL_DECIMAL, MYSQL_TYPE_DECIMAL, 0, 1}, /* SQL_INTEGER= 4 */ {"int", 3, SQL_INTEGER, MYSQL_TYPE_LONG, 10, 1}, {"mediumint", 9, SQL_INTEGER, MYSQL_TYPE_INT24, 8, 1}, /* SQL_SMALLINT= 5 */ {"smallint", 8, SQL_SMALLINT, MYSQL_TYPE_SHORT, 5, 1}, /* SQL_REAL= 7 */ {"float", 5, SQL_REAL, MYSQL_TYPE_FLOAT, 7, 1}, /* SQL_DOUBLE= 8 */ {"double", 6, SQL_DOUBLE, MYSQL_TYPE_DOUBLE, 15, 1}, /* SQL_DATETIME= 9 */ {"datetime", 8, SQL_TYPE_TIMESTAMP, MYSQL_TYPE_DATETIME, 19, 1}, /* SQL_VARCHAR= 12 */ {"varchar", 7, SQL_VARCHAR, MYSQL_TYPE_VARCHAR, 0, 0}, /* SQL_TYPE_DATE= 91 */ {"date", 4, SQL_TYPE_DATE, MYSQL_TYPE_DATE, 10, 1}, /* YEAR - SQL_SMALLINT */ {"year", 4, SQL_SMALLINT, MYSQL_TYPE_YEAR, 2, 1}, /* SQL_TYPE_TIMESTAMP= 93 */ {"timestamp", 9, SQL_TYPE_TIMESTAMP, MYSQL_TYPE_TIMESTAMP, 19, 1}, /* SQL_TYPE_TIME= 92 */ {"time", 4, SQL_TYPE_TIME, MYSQL_TYPE_TIME, 8, 1} }; /** 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(SQLCHAR *ptype, int len) { int i; for (i= 0; i < TYPE_MAP_SIZE; ++i) { if (len >= SQL_TYPE_MAP_values[i].name_length && (!myodbc_casecmp(ptype, SQL_TYPE_MAP_values[i].type_name, SQL_TYPE_MAP_values[i].name_length))) return i; } return 16; /* "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; SQLCHAR 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= 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= strchr(ptype, '('); SQLCHAR *end_pos= strrchr(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, 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, 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(SQL_TYPE_MAP_values[sql_type_index].type_name, "set")) { param_size= proc_parse_enum_set(start_pos, end_pos - start_pos, FALSE); } else if (!myodbc_strcasecmp(SQL_TYPE_MAP_values[sql_type_index].type_name, "enum")) { param_size= proc_parse_enum_set(start_pos, end_pos - start_pos, TRUE); } else /* just normal character type */ { param_size= proc_parse_sizes(start_pos, 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_DATETIME: case MYSQL_TYPE_BIT: 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= col_size; temp_fld.decimals= decimal_digits; temp_fld.flags= flags; //temp_fld.charsetnr= stmt->dbc->ansi_charset_info->number; temp_fld.type= SQL_TYPE_MAP_values[sql_type_index].mysql_type; if (str_buff != NULL) { return fill_column_size_buff(str_buff, stmt, &temp_fld, FALSE); } else { return get_column_size( stmt, &temp_fld, FALSE); } } /** 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= col_size; temp_fld.decimals= decimal_digits; temp_fld.flags= flags; //temp_fld.charsetnr= stmt->dbc->ansi_charset_info->number; temp_fld.type= 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= 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) { int 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; } /** deletes the list element and moves the pointer forward @param[in] elem item to delete Returns pointer to the next list element */ LIST *list_delete_forward(LIST *elem) { if(elem->prev) elem->prev->next= elem->next; if(elem->next) { elem->next->prev= elem->prev; elem= elem->next; } return elem; } /** 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; } }