/*
  Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.

  The MySQL Connector/ODBC is licensed under the terms of the GPLv2
  <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, 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
  <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
  
  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 <ctype.h>

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(MYODBC_FIELD_CHARSETNR(field) == 63) &&
                           (test(MYODBC_FIELD_ORG_TABLE_LENGTH(field) > 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= MYODBC_FIELD_CHARSET(field);
  /* 3.23 single-byte charsets (latin1) have mbmaxlen 0 */
  unsigned int mbmaxlen= charset && charset->mbmaxlen ? 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 (MYODBC_FIELD_CHARSETNR(field) != 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= MYODBC_FIELD_CHARSET(field);
  /* 3.23 single-byte charsets (latin1) have mbmaxlen 0 */
  unsigned int mbmaxlen= charset && charset->mbmaxlen ? 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 (MYODBC_FIELD_CHARSETNR(field) == 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<end )
        {
            if ( (l= my_ismbchar(default_charset_info, s,end)) )
            {
                while ( l-- )
                    if ( *s++ != *t++ ) return 1;
            }
            else if ( my_ismbhead(default_charset_info, *t) ) return 1;
            else if ( toupper((uchar) *s++) != toupper((uchar) *t++) ) return 1;
        }
        return *t;
    }
    else
#endif
    {
        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)
{
#ifdef USE_MB
    if ( use_mb(default_charset_info) )
    {
        register uint32 l;
        register const char *end= s+len;
        while ( s<end )
        {
            if ( (l= my_ismbchar(default_charset_info, s,end)) )
            {
                while ( l-- )
                    if ( *s++ != *t++ ) return 1;
            }
            else if ( my_ismbhead(default_charset_info, *t) ) return 1;
            else if ( toupper((uchar) *s++) != toupper((uchar) *t++) ) return 1;
        }
        return 0;
    }
    else
#endif /* USE_MB */
    {
        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 )
        fprintf(log_file, "%s;\n",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,
                           uint length)
{
    if ( strncmp(server_version,version,length) >= 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;
  }
}
