List:Commits« Previous MessageNext Message »
From:jwinstead Date:August 2 2007 7:04pm
Subject:Connector/ODBC 3.51 commit: r627 - in trunk: . driver test
View as plain text  
Modified:
   trunk/ChangeLog
   trunk/driver/catalog.c
   trunk/driver/cursor.c
   trunk/driver/myutil.h
   trunk/driver/results.c
   trunk/driver/utility.c
   trunk/test/my_basics.c
   trunk/test/my_catalog.c
   trunk/test/my_datetime.c
   trunk/test/my_result.c
   trunk/test/my_types.c
Log:
Big cleanup of how column lengths are reported. Fixes a number of known
bugs, and probably some unknown ones.

* Lengths returned by SQLColumns(), SQLDescribeCol(), and SQLColAttribute()
  were often incorrect. These lengths should now conform to the ODBC
  specification. FLAG_FIELD_LENGTH no longer has any effect. The default
  behavior was incorrect. (Bug #27862)
* The SQL_DATA_TYPE column in SQLColumns() results did not report the
  correct value for date and time types.
* The SQL_DATETIME_SUB column in SQLColumns() was not correctly set for
  date and time types.
* The value for SQL_DESC_FIXED_PREC_SCALE was not returned correctly
  for decimal values in MySQL 5.0 and later.
* The wrong value from SQL_DESC_LITERAL_SUFFIX was returned for binary
  fields.
* The wrong value for SQL_DESC_TYPE was returned for date and time types.
* The wrong value for DECIMAL_DIGITS in SQLColumns() was reported for
  FLOAT and DOUBLE fields, as well as the wrong value for the scale
  parameter to SQLDescribeCol(), and the SQL_DESC_SCALE attribute
  from SQLColAttribute().
* MySQL BIT(n) fields were always treated as SQL_BIT data. When n > 1,
  they are now treated as binary data.
* If the connection character set was set to a multibyte character set,
  such as UTF-8, the wrong column size was reported. (Bug #19345)


Modified: trunk/ChangeLog
===================================================================
--- trunk/ChangeLog	2007-08-02 00:48:34 UTC (rev 626)
+++ trunk/ChangeLog	2007-08-02 17:04:03 UTC (rev 627)
@@ -7,6 +7,27 @@
     ODBC logging should be used.
 
   Bugs fixed:
+  * Lengths returned by SQLColumns(), SQLDescribeCol(), and SQLColAttribute()
+    were often incorrect. These lengths should now conform to the ODBC
+    specification. FLAG_FIELD_LENGTH no longer has any effect. The default
+    behavior was incorrect. (Bug #27862)
+  * The SQL_DATA_TYPE column in SQLColumns() results did not report the
+    correct value for date and time types.
+  * The SQL_DATETIME_SUB column in SQLColumns() was not correctly set for
+    date and time types.
+  * The value for SQL_DESC_FIXED_PREC_SCALE was not returned correctly
+    for decimal values in MySQL 5.0 and later.
+  * The wrong value from SQL_DESC_LITERAL_SUFFIX was returned for binary
+    fields.
+  * The wrong value for SQL_DESC_TYPE was returned for date and time types.
+  * The wrong value for DECIMAL_DIGITS in SQLColumns() was reported for
+    FLOAT and DOUBLE fields, as well as the wrong value for the scale
+    parameter to SQLDescribeCol(), and the SQL_DESC_SCALE attribute
+    from SQLColAttribute().
+  * MySQL BIT(n) fields were always treated as SQL_BIT data. When n > 1,
+    they are now treated as binary data.
+  * If the connection character set was set to a multibyte character set,
+    such as UTF-8, the wrong column size was reported. (Bug #19345)
   * SQLSpecialColumns() returned all TIMESTAMP fields when queried for
     SQL_ROWVER, not just an auto-updating TIMESTAMP field. (Bug #9927, still
     limited by Bug #30081 in the server.)

Modified: trunk/driver/catalog.c
===================================================================
--- trunk/driver/catalog.c	2007-08-02 00:48:34 UTC (rev 626)
+++ trunk/driver/catalog.c	2007-08-02 17:04:03 UTC (rev 627)
@@ -822,9 +822,8 @@
 
     while ((field= mysql_fetch_field(table_res)))
     {
-      ulong transfer_length, precision, display_size;
+      SQLSMALLINT type;
       char buff[255]; /* @todo justify the size of this buffer */
-      int type;
       MYSQL_ROW row= stmt->result_array + (SQLCOLUMNS_FIELDS * next_row++);
 
       row[0]= db;                     /* TABLE_CAT */
@@ -832,38 +831,55 @@
       row[2]= strdup_root(alloc, field->table); /* TABLE_NAME */
       row[3]= strdup_root(alloc, field->name);  /* COLUMN_NAME */
 
-      field->max_length= field->length;
-      type= unireg_to_sql_datatype(stmt, field, buff, &transfer_length,
-                                   &precision, &display_size);
+      type= get_sql_data_type(stmt, field, buff);
 
-      /* DATA_TYPE and SQL_DATA_TYPE */
+      row[5]= strdup_root(alloc, buff); /* TYPE_NAME */
+
       sprintf(buff, "%d", type);
-      row[4]= row[13]= strdup_root(alloc, buff);
+      row[4]= strdup_root(alloc, buff); /* DATA_TYPE */
 
-      row[5]= strdup_root(alloc, buff); /* TYPE_NAME */
+      if (type == SQL_TYPE_DATE || type == SQL_TYPE_TIME ||
+          type == SQL_TYPE_TIMESTAMP)
+      {
+        row[14]= row[4];    /* SQL_DATETIME_SUB */
+        sprintf(buff, "%d", SQL_DATETIME);
+        row[13]= strdup_root(alloc, buff); /* SQL_DATA_TYPE */
+      }
+      else
+      {
+        row[13]= row[4];    /* SQL_DATA_TYPE */
+        row[14]= NULL;      /* SQL_DATETIME_SUB */
+      }
 
       /* COLUMN_SIZE */
-      sprintf(buff, "%ld", precision);
+      sprintf(buff, "%ld", get_column_size(stmt, field, FALSE));
       row[6]= strdup_root(alloc, buff);
 
       /* BUFFER_LENGTH */
-      sprintf(buff, "%ld", transfer_length);
+      sprintf(buff, "%ld", get_transfer_octet_length(stmt, field));
       row[7]= strdup_root(alloc, buff);
 
-      if (IS_NUM(field->type))
-      {
-        sprintf(buff, "%d", field->decimals);
-        row[8]= strdup_root(alloc,buff);  /* DECIMAL_DIGITS */
-        row[9]= "10";                     /* NUM_PREC_RADIX */
-        row[15]= NULL;                    /* CHAR_OCTET_LENGTH */
-      }
+      if (is_char_sql_type(type) || is_wchar_sql_type(type) ||
+          is_binary_sql_type(type))
+        row[15]= strdup_root(alloc, buff); /* CHAR_OCTET_LENGTH */
       else
+        row[15]= NULL;                     /* CHAR_OCTET_LENGTH */
+
       {
-        row[8]= row[9]= NullS;             /* DECIMAL_DIGITS, NUM_PREC_RADIX */
-        row[15]= strdup_root(alloc, buff); /* CHAR_OCTET_LENGTH */
+        SQLLEN digits= get_decimal_digits(stmt, field);
+        if (digits != SQL_NO_TOTAL)
+        {
+          sprintf(buff, "%ld", digits);
+          row[8]= strdup_root(alloc, buff);  /* DECIMAL_DIGITS */
+          row[9]= "10";                      /* NUM_PREC_RADIX */
+        }
+        else
+        {
+          row[8]= row[9]= NullS;             /* DECIMAL_DIGITS, NUM_PREC_RADIX */
+        }
       }
 
-      if ((field->flags & NOT_NULL_FLAG) == NOT_NULL_FLAG)
+      if (field->flags & NOT_NULL_FLAG)
       {
         sprintf(buff, "%d", SQL_NO_NULLS);
         row[10]= strdup_root(alloc, buff); /* NULLABLE */
@@ -901,7 +917,7 @@
         else
         {
           char *def= alloc_root(alloc, strlen(field->def) + 3);
-          if (IS_NUM(field->type))
+          if (is_numeric_mysql_type(field->type))
             sprintf(def, "%s", field->def);
           else
             sprintf(def, "'%s'", field->def);
@@ -909,9 +925,6 @@
         }
       }
 
-      /** @todo this is not correct. */
-      row[14]= NULL;    /* SQL_DATETIME_SUB */
-
       sprintf(buff, "%d", ++count);
       row[16]= strdup_root(alloc, buff); /* ORDINAL_POSITION */
     }
@@ -1506,7 +1519,6 @@
     MYSQL_RES   *result;
     MYSQL_FIELD *field;
     MEM_ROOT    *alloc;
-    ulong       transfer_length,precision,display_size;
     uint        field_count;
     my_bool     primary_key;
 
@@ -1544,7 +1556,7 @@
         for ( row= stmt->result_array;
             (field = mysql_fetch_field(result)); )
         {
-            int type;
+            SQLSMALLINT type;
             if ((field->type != MYSQL_TYPE_TIMESTAMP))
               continue;
             /*
@@ -1555,20 +1567,26 @@
             if (!(field->flags & TIMESTAMP_FLAG))
               continue;
             field_count++;
-            sprintf(buff,"%d",SQL_SCOPE_SESSION);
-            row[0]= strdup_root(alloc,buff);
+            row[0]= NULL;
             row[1]= field->name;
-            type= unireg_to_sql_datatype(stmt,field,buff,&transfer_length,
-                                         &precision,&display_size);
+            type= get_sql_data_type(stmt, field, buff);
             row[3]= strdup_root(alloc,buff);
             sprintf(buff,"%d",type);
             row[2]= strdup_root(alloc,buff);
-            sprintf(buff,"%ld",precision);
+            sprintf(buff, "%ld", get_column_size(stmt, field, FALSE));
             row[4]= strdup_root(alloc,buff);
-            sprintf(buff,"%ld",transfer_length);
+            sprintf(buff, "%ld", get_transfer_octet_length(stmt, field));
             row[5]= strdup_root(alloc,buff);
-            sprintf(buff,"%d",field->decimals);
-            row[6]= strdup_root(alloc,buff);
+            {
+              SQLLEN digits= get_decimal_digits(stmt, field);
+              if (digits != SQL_NO_TOTAL)
+              {
+                sprintf(buff,"%ld", digits);
+                row[6]= strdup_root(alloc,buff);
+              }
+              else
+                row[6]= NULL;
+            }
             sprintf(buff,"%d",SQL_PC_NOT_PSEUDO);
             row[7]= strdup_root(alloc,buff);
             row+= SQLSPECIALCOLUMNS_FIELDS;
@@ -1614,7 +1632,7 @@
     for ( row= stmt->result_array ;
         (field= mysql_fetch_field(result)); )
     {
-        int type;
+        SQLSMALLINT type;
         if ( primary_key && !(field->flags & PRI_KEY_FLAG) )
             continue;
 #ifndef SQLSPECIALCOLUMNS_RETURN_ALL_COLUMNS
@@ -1627,17 +1645,24 @@
         sprintf(buff,"%d",SQL_SCOPE_SESSION);
         row[0]= strdup_root(alloc,buff);
         row[1]= field->name;
-        type= unireg_to_sql_datatype(stmt,field,buff,&transfer_length,
-                                     &precision,&display_size);
+        type= get_sql_data_type(stmt, field, buff);
         row[3]= strdup_root(alloc,buff);
         sprintf(buff,"%d",type);
         row[2]= strdup_root(alloc,buff);
-        sprintf(buff,"%ld",precision);
+        sprintf(buff,"%ld", get_column_size(stmt, field, FALSE));
         row[4]= strdup_root(alloc,buff);
-        sprintf(buff,"%ld",transfer_length);
+        sprintf(buff,"%ld", get_transfer_octet_length(stmt, field));
         row[5]= strdup_root(alloc,buff);
-        sprintf(buff,"%d",field->decimals);
-        row[6]= strdup_root(alloc,buff);
+        {
+          SQLLEN digits= get_decimal_digits(stmt, field);
+          if (digits != SQL_NO_TOTAL)
+          {
+            sprintf(buff,"%ld", digits);
+            row[6]= strdup_root(alloc, buff);
+          }
+          else
+            row[6]= NULL;
+        }
         sprintf(buff,"%d",SQL_PC_NOT_PSEUDO);
         row[7]= strdup_root(alloc,buff);
         row+= SQLSPECIALCOLUMNS_FIELDS;

Modified: trunk/driver/cursor.c
===================================================================
--- trunk/driver/cursor.c	2007-08-02 00:48:34 UTC (rev 626)
+++ trunk/driver/cursor.c	2007-08-02 17:04:03 UTC (rev 627)
@@ -502,7 +502,6 @@
                             SQLUSMALLINT nSrcCol)
 {
     PARAM_BIND  param;
-    ulong       transfer_length,precision,display_size;
     MYSQL_FIELD *field= mysql_fetch_field_direct(result,nSrcCol);
     MYSQL_ROW   row_data= result->data_cursor->data + nSrcCol;
     NET         *net=&stmt->dbc->mysql.net;
@@ -511,12 +510,7 @@
 
     /* Copy row buffer data to statement */
     param.used= 1;
-    param.SqlType= unireg_to_sql_datatype( stmt,
-                                           field,
-                                           0,
-                                           &transfer_length,
-                                           &precision,
-                                           &display_size );
+    param.SqlType= get_sql_data_type(stmt, field, 0);
     param.CType= SQL_C_CHAR;
 
     if ( row_data && *row_data )
@@ -787,7 +781,6 @@
                                   DYNAMIC_STRING *dynQuery)
 {
     PARAM_BIND    param;
-    ulong         transfer_length,precision,display_size;
     SQLLEN        length;
     uint          ncol, ignore_count= 0;
     MYSQL_FIELD *field;
@@ -843,11 +836,7 @@
         dynstr_append_mem(dynQuery,"=",1);
 
         param.used= 1;
-        param.SqlType= unireg_to_sql_datatype( stmt,field,
-                                               0,
-                                               &transfer_length,
-                                               &precision,
-                                               &display_size );
+        param.SqlType= get_sql_data_type(stmt, field, NULL);
         param.CType= bind->fCType;
         param.buffer= (gptr) bind->rgbValue+irow*bind->cbValueMax;
         param.ValueMax= bind->cbValueMax;
@@ -1151,7 +1140,6 @@
             dynstr_append_mem(ext_query,"(", 1);
             for ( ncol= 0; ncol < result->field_count; ncol++ )
             {
-                ulong        transfer_length,precision,display_size;
                 MYSQL_FIELD *field= mysql_fetch_field_direct(result,ncol);
                 BIND        *bind= stmt->bind+ncol;
                 SQLINTEGER   binding_offset= 0;
@@ -1174,12 +1162,7 @@
                 else
                   ind_or_len= bind->cbValueMax;
 
-                param.SqlType= unireg_to_sql_datatype(stmt,
-                                                      field,
-                                                      0,
-                                                      &transfer_length,
-                                                      &precision,
-                                                      &display_size);
+                param.SqlType= get_sql_data_type(stmt, field, NULL);
                 param.CType = bind->fCType;
                 param.buffer= ((gptr)bind->rgbValue +
                                binding_offset +

Modified: trunk/driver/myutil.h
===================================================================
--- trunk/driver/myutil.h	2007-08-02 00:48:34 UTC (rev 626)
+++ trunk/driver/myutil.h	2007-08-02 17:04:03 UTC (rev 627)
@@ -47,8 +47,6 @@
 #define reset_ptr(x) if (x) x= 0
 #define digit(A) ((int) (A - '0'))
 #define option_flag(A,B) ((A)->dbc->flag & B)
-#define binary_field(fld) ((fld)->flags & BINARY_FLAG)
-#define num_field(fld) ((fld)->flags & NUM_FLAG)
 
 #ifdef MYODBC_DBG
 
@@ -112,9 +110,26 @@
 SQLRETURN handle_connection_error(STMT *stmt);
 void set_mem_error(MYSQL *mysql);
 void translate_error(char *save_state,myodbc_errid errid,uint mysql_err);
-int unireg_to_sql_datatype(STMT FAR *stmt, MYSQL_FIELD *field, char *buff,
-			   ulong *transfer_length,ulong *precision,
-			   ulong *display_size);
+
+SQLSMALLINT get_sql_data_type(STMT *stmt, MYSQL_FIELD *field, char *buff);
+SQLLEN get_column_size(STMT *stmt, MYSQL_FIELD *field, my_bool actual);
+SQLLEN get_decimal_digits(STMT *stmt, MYSQL_FIELD *field);
+SQLLEN get_transfer_octet_length(STMT *stmt, MYSQL_FIELD *field);
+SQLLEN get_display_size(STMT *stmt, MYSQL_FIELD *field);
+
+#define is_char_sql_type(type) \
+  ((type) == SQL_CHAR || (type) == SQL_VARCHAR || (type) == SQL_LONGVARCHAR)
+#define is_wchar_sql_type(type) \
+  ((type) == SQL_WCHAR || (type) == SQL_WVARCHAR || (type) == SQL_WLONGVARCHAR)
+#define is_binary_sql_type(type) \
+  ((type) == SQL_BINARY || (type) == SQL_VARBINARY || \
+   (type) == SQL_LONGVARBINARY)
+
+#define is_numeric_mysql_type(type) \
+  ((type) <= MYSQL_TYPE_NULL || (type) == MYSQL_TYPE_LONGLONG || \
+   (type) == MYSQL_TYPE_INT24 || (type) == MYSQL_TYPE_BIT || \
+   (type) == MYSQL_TYPE_NEWDECIMAL)
+
 SQLRETURN SQL_API my_SQLBindParameter(SQLHSTMT hstmt,SQLUSMALLINT ipar,
 				      SQLSMALLINT fParamType,
 				      SQLSMALLINT fCType, SQLSMALLINT fSqlType,

Modified: trunk/driver/results.c
===================================================================
--- trunk/driver/results.c	2007-08-02 00:48:34 UTC (rev 626)
+++ trunk/driver/results.c	2007-08-02 17:04:03 UTC (rev 627)
@@ -515,8 +515,6 @@
     SQLRETURN error;
     MYSQL_FIELD *field;
     STMT FAR *stmt= (STMT FAR*) hstmt;
-    ulong transfer_length,precision,display_size;
-    int type;
 
     if ( (error= check_result(stmt)) != SQL_SUCCESS )
         return error;
@@ -527,16 +525,12 @@
     if ( !(field= mysql_fetch_field(stmt->result)) )
         return set_error(stmt,MYERR_S1002,"Invalid column number",0);
 
-    type=
unireg_to_sql_datatype(stmt,field,0,&transfer_length,&precision,&display_size
);
-/*
-printf( "[PAH][%s][%d][%s] type=%d\n", __FILE__, __LINE__, __FUNCTION__, type );
-*/
-    if ( pnColumnSize )
-        *pnColumnSize = precision;
-    if ( pfSqlType )
-        *pfSqlType= type;
-    if ( pibScale )
-        *pibScale= field->decimals;
+    if (pfSqlType)
+      *pfSqlType= get_sql_data_type(stmt, field, NULL);
+    if (pnColumnSize)
+      *pnColumnSize= get_column_size(stmt, field, FALSE);
+    if (pibScale)
+      *pibScale= max(0, get_decimal_digits(stmt, field));
     if ( pfNullable )
         *pfNullable= (((field->flags & (NOT_NULL_FLAG)) ==
                        NOT_NULL_FLAG) ?
@@ -581,7 +575,6 @@
     SQLSMALLINT str_length;
     SQLLEN strparam= 0;
     SQLPOINTER nparam= 0;
-    ulong transfer_length,precision,display_size;
     SQLRETURN error;
 
     if ( check_result(stmt) != SQL_SUCCESS )
@@ -673,56 +666,63 @@
             break;
 
         case SQL_DESC_DISPLAY_SIZE:
-            (void)
unireg_to_sql_datatype(stmt,field,0,&transfer_length,&precision,
-                                          &display_size);
-            *NumericAttributePtr= display_size;
+            *NumericAttributePtr= get_display_size(stmt, field);
             break;
 
-        case SQL_DESC_FIXED_PREC_SCALE: /* need to verify later */
-            {
-                if ( field->type == MYSQL_TYPE_DECIMAL )
-                    *(SQLINTEGER *)NumericAttributePtr= SQL_TRUE;
-                else
-                    *(SQLINTEGER *)NumericAttributePtr= SQL_FALSE;
-                break;
-            }
+        case SQL_DESC_FIXED_PREC_SCALE:
+            if (field->type == MYSQL_TYPE_DECIMAL ||
+                field->type == MYSQL_TYPE_NEWDECIMAL)
+              *(SQLINTEGER *)NumericAttributePtr= SQL_TRUE;
+            else
+              *(SQLINTEGER *)NumericAttributePtr= SQL_FALSE;
+            break;
 
-        case SQL_COLUMN_LENGTH:
         case SQL_DESC_LENGTH:
-        case SQL_DESC_OCTET_LENGTH: /* need to validate again for octet length..*/
-            (void)
unireg_to_sql_datatype(stmt,field,0,&transfer_length,&precision,
-                                          &display_size);
-            *NumericAttributePtr= transfer_length;
-            break;
+          *NumericAttributePtr= get_column_size(stmt, field, TRUE);
+          break;
 
+        case SQL_COLUMN_LENGTH:
+        case SQL_DESC_OCTET_LENGTH:
+          /* Need to add 1 for \0 on character fields. */
+          *NumericAttributePtr= get_transfer_octet_length(stmt, field) +
+            test(field->charsetnr != 63);
+          break;
+
+        case SQL_DESC_LITERAL_SUFFIX:
         case SQL_DESC_LITERAL_PREFIX:
-        case SQL_DESC_LITERAL_SUFFIX:
-            switch ( field->type )
-            {
-                case MYSQL_TYPE_LONG_BLOB:
-                case MYSQL_TYPE_TINY_BLOB:
-                case MYSQL_TYPE_MEDIUM_BLOB:
-                case MYSQL_TYPE_BLOB:
-                    return copy_str_data(SQL_HANDLE_STMT, stmt,
-                                         CharacterAttributePtr,
-                                         BufferLength, StringLengthPtr, "0x");
+            switch (field->type) {
+            case MYSQL_TYPE_LONG_BLOB:
+            case MYSQL_TYPE_TINY_BLOB:
+            case MYSQL_TYPE_MEDIUM_BLOB:
+            case MYSQL_TYPE_BLOB:
+            case MYSQL_TYPE_DATE:
+            case MYSQL_TYPE_DATETIME:
+            case MYSQL_TYPE_NEWDATE:
+            case MYSQL_TYPE_VAR_STRING:
+            case MYSQL_TYPE_STRING:
+            case MYSQL_TYPE_TIMESTAMP:
+            case MYSQL_TYPE_TIME:
+            case MYSQL_TYPE_YEAR:
+              if (field->charsetnr == 63)
+              {
+                if (FieldIdentifier == SQL_DESC_LITERAL_PREFIX)
+                  return copy_str_data(SQL_HANDLE_STMT, stmt,
+                                       CharacterAttributePtr,
+                                       BufferLength, StringLengthPtr, "0x");
+                else
+                  return copy_str_data(SQL_HANDLE_STMT, stmt,
+                                       CharacterAttributePtr,
+                                       BufferLength, StringLengthPtr, "");
+              }
+              else
+                return copy_str_data(SQL_HANDLE_STMT, stmt,
+                                     CharacterAttributePtr,
+                                     BufferLength, StringLengthPtr, "'");
 
-                case MYSQL_TYPE_DATE:
-                case MYSQL_TYPE_DATETIME:
-                case MYSQL_TYPE_NEWDATE:
-                case MYSQL_TYPE_VAR_STRING:
-                case MYSQL_TYPE_STRING:
-                case MYSQL_TYPE_TIMESTAMP:
-                case MYSQL_TYPE_TIME:
-                case MYSQL_TYPE_YEAR:
-                    return copy_str_data(SQL_HANDLE_STMT, stmt,
-                                         CharacterAttributePtr,
-                                         BufferLength, StringLengthPtr, "'");
-
-                default:
-                    return copy_str_data(SQL_HANDLE_STMT, stmt,
-                                         CharacterAttributePtr,
-                                         BufferLength, StringLengthPtr,"");
+            default:
+                return copy_str_data(SQL_HANDLE_STMT, stmt,
+                                     CharacterAttributePtr,
+                                     BufferLength, StringLengthPtr,"");
             }
             break;
 
@@ -760,14 +760,12 @@
 
         case SQL_COLUMN_PRECISION:
         case SQL_DESC_PRECISION:
-            (void)
unireg_to_sql_datatype(stmt,field,0,&transfer_length,&precision,
-                                          &display_size);
-            *(SQLINTEGER *)NumericAttributePtr= precision;
-            break;
+          *NumericAttributePtr= get_column_size(stmt, field, FALSE);
+          break;
 
         case SQL_COLUMN_SCALE:
         case SQL_DESC_SCALE:
-            *(SQLINTEGER *)NumericAttributePtr= field->decimals;
+            *NumericAttributePtr= max(0, get_decimal_digits(stmt, field));
             break;
 
         case SQL_DESC_SCHEMA_NAME:
@@ -779,17 +777,25 @@
             break;
 
         case SQL_DESC_TYPE:
-        case SQL_DESC_CONCISE_TYPE:
-            *(SQLINTEGER *)NumericAttributePtr =
-            unireg_to_sql_datatype(stmt, field, 0, &transfer_length, &precision,
-                                   &display_size);
+          {
+            SQLSMALLINT type= get_sql_data_type(stmt, field, NULL);
+            if (type == SQL_DATE || type == SQL_TYPE_DATE || type == SQL_TIME ||
+                type == SQL_TYPE_TIME || type == SQL_TIMESTAMP ||
+                type == SQL_TYPE_TIMESTAMP)
+              type= SQL_DATETIME;
+            *(SQLINTEGER *)NumericAttributePtr= type;
             break;
+          }
 
+        case SQL_DESC_CONCISE_TYPE:
+          *(SQLINTEGER *)NumericAttributePtr=
+            get_sql_data_type(stmt, field, NULL);
+          break;
+
         case SQL_DESC_TYPE_NAME:
             {
                 char buff[40];
-                (void)unireg_to_sql_datatype(stmt,field,(char
*)buff,&transfer_length,
-                                             &precision, &display_size);
+                (void)get_sql_data_type(stmt, field, buff);
                 return copy_str_data(SQL_HANDLE_STMT, stmt,
                                      CharacterAttributePtr, BufferLength,
                                      StringLengthPtr, buff);

Modified: trunk/driver/utility.c
===================================================================
--- trunk/driver/utility.c	2007-08-02 00:48:34 UTC (rev 626)
+++ trunk/driver/utility.c	2007-08-02 17:04:03 UTC (rev 627)
@@ -342,288 +342,504 @@
 }
 
 
-/*
-  @type    : myodbc internal
-  @purpose : get type, transfer length and precision for a unireg column
-  note that timestamp is changed to YYYY-MM-DD HH:MM:SS type
+/**
+  Get the SQL data type and (optionally) type name for a MYSQL_FIELD.
 
-  SQLUINTEGER
+  @param[in]  stmt
+  @param[in]  field
+  @param[out] buff
 
+  @return  The SQL data type.
 */
-
-int unireg_to_sql_datatype(STMT FAR *stmt, MYSQL_FIELD *field, char *buff,
-                           ulong *transfer_length, ulong *precision,
-                           ulong *display_size)
+SQLSMALLINT get_sql_data_type(STMT *stmt, MYSQL_FIELD *field, char *buff)
 {
-    char *pos;
-    my_bool field_is_binary= binary_field(field);
-/* PAH - SESSION 01
-    if ( stmt->dbc->flag & (FLAG_FIELD_LENGTH | FLAG_SAFE) )
-*/        *transfer_length= *precision= *display_size= max(field->length,
-                                                         field->max_length);
-/* PAH - SESSION 01
-    else
+  my_bool field_is_binary= test(field->charsetnr == 63);
 
-        *transfer_length= *precision= *display_size= field->max_length;
-*/
+  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;
 
-/* PAH - SESSION 01
-printf( "[PAH][%s][%d][%s] field->type=%d field_is_binary=%d\n", __FILE__, __LINE__,
__FUNCTION__, field->type, field_is_binary );
-*/
-    switch ( field->type )
+  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)
     {
-        case MYSQL_TYPE_BIT:
-            if ( buff )
-            {
-                pos= strmov(buff,"bit");
-            }
-            *transfer_length= 1;
-            return SQL_BIT;  
+      buff= strmov(buff, (field->flags & NUM_FLAG) ? "tinyint" : "char");
+      if (field->flags & UNSIGNED_FLAG)
+        (void)strmov(buff, " unsigned");
+    }
+    return (field->flags & NUM_FLAG) ? SQL_TINYINT : SQL_CHAR;
 
-        case MYSQL_TYPE_DECIMAL:
-        case MYSQL_TYPE_NEWDECIMAL:
-            *display_size= max(field->length,field->max_length) -
-                           test(!(field->flags & UNSIGNED_FLAG)) -
-                           test(field->decimals);
-            *precision= *display_size;
-            if ( buff ) strmov(buff,"decimal");
-            return SQL_DECIMAL;
+  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_TINY:
-            if ( num_field(field) )
-            {
-                if ( buff )
-                {
-                    pos= strmov(buff,"tinyint");
-                    if ( field->flags & UNSIGNED_FLAG )
-                        strmov(pos," unsigned");
-                }
-                *transfer_length= 1;
-                return SQL_TINYINT;
-            }
-            if ( buff )
-            {
-                pos= strmov(buff,"char");
-                if ( field->flags & UNSIGNED_FLAG )
-                    strmov(pos," unsigned");
-            }
-            *transfer_length= 1;
-            return SQL_CHAR;
+  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_SHORT:
-            if ( buff )
-            {
-                pos= strmov(buff,"smallint");
-                if ( field->flags & UNSIGNED_FLAG )
-                    strmov(pos," unsigned");
-            }
-            *transfer_length= 2;
-            return SQL_SMALLINT;
+  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_INT24:
-            if ( buff )
-            {
-                pos= strmov(buff,"mediumint");
-                if ( field->flags & UNSIGNED_FLAG )
-                    strmov(pos," unsigned");
-            }
-            *transfer_length= 4;
-            return SQL_INTEGER;
+  case MYSQL_TYPE_LONGLONG:
+    if (buff)
+    {
+      buff= strmov(buff, "bigint");
+      if (field->flags & UNSIGNED_FLAG)
+        (void)strmov(buff, " unsigned");
+    }
 
-        case MYSQL_TYPE_LONG:
-            if ( buff )
-            {
-                pos= strmov(buff,"integer");
-                if ( field->flags & UNSIGNED_FLAG )
-                    strmov(pos," unsigned");
-            }
-            *transfer_length= 4;
-            return SQL_INTEGER;
+    if (stmt->dbc->flag & FLAG_NO_BIGINT)
+      return SQL_INTEGER;
 
-        case MYSQL_TYPE_LONGLONG:
-            if ( buff )
-            {
-                pos= strmov(buff,"bigint");
-                if ( field->flags & UNSIGNED_FLAG )
-                    strmov(pos," unsigned");
-            }
-            *transfer_length= 20;
-            if ( stmt->dbc->flag & FLAG_NO_BIGINT )
-                return SQL_INTEGER;
-            if ( field->flags & UNSIGNED_FLAG )
-                *transfer_length= *precision= 20;
-            else
-                *transfer_length= *precision= 19;
-            return SQL_BIGINT;
+    return SQL_BIGINT;
 
-        case MYSQL_TYPE_FLOAT:
-            if ( buff )
-            {
-                pos= strmov(buff,"float");
-                if ( field->flags & UNSIGNED_FLAG )
-                    strmov(pos," unsigned");
-            }
-            *transfer_length= 4;
-            return SQL_REAL;
-        case MYSQL_TYPE_DOUBLE:
-            if ( buff )
-            {
-                pos= strmov(buff,"double");
-                if ( field->flags & UNSIGNED_FLAG )
-                    strmov(pos," unsigned");
-            }
-            *transfer_length= 8;
-            return SQL_DOUBLE;
+  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_NULL:
-            if ( buff ) strmov(buff,"null");
-            return SQL_VARCHAR;
+  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_YEAR:
-            if ( buff )
-                pos= strmov(buff,"year");
-            *transfer_length= 2;
-            return SQL_SMALLINT;
+  case MYSQL_TYPE_NULL:
+    if (buff)
+      (void)strmov(buff, "null");
+    return SQL_VARCHAR;
 
-        case MYSQL_TYPE_TIMESTAMP:
-            if ( buff ) strmov(buff,"timestamp");
-            *transfer_length= 16;      /* size of timestamp_struct */
-            *precision= *display_size= 19;
-            if ( stmt->dbc->env->odbc_ver == SQL_OV_ODBC3 )
-                return SQL_TYPE_TIMESTAMP;
-            return SQL_TIMESTAMP;
+  case MYSQL_TYPE_YEAR:
+    if (buff)
+      (void)strmov(buff, "year");
+    return SQL_SMALLINT;
 
-        case MYSQL_TYPE_DATETIME:
-            if ( buff ) strmov(buff,"datetime");
-            *transfer_length= 16;      /* size of timestamp_struct */
-            *precision= *display_size= 19;
-            if ( stmt->dbc->env->odbc_ver == SQL_OV_ODBC3 )
-                return SQL_TYPE_TIMESTAMP;
-            return SQL_TIMESTAMP;
+  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_NEWDATE:
-        case MYSQL_TYPE_DATE:
-            if ( buff ) strmov(buff,"date");
-            *transfer_length= 6;       /* size of date struct */
-            *precision= *display_size= 10;
-            if ( stmt->dbc->env->odbc_ver == SQL_OV_ODBC3 )
-                return SQL_TYPE_DATE;
-            return SQL_DATE;
+  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_TIME:
-            if ( buff ) strmov(buff,"time");
-            *transfer_length= 6;       /* size of time struct */
-            *precision= *display_size= 8;
-            if ( stmt->dbc->env->odbc_ver == SQL_OV_ODBC3 )
-                return SQL_TYPE_TIME;
-            return SQL_TIME;
+  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_STRING:
-            /* Binary flag is for handling "VARCHAR() BINARY" but is unreliable (see
BUG-4578) - PAH */
-            if (field_is_binary)
-            {
-              if (buff) strmov(buff,"binary");
-              return SQL_BINARY;
-            }
+  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;
 
-            *transfer_length= *precision= *display_size= field->length ? 
-                (stmt->dbc->mysql.charset ? 
-                field->length/stmt->dbc->mysql.charset->mbmaxlen:
field->length): 255;
-            if ( buff ) strmov(buff,"char");
-            return SQL_CHAR;
+  case MYSQL_TYPE_STRING:
+    if (buff)
+      (void)strmov(buff, field_is_binary ? "binary" : "char");
 
-        /*
-          MYSQL_TYPE_VARCHAR is never actually sent, this just silences
-          a compiler warning.
-        */
-        case MYSQL_TYPE_VARCHAR:
-        case MYSQL_TYPE_VAR_STRING:
-            /* 
-            TODO: field->length should be replaced by max(length, maxlength)
-                  in order to restore FLAG_FIELD_LENGTH option
-            */
-            *transfer_length= *precision= *display_size= field->length ?
-              (stmt->dbc->mysql.charset ?
-               field->length / stmt->dbc->mysql.charset->mbmaxlen :
-               field->length) : 255;
+    return field_is_binary ? SQL_BINARY : SQL_CHAR;
 
-            /*
-            TODO: Uncomment this code when MySQL Server returns the metadata correctly
+  /*
+    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");
 
-            if (field_is_binary)
-            {
-                if (buff)
-                  strmov(buff,"varbinary");
-                return SQL_VARBINARY;
-            }
-            */
+    return field_is_binary ? SQL_VARBINARY : SQL_VARCHAR;
 
-            if (buff)
-              strmov(buff,"varchar");
+  case MYSQL_TYPE_TINY_BLOB:
+    if (buff)
+      (void)strmov(buff, field_is_binary ? "tinyblob" : "tinytext");
 
-            return SQL_VARCHAR;
+    return field_is_binary ? SQL_LONGVARBINARY : SQL_LONGVARCHAR;
 
-        case MYSQL_TYPE_TINY_BLOB:
-            if ( buff )
-                strmov(buff,(field_is_binary) ? "tinyblob" : "tinytext");
-/* PAH - SESSION 01
-            if ( stmt->dbc->flag & (FLAG_FIELD_LENGTH | FLAG_SAFE) )
+  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;
+}
+
+
+/**
+  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
 */
-                *transfer_length= *precision= *display_size= field->length ? 
-                (stmt->dbc->mysql.charset ?
-                field->length/stmt->dbc->mysql.charset->mbmaxlen:
field->length): 255;
-            return(field_is_binary) ? SQL_LONGVARBINARY : SQL_LONGVARCHAR;
+SQLLEN get_column_size(STMT *stmt __attribute__((unused)), MYSQL_FIELD *field,
+                       my_bool actual)
+{
+  CHARSET_INFO *charset= get_charset(field->charsetnr, MYF(0));
+  unsigned int mbmaxlen= charset ? charset->mbmaxlen : 1;
+  SQLLEN length= actual ? field->max_length : field->length;
 
-        case MYSQL_TYPE_BLOB:
-            if ( buff )
-                strmov( buff, (field_is_binary) ? "blob" : "text" );
+  switch (field->type) {
+  case MYSQL_TYPE_TINY:
+    return (field->flags & NUM_FLAG) ? 3 : 1;
 
-/* PAH - SESSION 01
-            if ( stmt->dbc->flag & (FLAG_FIELD_LENGTH | FLAG_SAFE) ) 
+  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:
+    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. * 2
+    */
+    if (length == 1)
+      return 1;
+    return (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 length * 2;
+    else
+      return length / mbmaxlen;
+  }
+
+  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
 */
-                *transfer_length= *precision= *display_size= field->length ?
(stmt->dbc->mysql.charset ?
field->length/stmt->dbc->mysql.charset->mbmaxlen: field->length): 65535;
+SQLLEN get_decimal_digits(STMT *stmt __attribute__((unused)),
+                          MYSQL_FIELD *field)
+{
+  switch (field->type) {
+  case MYSQL_TYPE_DECIMAL:
+  case MYSQL_TYPE_NEWDECIMAL:
+    return field->decimals;
 
-            return ( field_is_binary ) ? SQL_LONGVARBINARY : SQL_LONGVARCHAR;
+  /* 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;
 
-        case MYSQL_TYPE_MEDIUM_BLOB:
-            if ( buff )
-                strmov(buff,((field_is_binary) ? "mediumblob" :
-                             "mediumtext"));
-/* PAH - SESSION 01
-            if ( stmt->dbc->flag & (FLAG_FIELD_LENGTH | FLAG_SAFE) )
+  /* 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
 */
-                *transfer_length= *precision= *display_size= field->length ? 
-                (stmt->dbc->mysql.charset ?
-                field->length/stmt->dbc->mysql.charset->mbmaxlen:
field->length): (1L << 24)-1L;
-            return(field_is_binary) ? SQL_LONGVARBINARY : SQL_LONGVARCHAR;
+SQLLEN get_transfer_octet_length(STMT *stmt __attribute__((unused)),
+                                 MYSQL_FIELD *field)
+{
+  switch (field->type) {
+  case MYSQL_TYPE_TINY:
+    return 1;
 
-        case MYSQL_TYPE_LONG_BLOB:
-            if ( buff )
-                strmov(buff,((field_is_binary) ? "longblob": "longtext"));
-/* PAH - SESSION 01
-            if ( stmt->dbc->flag & (FLAG_FIELD_LENGTH | FLAG_SAFE) )
+  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 -
+            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. 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
 */
-                *transfer_length= *precision= *display_size= field->length ? 
-                (stmt->dbc->mysql.charset ?
-                field->length/stmt->dbc->mysql.charset->mbmaxlen:
field->length): INT_MAX32;
-            return(field_is_binary) ? SQL_LONGVARBINARY : SQL_LONGVARCHAR;
+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;
 
-        case MYSQL_TYPE_ENUM:
-            if ( buff )
-                strmov(buff,"enum");
-            return SQL_CHAR;
+  switch (field->type) {
+  case MYSQL_TYPE_TINY:
+    return 3 + test(field->flags & UNSIGNED_FLAG);
 
-        case MYSQL_TYPE_SET:
-            if ( buff )
-                strmov(buff,"set");
-            return SQL_CHAR;
+  case MYSQL_TYPE_SHORT:
+    return 5 + test(field->flags & UNSIGNED_FLAG);
 
-        case MYSQL_TYPE_GEOMETRY:
-            if ( buff )
-                strmov(buff,"blob");
-            return SQL_LONGVARBINARY;
-    }
-    return 0; /* Impossible */
+  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;
 }
 
 
@@ -640,7 +856,11 @@
         default:
             return SQL_C_CHAR;
         case MYSQL_TYPE_BIT:
-            return SQL_C_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:

Modified: trunk/test/my_basics.c
===================================================================
--- trunk/test/my_basics.c	2007-08-02 00:48:34 UTC (rev 626)
+++ trunk/test/my_basics.c	2007-08-02 17:04:03 UTC (rev 627)
@@ -259,6 +259,15 @@
   SQLCHAR   conn[256], conn_out[256];
   SQLSMALLINT conn_out_len;
 
+  /**
+   Bug #19345: Table column length multiplies on size session character set
+  */
+  ok_sql(hstmt, "DROP TABLE IF EXISTS t_bug19345");
+  ok_sql(hstmt, "CREATE TABLE t_bug19345 (a VARCHAR(10), b VARBINARY(10))");
+  ok_sql(hstmt, "INSERT INTO t_bug19345 VALUES ('abc','def')");
+
+  ok_stmt(hstmt, SQLFreeStmt(hstmt, SQL_CLOSE));
+
   sprintf((char *)conn, "DSN=%s;UID=%s;PASSWORD=%s;CHARSET=utf8",
           mydsn, myuid, mypwd);
   if (mysock != NULL)
@@ -282,6 +291,22 @@
 
   expect_stmt(hstmt1, SQLFetch(hstmt1), SQL_NO_DATA);
 
+  ok_stmt(hstmt1, SQLFreeStmt(hstmt1, SQL_CLOSE));
+
+  ok_stmt(hstmt1, SQLColumns(hstmt1, (SQLCHAR *)"test", SQL_NTS, NULL, 0,
+                             (SQLCHAR *)"t_bug19345", SQL_NTS,
+                             (SQLCHAR *)"%", 1));
+
+  ok_stmt(hstmt1, SQLFetch(hstmt1));
+  is_num(my_fetch_int(hstmt1, 7), 10);
+  is_num(my_fetch_int(hstmt1, 8), 30);
+  is_num(my_fetch_int(hstmt1, 16), 30);
+
+  ok_stmt(hstmt1, SQLFetch(hstmt1));
+  is_num(my_fetch_int(hstmt1, 7), 20);
+  is_num(my_fetch_int(hstmt1, 8), 10);
+  is_num(my_fetch_int(hstmt1, 16), 10);
+
   ok_stmt(hstmt1, SQLFreeStmt(hstmt1, SQL_DROP));
   ok_con(hdbc1, SQLDisconnect(hdbc1));
   ok_con(hdbc1, SQLFreeHandle(SQL_HANDLE_DBC, hdbc1));

Modified: trunk/test/my_catalog.c
===================================================================
--- trunk/test/my_catalog.c	2007-08-02 00:48:34 UTC (rev 626)
+++ trunk/test/my_catalog.c	2007-08-02 17:04:03 UTC (rev 627)
@@ -480,13 +480,13 @@
   SQLCHAR       ColumnName[MAX_NAME_LEN], DatabaseName[MAX_NAME_LEN];
   SQLINTEGER    Values[7][5][2]=
   {
-    { {5,2},  {6,4}, {0,2},  {10,2},  {1,2}},
+    { {5,2},  {5,4}, {0,2},  {10,2},  {1,2}},
     { {1,2},  {5,4},  {0,-1}, {10,-1}, {1,2}},
     { {12,2}, {20,4}, {0,-1}, {10,-1}, {0,2}},
     { {3,2},  {10,4}, {2,2},  {10,2},  {1,2}},
-    { {-6,2},  {4,4}, {0,2},  {10,2},  {0,2}},
-    { {4,2}, {11,4}, {0,2},  {10,2},  {0,2}},
-    { {-6,2}, {4,4}, {0,2},  {10,2},  {0,2}}
+    { {-6,2},  {3,4}, {0,2},  {10,2},  {0,2}},
+    { {4,2}, {10,4}, {0,2},  {10,2},  {0,2}},
+    { {-6,2}, {3,4}, {0,2},  {10,2},  {0,2}}
   };
 
   ok_sql(hstmt, "DROP TABLE IF EXISTS t_columns");
@@ -512,6 +512,7 @@
   for (i= 0; i < ColumnCount; i++)
   {
     sprintf((char *)ColumnName, "col%d", (int)i);
+    printMessage("checking column `%s`", (char *)ColumnName);
 
     ok_stmt(hstmt, SQLColumns(hstmt,
                               DatabaseName, cbDatabaseName,

Modified: trunk/test/my_datetime.c
===================================================================
--- trunk/test/my_datetime.c	2007-08-02 00:48:34 UTC (rev 626)
+++ trunk/test/my_datetime.c	2007-08-02 17:04:03 UTC (rev 627)
@@ -741,6 +741,74 @@
 
   return OK;
 }
+
+
+/**
+  Verify that we get correct data for SQL_DATA_TYPE and SQL_DATETIME_SUB
+  from SQLColumns(). Also check SQL_DESC_TYPE from SQLColAttribute().
+*/
+DECLARE_TEST(t_datecolumns)
+{
+  SQLCHAR col[10];
+  SQLINTEGER type;
+
+  ok_sql(hstmt, "DROP TABLE IF EXISTS t_datecolumns");
+  ok_sql(hstmt,
+         "CREATE TABLE t_datecolumns(a TIMESTAMP, b DATETIME, c DATE, d TIME)");
+
+  ok_stmt(hstmt, SQLColumns(hstmt, NULL, 0, NULL, 0,
+                            (SQLCHAR *)"t_datecolumns", SQL_NTS, NULL, 0));
+
+  ok_stmt(hstmt, SQLFetch(hstmt));
+
+  is_str(my_fetch_str(hstmt, col, 4), "a", 1);
+  is_num(my_fetch_int(hstmt, 14), SQL_DATETIME);
+  is_num(my_fetch_int(hstmt, 15), SQL_TYPE_TIMESTAMP);
+
+  ok_stmt(hstmt, SQLFetch(hstmt));
+
+  is_str(my_fetch_str(hstmt, col, 4), "b", 1);
+  is_num(my_fetch_int(hstmt, 14), SQL_DATETIME);
+  is_num(my_fetch_int(hstmt, 15), SQL_TYPE_TIMESTAMP);
+
+  ok_stmt(hstmt, SQLFetch(hstmt));
+
+  is_str(my_fetch_str(hstmt, col, 4), "c", 1);
+  is_num(my_fetch_int(hstmt, 14), SQL_DATETIME);
+  is_num(my_fetch_int(hstmt, 15), SQL_TYPE_DATE);
+
+  ok_stmt(hstmt, SQLFetch(hstmt));
+
+  is_str(my_fetch_str(hstmt, col, 4), "d", 1);
+  is_num(my_fetch_int(hstmt, 14), SQL_DATETIME);
+  is_num(my_fetch_int(hstmt, 15), SQL_TYPE_TIME);
+
+  expect_stmt(hstmt, SQLFetch(hstmt), SQL_NO_DATA_FOUND);
+
+  ok_stmt(hstmt, SQLFreeStmt(hstmt, SQL_CLOSE));
+
+  ok_sql(hstmt, "SELECT * FROM t_datecolumns");
+
+  ok_stmt(hstmt, SQLColAttribute(hstmt, 1, SQL_DESC_TYPE, NULL, 0, NULL,
+                                 &type));
+  is_num(type, SQL_DATETIME);
+  ok_stmt(hstmt, SQLColAttribute(hstmt, 2, SQL_DESC_TYPE, NULL, 0, NULL,
+                                 &type));
+  is_num(type, SQL_DATETIME);
+  ok_stmt(hstmt, SQLColAttribute(hstmt, 3, SQL_DESC_TYPE, NULL, 0, NULL,
+                                 &type));
+  is_num(type, SQL_DATETIME);
+  ok_stmt(hstmt, SQLColAttribute(hstmt, 4, SQL_DESC_TYPE, NULL, 0, NULL,
+                                 &type));
+  is_num(type, SQL_DATETIME);
+
+  ok_stmt(hstmt, SQLFreeStmt(hstmt, SQL_CLOSE));
+
+  ok_sql(hstmt, "DROP TABLE IF EXISTS t_datecolumns");
+  return OK;
+}
+
+
 BEGIN_TESTS
   ADD_TEST(my_ts)
   ADD_TEST(t_tstotime)
@@ -752,6 +820,7 @@
   ADD_TEST(t_bug15773)
   ADD_TEST(t_bug9927)
   ADD_TODO(t_bug30081)
+  ADD_TEST(t_datecolumns)
 END_TESTS
 
 

Modified: trunk/test/my_result.c
===================================================================
--- trunk/test/my_result.c	2007-08-02 00:48:34 UTC (rev 626)
+++ trunk/test/my_result.c	2007-08-02 17:04:03 UTC (rev 627)
@@ -258,8 +258,8 @@
                            SQLUSMALLINT icol,
                            const char *name,
                            SQLSMALLINT sql_type,
-                           SQLUINTEGER col_def,
-                           SQLUINTEGER col_def1,
+                           SQLULEN col_def,
+                           SQLULEN col_def1,
                            SQLSMALLINT scale,
                            SQLSMALLINT nullable)
 {
@@ -334,36 +334,31 @@
     fprintf(stdout,"total columns: %d\n", ColumnCount);
     my_assert(ColumnCount == 23);
 
-    desc_col_check(hstmt, 1,  "c1",  SQL_INTEGER,   10, 11, 0,  SQL_NULLABLE);
-    desc_col_check(hstmt, 2,  "c2",  SQL_BINARY,    2,  2,  0,  SQL_NO_NULLS);
+    desc_col_check(hstmt, 1,  "c1",  SQL_INTEGER,   10, 10, 0,  SQL_NULLABLE);
+    desc_col_check(hstmt, 2,  "c2",  SQL_BINARY,    4,  2,  0,  SQL_NO_NULLS);
     desc_col_check(hstmt, 3,  "c3",  SQL_CHAR,      1,  1,  0,  SQL_NULLABLE);
     desc_col_check(hstmt, 4,  "c4",  SQL_VARCHAR,   5,  5,  0,  SQL_NULLABLE);
     desc_col_check(hstmt, 5,  "c5",  SQL_DECIMAL,   10, 10, 3,  SQL_NO_NULLS);
     desc_col_check(hstmt, 6,  "c6",  SQL_TINYINT,   3,  4,  0,  SQL_NULLABLE);
     desc_col_check(hstmt, 7,  "c7",  SQL_SMALLINT,  5,  6,  0,  SQL_NULLABLE);
     desc_col_check(hstmt, 8,  "c8",  SQL_DECIMAL,   4,  4,  2,  SQL_NULLABLE);
-    desc_col_check(hstmt, 9,  "c9",  SQL_DOUBLE,    22, 24, 31, SQL_NULLABLE);
-    desc_col_check(hstmt, 10, "c10", SQL_REAL,      12, 24, 31, SQL_NULLABLE);
+    desc_col_check(hstmt, 9,  "c9",  SQL_DOUBLE,    15, 15, 0, SQL_NULLABLE);
+    desc_col_check(hstmt, 10, "c10", SQL_REAL,      7,  7,  0, SQL_NULLABLE);
     desc_col_check(hstmt, 11, "c11", SQL_BIGINT,    19, 19, 0,  SQL_NO_NULLS);
 
-    /*
-    SQL_VARBINARY is temporarily disabled due to the problems with metadata 
-    returned by the server.
+    desc_col_check(hstmt, 12, "c12", SQL_VARBINARY, 24, 24, 0,  SQL_NULLABLE);
 
-    desc_col_check(hstmt, 12, "c12", SQL_VARBINARY, 12, 12, 0,  SQL_NULLABLE);
-    */
-
     desc_col_check(hstmt, 13, "c13", SQL_CHAR,      20, 20, 0,  SQL_NO_NULLS);
-    desc_col_check(hstmt, 14, "c14", SQL_REAL,      10, 24, 3,  SQL_NULLABLE);
+    desc_col_check(hstmt, 14, "c14", SQL_REAL,      7,  7,  0,  SQL_NULLABLE);
     desc_col_check(hstmt, 15, "c15", SQL_LONGVARCHAR, 255, 255, 0,  SQL_NULLABLE);
     desc_col_check(hstmt, 16, "c16", SQL_LONGVARCHAR, 65535, 65535, 0,  SQL_NULLABLE);
     desc_col_check(hstmt, 17, "c17", SQL_LONGVARCHAR, 16777215, 16777215, 0, 
SQL_NULLABLE);
     desc_col_check(hstmt, 18, "c18", SQL_LONGVARCHAR, 4294967295 , 16777215 , 0, 
SQL_NULLABLE);
-    desc_col_check(hstmt, 19, "c19", SQL_LONGVARBINARY, 255, 255, 0,  SQL_NULLABLE);
-    desc_col_check(hstmt, 20, "c20", SQL_LONGVARBINARY, 65535, 65535, 0,  SQL_NULLABLE);
-    desc_col_check(hstmt, 21, "c21", SQL_LONGVARBINARY, 16777215, 16777215, 0, 
SQL_NULLABLE);
-    desc_col_check(hstmt, 22, "c22", SQL_LONGVARBINARY, 4294967295 , 16777215 , 0, 
SQL_NULLABLE);
-    desc_col_check(hstmt, 23, "c23", SQL_LONGVARBINARY, 255, 5, 0,  SQL_NULLABLE);
+    desc_col_check(hstmt, 19, "c19", SQL_LONGVARBINARY, 255 * 2, 255, 0,  SQL_NULLABLE);
+    desc_col_check(hstmt, 20, "c20", SQL_LONGVARBINARY, 65535 * 2, 65535, 0, 
SQL_NULLABLE);
+    desc_col_check(hstmt, 21, "c21", SQL_LONGVARBINARY, 16777215 * 2, 16777215, 0, 
SQL_NULLABLE);
+    desc_col_check(hstmt, 22, "c22", SQL_LONGVARBINARY, 4294967295 * 2 , 16777215 , 0, 
SQL_NULLABLE);
+    desc_col_check(hstmt, 23, "c23", SQL_LONGVARBINARY, 255 * 2, 5, 0,  SQL_NULLABLE);
 
     SQLFreeStmt(hstmt,SQL_CLOSE);
 
@@ -1132,7 +1127,7 @@
 
 
 void desccol(SQLHSTMT hstmt, SQLCHAR    *cname,  SQLSMALLINT clen,
-             SQLSMALLINT sqltype,SQLUINTEGER size,
+             SQLSMALLINT sqltype,SQLULEN size,
              SQLSMALLINT scale,SQLSMALLINT isNull)
 {
     SQLRETURN   rc =0;
@@ -1246,19 +1241,19 @@
     rc = SQLFreeStmt(hstmt,SQL_CLOSE);
     mystmt(hstmt,rc);
 
-    desccol(hstmt,"t1",2,SQL_TINYINT,4,0,SQL_NULLABLE);
-    desccol(hstmt,"t2",2,SQL_TINYINT,10,0,SQL_NULLABLE);
+    desccol(hstmt,"t1",2,SQL_TINYINT,3,0,SQL_NULLABLE);
+    desccol(hstmt,"t2",2,SQL_TINYINT,3,0,SQL_NULLABLE);
     desccol(hstmt,"t3",2,SQL_TINYINT,3,0,SQL_NULLABLE);
 
-    desccol(hstmt,"s1",2,SQL_SMALLINT,6,0,SQL_NULLABLE);
-    desccol(hstmt,"s2",2,SQL_SMALLINT,10,0,SQL_NULLABLE);
+    desccol(hstmt,"s1",2,SQL_SMALLINT,5,0,SQL_NULLABLE);
+    desccol(hstmt,"s2",2,SQL_SMALLINT,5,0,SQL_NULLABLE);
     desccol(hstmt,"s3",2,SQL_SMALLINT,5,0,SQL_NULLABLE);
 
-    desccol(hstmt,"m1",2,SQL_INTEGER,9,0,SQL_NULLABLE);
-    desccol(hstmt,"m2",2,SQL_INTEGER,10,0,SQL_NULLABLE);
+    desccol(hstmt,"m1",2,SQL_INTEGER,8,0,SQL_NULLABLE);
+    desccol(hstmt,"m2",2,SQL_INTEGER,8,0,SQL_NULLABLE);
     desccol(hstmt,"m3",2,SQL_INTEGER,8,0,SQL_NULLABLE);
 
-    desccol(hstmt,"i1",2,SQL_INTEGER,11,0,SQL_NULLABLE);
+    desccol(hstmt,"i1",2,SQL_INTEGER,10,0,SQL_NULLABLE);
     desccol(hstmt,"i2",2,SQL_INTEGER,10,0,SQL_NO_NULLS);
     desccol(hstmt,"i3",2,SQL_INTEGER,10,0,SQL_NULLABLE);
     desccol(hstmt,"i4",2,SQL_INTEGER,10,0,SQL_NULLABLE);
@@ -1267,15 +1262,15 @@
     desccol(hstmt,"b2",2,SQL_BIGINT,19,0,SQL_NULLABLE);
     desccol(hstmt,"b3",2,SQL_BIGINT,20,0,SQL_NULLABLE);
 
-    desccol(hstmt,"f1",2,SQL_REAL,12,31,SQL_NULLABLE);
-    desccol(hstmt,"f2",2,SQL_REAL,12,31,SQL_NULLABLE);
-    desccol(hstmt,"f3",2,SQL_REAL,12,31,SQL_NULLABLE);
-    desccol(hstmt,"f4",2,SQL_REAL,10,4,SQL_NULLABLE);
+    desccol(hstmt,"f1",2,SQL_REAL,7,0,SQL_NULLABLE);
+    desccol(hstmt,"f2",2,SQL_REAL,7,0,SQL_NULLABLE);
+    desccol(hstmt,"f3",2,SQL_REAL,7,0,SQL_NULLABLE);
+    desccol(hstmt,"f4",2,SQL_REAL,7,0,SQL_NULLABLE);
 
-    desccol(hstmt,"d1",2,SQL_DOUBLE,22,31,SQL_NULLABLE);
-    desccol(hstmt,"d2",2,SQL_DOUBLE,30,3,SQL_NULLABLE);
-    desccol(hstmt,"d3",2,SQL_DOUBLE,22,31,SQL_NULLABLE);
-    desccol(hstmt,"d4",2,SQL_DOUBLE,30,3,SQL_NULLABLE);
+    desccol(hstmt,"d1",2,SQL_DOUBLE,15,0,SQL_NULLABLE);
+    desccol(hstmt,"d2",2,SQL_DOUBLE,15,0,SQL_NULLABLE);
+    desccol(hstmt,"d3",2,SQL_DOUBLE,15,0,SQL_NULLABLE);
+    desccol(hstmt,"d4",2,SQL_DOUBLE,15,0,SQL_NULLABLE);
 
     rc = SQLFreeStmt(hstmt,SQL_CLOSE);
     mystmt(hstmt,rc);

Modified: trunk/test/my_types.c
===================================================================
--- trunk/test/my_types.c	2007-08-02 00:48:34 UTC (rev 626)
+++ trunk/test/my_types.c	2007-08-02 17:04:03 UTC (rev 627)
@@ -485,6 +485,205 @@
 }
 
 
+/**
+ Bug #27862: Function return incorrect SQL_COLUMN_SIZE
+*/
+DECLARE_TEST(t_bug27862_1)
+{
+  SQLLEN len;
+
+  ok_sql(hstmt, "DROP TABLE IF EXISTS t_bug27862");
+  ok_sql(hstmt, "CREATE TABLE t_bug27862 (a VARCHAR(2), b VARCHAR(2))");
+  ok_sql(hstmt, "INSERT INTO t_bug27862 VALUES ('a','b')");
+
+  ok_sql(hstmt, "SELECT CONCAT(a,b) FROM t_bug27862");
+
+  ok_stmt(hstmt, SQLColAttribute(hstmt, 1, SQL_DESC_DISPLAY_SIZE, NULL, 0,
+                                 NULL, &len));
+  is_num(len, 4);
+  ok_stmt(hstmt, SQLColAttribute(hstmt, 1, SQL_DESC_LENGTH, NULL, 0,
+                                 NULL, &len));
+  is_num(len, 2);
+  ok_stmt(hstmt, SQLColAttribute(hstmt, 1, SQL_DESC_OCTET_LENGTH, NULL, 0,
+                                 NULL, &len));
+  is_num(len, 5);
+
+  ok_stmt(hstmt, SQLFreeStmt(hstmt, SQL_CLOSE));
+
+  ok_sql(hstmt, "DROP TABLE IF EXISTS t_bug27862");
+
+  return OK;
+}
+
+
+/**
+ Because integers are given the charset 63 (binary) when they are
+ used as strings, functions like CONCAT() return a binary string.
+ This is a server bug that we do not try to work around.
+*/
+DECLARE_TEST(t_bug27862_2)
+{
+  SQLLEN len;
+
+  ok_sql(hstmt, "DROP TABLE IF EXISTS t_bug27862");
+  ok_sql(hstmt, "CREATE TABLE t_bug27862 (c DATE, d INT)");
+  ok_sql(hstmt, "INSERT INTO t_bug27862 VALUES ('2007-01-13',5)");
+
+  ok_sql(hstmt, "SELECT CONCAT_WS(' - ', DATE_FORMAT(c, '%b-%d-%y'), d) "
+         "FROM t_bug27862");
+
+  ok_stmt(hstmt, SQLColAttribute(hstmt, 1, SQL_DESC_DISPLAY_SIZE, NULL, 0,
+                                 NULL, &len));
+  is_num(len, 104);
+  ok_stmt(hstmt, SQLColAttribute(hstmt, 1, SQL_DESC_LENGTH, NULL, 0,
+                                 NULL, &len));
+  is_num(len, 26);
+  ok_stmt(hstmt, SQLColAttribute(hstmt, 1, SQL_DESC_OCTET_LENGTH, NULL, 0,
+                                 NULL, &len));
+  is_num(len, 27);
+
+  ok_stmt(hstmt, SQLFreeStmt(hstmt, SQL_CLOSE));
+
+  ok_sql(hstmt, "DROP TABLE IF EXISTS t_bug27862");
+
+  return OK;
+}
+
+
+/**
+  SQL_DESC_FIXED_PREC_SCALE was wrong for new DECIMAL types.
+*/
+DECLARE_TEST(decimal_scale)
+{
+  SQLINTEGER fixed= SQL_FALSE;
+
+  ok_sql(hstmt, "DROP TABLE IF EXISTS t_decscale");
+  ok_sql(hstmt, "CREATE TABLE t_decscale (a DECIMAL(5,3))");
+
+  ok_sql(hstmt, "SELECT * FROM t_decscale");
+
+  ok_stmt(hstmt, SQLColAttribute(hstmt, 1, SQL_DESC_FIXED_PREC_SCALE,
+                                 NULL, 0, NULL, &fixed));
+
+  is_num(fixed, SQL_TRUE);
+
+  ok_stmt(hstmt, SQLFreeStmt(hstmt, SQL_CLOSE));
+
+  ok_sql(hstmt, "DROP TABLE IF EXISTS t_decscale");
+  return OK;
+}
+
+
+/**
+  Wrong value returned for SQL_DESC_LITERAL_SUFFIX for binary field.
+*/
+DECLARE_TEST(binary_suffix)
+{
+  SQLCHAR suffix[10];
+  SQLSMALLINT len;
+
+  ok_sql(hstmt, "DROP TABLE IF EXISTS t_binarysuffix");
+  ok_sql(hstmt, "CREATE TABLE t_binarysuffix (a BINARY(10))");
+
+  ok_sql(hstmt, "SELECT * FROM t_binarysuffix");
+
+  ok_stmt(hstmt, SQLColAttribute(hstmt, 1, SQL_DESC_LITERAL_SUFFIX,
+                                 suffix, 10, &len, NULL));
+
+  is_num(len, 0);
+
+  ok_stmt(hstmt, SQLFreeStmt(hstmt, SQL_CLOSE));
+
+  ok_sql(hstmt, "DROP TABLE IF EXISTS t_binarysuffix");
+  return OK;
+}
+
+
+/**
+  Wrong value returned for SQL_DESC_SCALE for float and double.
+*/
+DECLARE_TEST(float_scale)
+{
+  SQLINTEGER scale;
+
+  ok_sql(hstmt, "DROP TABLE IF EXISTS t_floatscale");
+  ok_sql(hstmt, "CREATE TABLE t_floatscale(a FLOAT, b DOUBLE, c DECIMAL(3,2))");
+
+  ok_sql(hstmt, "SELECT * FROM t_floatscale");
+
+  ok_stmt(hstmt, SQLColAttribute(hstmt, 1, SQL_DESC_SCALE,
+                                 NULL, 0, NULL, &scale));
+
+  is_num(scale, 0);
+
+  ok_stmt(hstmt, SQLColAttribute(hstmt, 2, SQL_DESC_SCALE,
+                                 NULL, 0, NULL, &scale));
+
+  is_num(scale, 0);
+
+  ok_stmt(hstmt, SQLColAttribute(hstmt, 3, SQL_DESC_SCALE,
+                                 NULL, 0, NULL, &scale));
+
+  is_num(scale, 2);
+
+  ok_stmt(hstmt, SQLFreeStmt(hstmt, SQL_CLOSE));
+
+  ok_sql(hstmt, "DROP TABLE IF EXISTS t_floatscale");
+  return OK;
+}
+
+
+/**
+  Test the BIT type, which has different behavior for BIT(1) and BIT(n > 1).
+*/
+DECLARE_TEST(bit)
+{
+  SQLCHAR col[10];
+  SQLINTEGER type;
+
+  ok_sql(hstmt, "DROP TABLE IF EXISTS t_bit");
+  ok_sql(hstmt, "CREATE TABLE t_bit (a BIT(1), b BIT(17))");
+
+  ok_stmt(hstmt, SQLColumns(hstmt, NULL, 0, NULL, 0,
+                            (SQLCHAR *)"t_bit", SQL_NTS, NULL, 0));
+
+  ok_stmt(hstmt, SQLFetch(hstmt));
+
+  is_str(my_fetch_str(hstmt, col, 4), "a", 1);
+  is_num(my_fetch_int(hstmt, 5), SQL_BIT); /* DATA_TYPE */
+  is_num(my_fetch_int(hstmt, 7), 1); /* COLUMN_SIZE */
+  is_num(my_fetch_int(hstmt, 8), 1); /* BUFFER_LENGTH */
+  is_num(my_fetch_int(hstmt, 16), 1); /* CHAR_OCTET_LENGTH */
+
+  ok_stmt(hstmt, SQLFetch(hstmt));
+
+  is_str(my_fetch_str(hstmt, col, 4), "b", 1);
+  is_num(my_fetch_int(hstmt, 5), SQL_BINARY); /* DATA_TYPE */
+  is_num(my_fetch_int(hstmt, 7), 6); /* COLUMN_SIZE */
+  is_num(my_fetch_int(hstmt, 8), 3); /* BUFFER_LENGTH */
+  is_num(my_fetch_int(hstmt, 16), 3); /* CHAR_OCTET_LENGTH */
+
+  expect_stmt(hstmt, SQLFetch(hstmt), SQL_NO_DATA_FOUND);
+
+  ok_stmt(hstmt, SQLFreeStmt(hstmt, SQL_CLOSE));
+
+  ok_sql(hstmt, "SELECT * FROM t_bit");
+
+  ok_stmt(hstmt, SQLColAttribute(hstmt, 1, SQL_DESC_TYPE, NULL, 0, NULL,
+                                 &type));
+  is_num(type, SQL_BIT);
+
+  ok_stmt(hstmt, SQLColAttribute(hstmt, 2, SQL_DESC_TYPE, NULL, 0, NULL,
+                                 &type));
+  is_num(type, SQL_BINARY);
+
+  ok_stmt(hstmt, SQLFreeStmt(hstmt, SQL_CLOSE));
+
+  ok_sql(hstmt, "DROP TABLE IF EXISTS t_bit");
+  return OK;
+}
+
+
 BEGIN_TESTS
   ADD_TEST(t_longlong1)
   ADD_TEST(t_numeric)
@@ -493,6 +692,12 @@
   ADD_TEST(t_enumset)
   ADD_TEST(t_bug16917)
   ADD_TEST(t_bug16235)
+  ADD_TEST(t_bug27862_1)
+  ADD_TODO(t_bug27862_2)
+  ADD_TEST(decimal_scale)
+  ADD_TEST(binary_suffix)
+  ADD_TEST(float_scale)
+  ADD_TEST(bit)
 END_TESTS
 
 

Thread
Connector/ODBC 3.51 commit: r627 - in trunk: . driver testjwinstead2 Aug