Index: ChangeLog =================================================================== --- ChangeLog (revision 607) +++ ChangeLog (working copy) @@ -5,6 +5,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) * SQLConnect() and SQLDriverConnect() were rewritten to eliminate duplicate code and ensure all options were supported using both connection methods. SQLDriverConnect() now only requires the setup library to be present when Index: driver/results.c =================================================================== --- driver/results.c (revision 607) +++ driver/results.c (working copy) @@ -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); Index: driver/utility.c =================================================================== --- driver/utility.c (revision 607) +++ driver/utility.c (working copy) @@ -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: Index: driver/catalog.c =================================================================== --- driver/catalog.c (revision 607) +++ driver/catalog.c (working copy) @@ -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,56 @@ 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_DATE || type == SQL_TYPE_DATE || type == SQL_TIME || + type == SQL_TYPE_TIME || type == SQL_TIMESTAMP || + 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 +918,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 +926,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 +1520,6 @@ MYSQL_RES *result; MYSQL_FIELD *field; MEM_ROOT *alloc; - ulong transfer_length,precision,display_size; uint field_count; my_bool primary_key; @@ -1544,24 +1557,30 @@ for ( row= stmt->result_array; (field = mysql_fetch_field(result)); ) { - int type; - if ( (field->type != MYSQL_TYPE_TIMESTAMP) ) + SQLSMALLINT type; + if ((field->type != MYSQL_TYPE_TIMESTAMP)) 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; @@ -1607,7 +1626,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 @@ -1620,17 +1639,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; Index: driver/cursor.c =================================================================== --- driver/cursor.c (revision 607) +++ driver/cursor.c (working copy) @@ -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 + Index: driver/myutil.h =================================================================== --- driver/myutil.h (revision 607) +++ driver/myutil.h (working copy) @@ -46,8 +46,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 @@ -111,9 +109,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, Index: test/my_types.c =================================================================== --- test/my_types.c (revision 607) +++ test/my_types.c (working copy) @@ -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 Index: test/my_catalog.c =================================================================== --- test/my_catalog.c (revision 607) +++ test/my_catalog.c (working copy) @@ -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, Index: test/my_datetime.c =================================================================== --- test/my_datetime.c (revision 607) +++ test/my_datetime.c (working copy) @@ -686,6 +686,72 @@ } +/** + 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) @@ -695,6 +761,7 @@ ADD_TEST(t_time1) ADD_TEST(t_bug12520) ADD_TEST(t_bug15773) + ADD_TEST(t_datecolumns) END_TESTS Index: test/my_basics.c =================================================================== --- test/my_basics.c (revision 607) +++ test/my_basics.c (working copy) @@ -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)); Index: test/my_result.c =================================================================== --- test/my_result.c (revision 607) +++ test/my_result.c (working copy) @@ -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);