=== modified file 'ChangeLog' --- ChangeLog 2009-11-18 15:50:09 +0000 +++ ChangeLog 2009-12-01 15:06:22 +0000 @@ -3,6 +3,7 @@ Bugs fixed: * Error if unsupported client character set is encountered(for wchar data). (Bug #36996) + * Binding bit field to a numeric types doesn't work. (Bug #32821) 5.1.6 (09-Nov-2009) === modified file 'driver/results.c' --- driver/results.c 2009-10-19 20:13:39 +0000 +++ driver/results.c 2009-12-01 14:42:37 +0000 @@ -44,6 +44,104 @@ } +/* Verifies if C type is suitable for copying SQL_BINARY data + http://msdn.microsoft.com/en-us/library/ms713559%28VS.85%29.aspx */ +my_bool is_binary_ctype( SQLSMALLINT cType) +{ + return (cType == SQL_C_CHAR + || cType == SQL_C_BINARY + || cType == SQL_C_WCHAR); +} + + +/* Converts binary(currently used for bit field only) to long long number.*/ +void binary2numeric(long long *dst, char *src, uint srcLen) +{ + *dst= 0; + + while (srcLen) + { + /* if source binary data is longer than 8 bytes(size of long long) + we consider only minor 8 bytes */ + if (srcLen > sizeof(long long)) + continue; + *dst+= (0xff & *src++) << (--srcLen)*8; + } +} + + +/* Function that verifies if conversion from given sql type to c type supported. + Based on http://msdn.microsoft.com/en-us/library/ms709280%28VS.85%29.aspx + and underlying pages. + Currently checks conversions for MySQL BIT(n) field(SQL_BIT or SQL_BINARY) +*/ +my_bool odbc_supported_conversion(SQLSMALLINT sqlType, SQLSMALLINT cType) +{ + switch (sqlType) + { + case SQL_BIT: + { + switch (cType) + { + case SQL_C_DATE: + case SQL_C_TYPE_DATE: + case SQL_C_TIME: + case SQL_C_TYPE_TIME: + case SQL_C_TIMESTAMP: + case SQL_C_TYPE_TIMESTAMP: + return FALSE; + } + } + case SQL_BINARY: + { + return is_binary_ctype(cType); + } + } + + return TRUE; +} + + + /* Conversion supported by driver as exception to odbc specs + (i.e. to odbc_supported_conversion() results). + e.g. we map bit(n>1) to SQL_BINARY, but provide its conversion to numeric + types */ + my_bool driver_supported_conversion(MYSQL_FIELD * field, SQLSMALLINT cType) + { + switch(field->type) + { + case MYSQL_TYPE_BIT: + { + switch (cType) + { + case SQL_C_BIT: + case SQL_C_TINYINT: + case SQL_C_STINYINT: + case SQL_C_UTINYINT: + case SQL_C_SHORT: + case SQL_C_SSHORT: + case SQL_C_USHORT: + case SQL_C_LONG: + case SQL_C_SLONG: + case SQL_C_ULONG: + case SQL_C_FLOAT: + case SQL_C_DOUBLE: + case SQL_C_SBIGINT: + case SQL_C_UBIGINT: + return TRUE; + + /* SQL_BIT should be converted SQL_C_NUMERIC, while SQL_BINARY should not + Tempted not convert to it at all */ + case SQL_C_NUMERIC: + return TRUE; + } + } + } + + return FALSE; + } + + /** Retrieve the data from a field as a specified ODBC C type. @@ -65,7 +163,9 @@ SQLPOINTER rgbValue, SQLLEN cbValueMax, SQLLEN *pcbValue, char *value, uint length, DESCREC *arrec) { - SQLLEN tmp; + SQLLEN tmp; + long long numericValue; + my_bool convert= 1; /* get the exact type if we don't already have it */ if (fCType == SQL_C_DEFAULT) @@ -108,9 +208,29 @@ } else { + if (!odbc_supported_conversion(get_sql_data_type(stmt, field, 0), fCType) + && !driver_supported_conversion(field,fCType)) + { + return set_stmt_error(stmt, "07009", "Conversion is not possible", 0); + } + if (!pcbValue) pcbValue= &tmp; /* Easier code */ + if (field->type == MYSQL_TYPE_BIT) + { + if (is_binary_ctype(fCType)) + { + return copy_binary_result(stmt, (SQLCHAR *)rgbValue, cbValueMax + , pcbValue, field , value , length); + } + else + { + binary2numeric(&numericValue, value, length); + convert= 0; + } + } + switch (fCType) { case SQL_C_CHAR: /* Handle BLOB -> CHAR conversion */ @@ -176,6 +296,7 @@ length= 19; } + /* Looks like this if and its "else" part are not needed here */ if (fCType == SQL_C_BINARY) return copy_binary_result(stmt, (SQLCHAR *)rgbValue, cbValueMax, pcbValue, field, value, length); @@ -193,10 +314,12 @@ case SQL_C_BIT: if (rgbValue) { - if (value[0] && value[0] != '0') - *((char *)rgbValue)= 1; + /* for MySQL bit(n>1) 1st byte may be '\0'. So testing already converted + to a number value or atoi for other types. */ + if (!convert) + *((char *)rgbValue)= numericValue > 0 ? '\1' : '\0'; else - *((char *)rgbValue)= 0; + *((char *)rgbValue)= atoi(value) > 0 ? '\1' : '\0'; } *pcbValue= 1; break; @@ -204,26 +327,34 @@ case SQL_C_TINYINT: case SQL_C_STINYINT: if (rgbValue) - *((SQLSCHAR *)rgbValue)= (SQLSCHAR)atoi(value); + *((SQLSCHAR *)rgbValue)= (SQLSCHAR)(convert + ? atoi(value) + : (numericValue & (SQLSCHAR)(-1))); *pcbValue= 1; break; case SQL_C_UTINYINT: if (rgbValue) - *((SQLCHAR *)rgbValue)= (SQLCHAR)(unsigned int)atoi(value); + *((SQLCHAR *)rgbValue)= (SQLCHAR)(unsigned int)(convert + ? atoi(value) + : (numericValue & (SQLCHAR)(-1))); *pcbValue= 1; break; case SQL_C_SHORT: case SQL_C_SSHORT: if (rgbValue) - *((SQLSMALLINT *)rgbValue)= (SQLSMALLINT)atoi(value); + *((SQLSMALLINT *)rgbValue)= (SQLSMALLINT)(convert + ? atoi(value) + : (numericValue & (SQLUSMALLINT)(-1))); *pcbValue= sizeof(SQLSMALLINT); break; case SQL_C_USHORT: if (rgbValue) - *((SQLUSMALLINT *)rgbValue)= (SQLUSMALLINT)(uint)atol(value); + *((SQLUSMALLINT *)rgbValue)= (SQLUSMALLINT)(uint)(convert + ? atol(value) + : (numericValue & (SQLUSMALLINT)(-1))); *pcbValue= sizeof(SQLUSMALLINT); break; @@ -232,34 +363,42 @@ if (rgbValue) { /* Check if it could be a date...... :) */ - if (length >= 10 && value[4] == '-' && value[7] == '-' && - (!value[10] || value[10] == ' ')) - { - *((SQLINTEGER *)rgbValue)= ((SQLINTEGER) atol(value) * 10000L + - (SQLINTEGER) atol(value + 5) * 100L + - (SQLINTEGER) atol(value + 8)); - } + if (convert) + if (length >= 10 && value[4] == '-' && value[7] == '-' && + (!value[10] || value[10] == ' ')) + { + *((SQLINTEGER *)rgbValue)= ((SQLINTEGER) atol(value) * 10000L + + (SQLINTEGER) atol(value + 5) * 100L + + (SQLINTEGER) atol(value + 8)); + } + else + *((SQLINTEGER *)rgbValue)= (SQLINTEGER) atol(value); else - *((SQLINTEGER *)rgbValue)= (SQLINTEGER) atol(value); + *((SQLINTEGER *)rgbValue)= (SQLINTEGER)(numericValue + & (SQLUINTEGER)(-1)); } *pcbValue= sizeof(SQLINTEGER); break; case SQL_C_ULONG: if (rgbValue) - *((SQLUINTEGER *)rgbValue)= (SQLUINTEGER)strtoul(value, NULL, 10); + *((SQLUINTEGER *)rgbValue)= (SQLUINTEGER)(convert + ? strtoul(value, NULL, 10) + : numericValue & (SQLUINTEGER)(-1)); *pcbValue= sizeof(SQLUINTEGER); break; case SQL_C_FLOAT: if (rgbValue) - *((float *)rgbValue)= (float)atof(value); + *((float *)rgbValue)= (float)(convert ? atof(value) + : numericValue & (int)(-1)); *pcbValue= sizeof(float); break; case SQL_C_DOUBLE: if (rgbValue) - *((double *)rgbValue)= (double)strtod(value, NULL); + *((double *)rgbValue)= (double)(convert ? strtod(value, NULL) + : numericValue); *pcbValue= sizeof(double); break; @@ -369,14 +508,17 @@ case SQL_C_SBIGINT: /** @todo This is not right. SQLBIGINT is not always longlong. */ if (rgbValue) - *((longlong *)rgbValue)= (longlong)strtoll(value, NULL, 10); + *((longlong *)rgbValue)= (longlong)(convert ? strtoll(value, NULL, 10) + : numericValue); *pcbValue= sizeof(longlong); break; case SQL_C_UBIGINT: /** @todo This is not right. SQLUBIGINT is not always ulonglong. */ if (rgbValue) - *((ulonglong *)rgbValue)= (ulonglong)strtoull(value, NULL, 10); + *((ulonglong *)rgbValue)= (ulonglong)(convert + ? strtoull(value, NULL, 10) + : numericValue); *pcbValue= sizeof(ulonglong); break; @@ -385,7 +527,21 @@ int overflow= 0; SQL_NUMERIC_STRUCT *sqlnum= (SQL_NUMERIC_STRUCT *) rgbValue; if (rgbValue) - sqlnum_from_str(value, sqlnum, &overflow); + { + if (convert) + sqlnum_from_str(value, sqlnum, &overflow); + else /* bit field */ + { + /* Lazy way - converting number we have to a string. + If it couldn't happen we have to scale/unscale number - we would + just reverse binary data */ + char _value[21]; /* max string length of 64bit number */ + sprintf(_value, "%llu", numericValue); + + sqlnum_from_str(_value, sqlnum, &overflow); + } + + } *pcbValue= sizeof(ulonglong); if (overflow) return set_stmt_error(stmt, "22003", === modified file 'test/my_result.c' --- test/my_result.c 2009-10-19 20:13:39 +0000 +++ test/my_result.c 2009-12-01 16:56:56 +0000 @@ -2493,6 +2493,94 @@ } +/* +Bug#32821(it might be duplicate though): Wrong value if bit field is bound to +other than SQL_C_BIT variable +*/ +DECLARE_TEST(t_bug32821) +{ + SQLRETURN rc; + SQLUINTEGER b; + SQLUSMALLINT c; + SQLINTEGER a_ind, b_ind, c_ind, i, j, k; + unsigned char a; + + SQL_NUMERIC_STRUCT b_numeric; + + SQLUINTEGER par= sizeof(SQLUSMALLINT)*8+1; + SQLUINTEGER beoyndShortBit= 1<<(par-1); + SQLINTEGER sPar= sizeof(SQLUINTEGER); + + /* 131071 = 0x1ffff - all 1 for field c*/ + SQLCHAR * insStmt= "insert into t_bug32821 values (0,0,0),(1,1,1)\ + ,(1,255,131071),(1,258,?)"; + const unsigned char expected_a[]= {'\0', '\1', '\1', '\1'}; + const SQLUINTEGER expected_b[]= {0L, 1L, 255L, 258L}; + const SQLUSMALLINT expected_c[]= {0, 1, 65535, 0}; + + ok_sql(hstmt, "DROP TABLE IF EXISTS t_bug32821"); + + ok_stmt(hstmt, SQLPrepare(hstmt, "CREATE TABLE t_bug32821 (a BIT(1), b BIT(16)\ + , c BIT(?))", SQL_NTS)); + ok_stmt(hstmt, SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_ULONG + , SQL_INTEGER, 0, 0, &par, 0, &sPar )); + ok_stmt(hstmt, SQLExecute(hstmt)); + + ok_stmt(hstmt, SQLFreeStmt(hstmt, SQL_CLOSE)); + + ok_stmt(hstmt, SQLPrepare(hstmt, insStmt, SQL_NTS)); + ok_stmt(hstmt, SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_ULONG + , SQL_INTEGER, 0, 0, &beoyndShortBit, 0 + , &sPar )); + ok_stmt(hstmt, SQLExecute(hstmt)); + + ok_stmt(hstmt, SQLFreeStmt(hstmt, SQL_CLOSE)); + + ok_sql(hstmt, "SELECT a,b,c FROM t_bug32821"); + + ok_stmt( hstmt, SQLBindCol( hstmt, 1, SQL_C_BIT, &a, 0, &a_ind ) ); + ok_stmt( hstmt, SQLBindCol( hstmt, 2, SQL_C_ULONG, &b, 0, &b_ind ) ); + /*ok_stmt( hstmt, SQLBindCol( hstmt, 1, SQL_C_TYPE_DATE, &d, 0, &b_ind ) );*/ + ok_stmt( hstmt, SQLBindCol( hstmt, 3, SQL_C_USHORT, &c, 0, &c_ind ) ); + + i= 0; + while( (rc= SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 0)) != SQL_NO_DATA_FOUND) + { + /*printMessage("testing row #%d", i+1);*/ + is_num(a, expected_a[i]); + is_num(b, expected_b[i]); + is_num(c, expected_c[i]); + + /* Test of binding to numeric - added later so a bit messy */ + for (k= 1; k < 3; ++k) + { + b_ind= sizeof(SQL_NUMERIC_STRUCT); + SQLGetData(hstmt, (SQLUSMALLINT)k, SQL_C_NUMERIC, &b_numeric, 0, &b_ind); + + b= 0; + for(j= 0; j < b_numeric.precision; ++j) + { + b+= (0xff & b_numeric.val[j]) << 8*j; + } + + switch (k) + { + case 1: is_num(b, expected_a[i]); break; + case 2: is_num(b, expected_b[i]); break; + } + + } + + ++i; + } + + ok_stmt(hstmt, SQLFreeStmt(hstmt, SQL_CLOSE)); + + ok_sql(hstmt, "DROP TABLE IF EXISTS t_bug32821"); + return OK; +} + + BEGIN_TESTS ADD_TEST(my_resultset) ADD_TEST(t_convert_type) @@ -2532,6 +2620,7 @@ ADD_TEST(t_bug36069) ADD_TEST(t_bug41942) ADD_TEST(t_bug39644) + ADD_TEST(t_bug32821) END_TESTS