Any queries through PDO_ODBC that return Unicode data will have an end of string character midway through the string.
Wez created this patch to fix the problem but has not been able to get it into CVS because I seem to always ask him at the wrong time. Could someone take a look at this and apply the patch? I've been using a branched version with this patch for two years and would love to upgrade to the latest version. Thanks, --Travis
Index: odbc_driver.c =================================================================== RCS file: /repository/php-src/ext/pdo_odbc/odbc_driver.c,v retrieving revision 1.27.2.2 diff -u -p -r1.27.2.2 odbc_driver.c --- odbc_driver.c 14 Dec 2005 04:56:22 -0000 1.27.2.2 +++ odbc_driver.c 30 Mar 2006 19:06:44 -0000 @@ -2,7 +2,7 @@ +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ - | Copyright (c) 1997-2005 The PHP Group | + | Copyright (c) 1997-2006 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.0 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | @@ -153,6 +153,7 @@ static int odbc_handle_preparer(pdo_dbh_ int nsql_len = 0; S->H = H; + S->assume_utf8 = H->assume_utf8; /* before we prepare, we need to peek at the query; if it uses named parameters, * we want PDO to rewrite them for us */ @@ -319,8 +320,24 @@ static int odbc_handle_rollback(pdo_dbh_ return 1; } +static int odbc_handle_set_attr(pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + switch (attr) { + case PDO_ODBC_ATTR_ASSUME_UTF8: + H->assume_utf8 = zval_is_true(val); + return 1; + default: + strcpy(H->einfo.last_err_msg, "Unknown Attribute"); + H->einfo.what = "setAttribute"; + strcpy(H->einfo.last_state, "IM0001"); + return -1; + } +} + static int odbc_handle_get_attr(pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; switch (attr) { case PDO_ATTR_CLIENT_VERSION: ZVAL_STRING(val, "ODBC-" PDO_ODBC_TYPE, 1); @@ -333,6 +350,9 @@ static int odbc_handle_get_attr(pdo_dbh_ case PDO_ATTR_CONNECTION_STATUS: break; + case PDO_ODBC_ATTR_ASSUME_UTF8: + ZVAL_BOOL(val, H->assume_utf8 ? 1 : 0); + return 1; } return 0; } @@ -345,7 +365,7 @@ static struct pdo_dbh_methods odbc_metho odbc_handle_begin, odbc_handle_commit, odbc_handle_rollback, - NULL, /* set attr */ + odbc_handle_set_attr, NULL, /* last id */ pdo_odbc_fetch_error_func, odbc_handle_get_attr, /* get attr */ Index: odbc_stmt.c =================================================================== RCS file: /repository/php-src/ext/pdo_odbc/odbc_stmt.c,v retrieving revision 1.26.2.2 diff -u -p -r1.26.2.2 odbc_stmt.c --- odbc_stmt.c 27 Mar 2006 21:04:12 -0000 1.26.2.2 +++ odbc_stmt.c 30 Mar 2006 19:06:44 -0000 @@ -2,7 +2,7 @@ +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ - | Copyright (c) 1997-2005 The PHP Group | + | Copyright (c) 1997-2006 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.0 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | @@ -30,6 +30,99 @@ #include "php_pdo_odbc.h" #include "php_pdo_odbc_int.h" +enum pdo_odbc_conv_result { + PDO_ODBC_CONV_NOT_REQUIRED, + PDO_ODBC_CONV_OK, + PDO_ODBC_CONV_FAIL +}; + +static int pdo_odbc_sqltype_is_unicode(pdo_odbc_stmt *S, SWORD sqltype) +{ + if (!S->assume_utf8) return 0; + switch (sqltype) { +#ifdef SQL_WCHAR + case SQL_WCHAR: + return 1; +#endif +#ifdef SQL_WLONGVARCHAR + case SQL_WLONGVARCHAR: + return 1; +#endif +#ifdef SQL_WVARCHAR + case SQL_WVARCHAR: + return 1; +#endif + default: + return 0; + } +} + +static int pdo_odbc_utf82ucs2(pdo_stmt_t *stmt, int is_unicode, const char *buf, + unsigned long buflen, unsigned long *outlen) +{ +#ifdef PHP_WIN32 + if (is_unicode && buflen) { + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + DWORD ret; + + ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, NULL, 0); + if (ret == 0) { + //printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf); + return PDO_ODBC_CONV_FAIL; + } + + ret *= sizeof(WCHAR); + + if (S->convbufsize <= ret) { + S->convbufsize = ret + sizeof(WCHAR); + S->convbuf = erealloc(S->convbuf, S->convbufsize); + } + + ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, (LPWSTR)S->convbuf, S->convbufsize / sizeof(WCHAR)); + if (ret == 0) { + //printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf); + return PDO_ODBC_CONV_FAIL; + } + + ret *= sizeof(WCHAR); + *outlen = ret; + return PDO_ODBC_CONV_OK; + } +#endif + return PDO_ODBC_CONV_NOT_REQUIRED; +} + +static int pdo_odbc_ucs22utf8(pdo_stmt_t *stmt, int is_unicode, const char *buf, + unsigned long buflen, unsigned long *outlen) +{ +#ifdef PHP_WIN32 + if (is_unicode && buflen) { + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + DWORD ret; + + ret = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)buf, buflen/sizeof(WCHAR), NULL, 0, NULL, NULL); + if (ret == 0) { + return PDO_ODBC_CONV_FAIL; + } + + if (S->convbufsize <= ret) { + S->convbufsize = ret + 1; + S->convbuf = erealloc(S->convbuf, S->convbufsize); + } + + ret = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)buf, buflen/sizeof(WCHAR), S->convbuf, S->convbufsize, NULL, NULL); + if (ret == 0) { + return PDO_ODBC_CONV_FAIL; + } + + *outlen = ret; + S->convbuf[*outlen] = '\0'; + return PDO_ODBC_CONV_OK; + } +#endif + return PDO_ODBC_CONV_NOT_REQUIRED; +} + static void free_cols(pdo_stmt_t *stmt, pdo_odbc_stmt *S TSRMLS_DC) { if (S->cols) { @@ -58,7 +151,9 @@ static int odbc_stmt_dtor(pdo_stmt_t *st } free_cols(stmt, S TSRMLS_CC); - + if (S->convbuf) { + efree(S->convbuf); + } efree(S); return 1; @@ -79,18 +174,45 @@ static int odbc_stmt_execute(pdo_stmt_t while (rc == SQL_NEED_DATA) { struct pdo_bound_param_data *param; + rc = SQLParamData(S->stmt, (SQLPOINTER*)¶m); if (rc == SQL_NEED_DATA) { php_stream *stm; int len; - + pdo_odbc_param *P; + + P = (pdo_odbc_param*)param->driver_data; if (Z_TYPE_P(param->parameter) != IS_RESOURCE) { /* they passed in a string */ + unsigned long ulen; convert_to_string(param->parameter); - SQLPutData(S->stmt, Z_STRVAL_P(param->parameter), Z_STRLEN_P(param->parameter)); + + switch (pdo_odbc_utf82ucs2(stmt, P->is_unicode, + Z_STRVAL_P(param->parameter), + Z_STRLEN_P(param->parameter), + &ulen)) { + case PDO_ODBC_CONV_NOT_REQUIRED: + SQLPutData(S->stmt, Z_STRVAL_P(param->parameter), + Z_STRLEN_P(param->parameter)); + break; + case PDO_ODBC_CONV_OK: + SQLPutData(S->stmt, S->convbuf, ulen); + break; + case PDO_ODBC_CONV_FAIL: + pdo_odbc_stmt_error("error converting input string"); + SQLCloseCursor(S->stmt); + if (buf) { + efree(buf); + } + return 0; + } + continue; } + /* we assumes that LOBs are binary and don't need charset + * conversion */ + php_stream_from_zval_no_verify(stm, ¶m->parameter); if (!stm) { /* shouldn't happen either */ @@ -213,6 +335,12 @@ static int odbc_stmt_param_hook(pdo_stmt P->len = 0; /* is re-populated each EXEC_PRE */ P->outbuf = NULL; + P->is_unicode = pdo_odbc_sqltype_is_unicode(S, sqltype); + if (P->is_unicode) { + /* avoid driver auto-translation: we'll do it ourselves */ + ctype = SQL_C_BINARY; + } + if ((param->param_type & PDO_PARAM_INPUT_OUTPUT) == PDO_PARAM_INPUT_OUTPUT) { P->paramtype = SQL_PARAM_INPUT_OUTPUT; } else if (param->max_value_len <= 0) { @@ -225,7 +353,10 @@ static int odbc_stmt_param_hook(pdo_stmt if (PDO_PARAM_TYPE(param->param_type) != PDO_PARAM_NULL) { /* need an explicit buffer to hold result */ P->len = param->max_value_len > 0 ? param->max_value_len : precision; - P->outbuf = emalloc(P->len + 1); + if (P->is_unicode) { + P->len *= 2; + } + P->outbuf = emalloc(P->len + (P->is_unicode ? 2:1)); } } @@ -309,8 +440,21 @@ static int odbc_stmt_param_hook(pdo_stmt } else { convert_to_string(param->parameter); if (P->outbuf) { - P->len = Z_STRLEN_P(param->parameter); - memcpy(P->outbuf, Z_STRVAL_P(param->parameter), P->len); + unsigned long ulen; + switch (pdo_odbc_utf82ucs2(stmt, P->is_unicode, + Z_STRVAL_P(param->parameter), + Z_STRLEN_P(param->parameter), + &ulen)) { + case PDO_ODBC_CONV_FAIL: + case PDO_ODBC_CONV_NOT_REQUIRED: + P->len = Z_STRLEN_P(param->parameter); + memcpy(P->outbuf, Z_STRVAL_P(param->parameter), P->len); + break; + case PDO_ODBC_CONV_OK: + P->len = ulen; + memcpy(P->outbuf, S->convbuf, P->len); + break; + } } else { P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(param->parameter)); } @@ -320,6 +464,10 @@ static int odbc_stmt_param_hook(pdo_stmt case PDO_PARAM_EVT_EXEC_POST: P = param->driver_data; if (P->outbuf) { + unsigned long ulen; + char *srcbuf; + unsigned long srclen; + switch (P->len) { case SQL_NULL_DATA: zval_dtor(param->parameter); @@ -327,10 +475,23 @@ static int odbc_stmt_param_hook(pdo_stmt break; default: convert_to_string(param->parameter); - Z_STRVAL_P(param->parameter) = erealloc(Z_STRVAL_P(param->parameter), P->len+1); - memcpy(Z_STRVAL_P(param->parameter), P->outbuf, P->len); - Z_STRLEN_P(param->parameter) = P->len; - Z_STRVAL_P(param->parameter)[P->len] = '\0'; + switch (pdo_odbc_ucs22utf8(stmt, P->is_unicode, P->outbuf, P->len, &ulen)) { + case PDO_ODBC_CONV_FAIL: + /* something fishy, but allow it to come back as binary */ + case PDO_ODBC_CONV_NOT_REQUIRED: + srcbuf = P->outbuf; + srclen = P->len; + break; + case PDO_ODBC_CONV_OK: + srcbuf = S->convbuf; + srclen = ulen; + break; + } + + Z_STRVAL_P(param->parameter) = erealloc(Z_STRVAL_P(param->parameter), srclen+1); + memcpy(Z_STRVAL_P(param->parameter), srcbuf, srclen); + Z_STRLEN_P(param->parameter) = srclen; + Z_STRVAL_P(param->parameter)[srclen] = '\0'; } } return 1; @@ -395,6 +556,7 @@ static int odbc_stmt_describe(pdo_stmt_t col->maxlen = S->cols[colno].datalen = colsize; col->namelen = colnamelen; col->name = estrdup(S->cols[colno].colname); + S->cols[colno].is_unicode = pdo_odbc_sqltype_is_unicode(S, S->cols[colno].coltype); /* returning data as a string */ col->param_type = PDO_PARAM_STR; @@ -405,7 +567,9 @@ static int odbc_stmt_describe(pdo_stmt_t if (colsize < 256 && !S->going_long) { S->cols[colno].data = emalloc(colsize+1); - rc = SQLBindCol(S->stmt, colno+1, SQL_C_CHAR, S->cols[colno].data, + rc = SQLBindCol(S->stmt, colno+1, + S->cols[colno].is_unicode ? SQL_C_BINARY : SQL_C_CHAR, + S->cols[colno].data, S->cols[colno].datalen+1, &S->cols[colno].fetched_len); if (rc != SQL_SUCCESS) { @@ -426,6 +590,7 @@ static int odbc_stmt_get_col(pdo_stmt_t { pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; pdo_odbc_column *C = &S->cols[colno]; + unsigned long ulen; /* if it is a column containing "long" data, perform late binding now */ if (C->datalen > 255) { @@ -438,9 +603,8 @@ static int odbc_stmt_get_col(pdo_stmt_t * of 256 bytes; if there is more to be had, we then allocate * bigger buffer for the caller to free */ - rc = SQLGetData(S->stmt, colno+1, SQL_C_CHAR, C->data, + rc = SQLGetData(S->stmt, colno+1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, C->data, 256, &C->fetched_len); - if (rc == SQL_SUCCESS) { /* all the data fit into our little buffer; * jump down to the generic bound data case */ @@ -491,6 +655,9 @@ static int odbc_stmt_get_col(pdo_stmt_t *ptr = buf; *caller_frees = 1; *len = used; + if (C->is_unicode) { + goto unicode_conv; + } return 1; } @@ -511,6 +678,9 @@ in_data: /* it was stored perfectly */ *ptr = C->data; *len = C->fetched_len; + if (C->is_unicode) { + goto unicode_conv; + } return 1; } else { /* no data? */ @@ -518,6 +688,26 @@ in_data: *len = 0; return 1; } + +unicode_conv: + switch (pdo_odbc_ucs22utf8(stmt, C->is_unicode, *ptr, *len, &ulen)) { + case PDO_ODBC_CONV_FAIL: + /* oh well. They can have the binary version of it */ + case PDO_ODBC_CONV_NOT_REQUIRED: + /* shouldn't happen... */ + return 1; + + case PDO_ODBC_CONV_OK: + if (*caller_frees) { + efree(*ptr); + } + *ptr = emalloc(ulen + 1); + *len = ulen; + memcpy(*ptr, S->convbuf, ulen+1); + *caller_frees = 1; + return 1; + } + return 1; } static int odbc_stmt_set_param(pdo_stmt_t *stmt, long attr, zval *val TSRMLS_DC) @@ -536,6 +726,10 @@ static int odbc_stmt_set_param(pdo_stmt_ pdo_odbc_stmt_error("SQLSetCursorName"); return 0; + case PDO_ODBC_ATTR_ASSUME_UTF8: + S->assume_utf8 = zval_is_true(val); + return 0; + default: strcpy(S->einfo.last_err_msg, "Unknown Attribute"); S->einfo.what = "setAttribute"; @@ -564,6 +758,10 @@ static int odbc_stmt_get_attr(pdo_stmt_t return 0; } + case PDO_ODBC_ATTR_ASSUME_UTF8: + ZVAL_BOOL(val, S->assume_utf8 ? 1 : 0); + return 0; + default: strcpy(S->einfo.last_err_msg, "Unknown Attribute"); S->einfo.what = "getAttribute"; Index: pdo_odbc.c =================================================================== RCS file: /repository/php-src/ext/pdo_odbc/pdo_odbc.c,v retrieving revision 1.14.2.7 diff -u -p -r1.14.2.7 pdo_odbc.c --- pdo_odbc.c 4 Dec 2005 22:34:26 -0000 1.14.2.7 +++ pdo_odbc.c 30 Mar 2006 19:06:44 -0000 @@ -2,7 +2,7 @@ +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ - | Copyright (c) 1997-2005 The PHP Group | + | Copyright (c) 1997-2006 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.0 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | @@ -134,6 +134,7 @@ PHP_MINIT_FUNCTION(pdo_odbc) #endif REGISTER_PDO_CLASS_CONST_LONG("ODBC_ATTR_USE_CURSOR_LIBRARY", PDO_ODBC_ATTR_USE_CURSOR_LIBRARY); + REGISTER_PDO_CLASS_CONST_LONG("ODBC_ATTR_ASSUME_UTF8", PDO_ODBC_ATTR_ASSUME_UTF8); REGISTER_PDO_CLASS_CONST_LONG("ODBC_SQL_USE_IF_NEEDED", SQL_CUR_USE_IF_NEEDED); REGISTER_PDO_CLASS_CONST_LONG("ODBC_SQL_USE_DRIVER", SQL_CUR_USE_DRIVER); REGISTER_PDO_CLASS_CONST_LONG("ODBC_SQL_USE_ODBC", SQL_CUR_USE_ODBC); Index: php_pdo_odbc.h =================================================================== RCS file: /repository/php-src/ext/pdo_odbc/php_pdo_odbc.h,v retrieving revision 1.2 diff -u -p -r1.2 php_pdo_odbc.h --- php_pdo_odbc.h 7 Jul 2005 12:49:21 -0000 1.2 +++ php_pdo_odbc.h 30 Mar 2006 19:06:44 -0000 @@ -2,7 +2,7 @@ +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ - | Copyright (c) 1997-2005 The PHP Group | + | Copyright (c) 1997-2006 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.0 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | Index: php_pdo_odbc_int.h =================================================================== RCS file: /repository/php-src/ext/pdo_odbc/php_pdo_odbc_int.h,v retrieving revision 1.9.2.1 diff -u -p -r1.9.2.1 php_pdo_odbc_int.h --- php_pdo_odbc_int.h 27 Mar 2006 21:04:12 -0000 1.9.2.1 +++ php_pdo_odbc_int.h 30 Mar 2006 19:06:44 -0000 @@ -2,7 +2,7 @@ +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ - | Copyright (c) 1997-2005 The PHP Group | + | Copyright (c) 1997-2006 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.0 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | @@ -129,6 +129,8 @@ typedef struct { PDO_ODBC_HENV env; PDO_ODBC_HDBC dbc; pdo_odbc_errinfo einfo; + unsigned assume_utf8:1; + unsigned _spare:31; } pdo_odbc_db_handle; typedef struct { @@ -136,6 +138,8 @@ typedef struct { unsigned long datalen; long fetched_len; SWORD coltype; + unsigned is_unicode:1; + unsigned _spare:31; char colname[128]; } pdo_odbc_column; @@ -144,14 +148,19 @@ typedef struct { pdo_odbc_column *cols; pdo_odbc_db_handle *H; pdo_odbc_errinfo einfo; + char *convbuf; + unsigned long convbufsize; unsigned going_long:1; - unsigned _spare:31; + unsigned assume_utf8:1; + unsigned _spare:30; } pdo_odbc_stmt; typedef struct { SQLINTEGER len; SQLSMALLINT paramtype; char *outbuf; + unsigned is_unicode:1; + unsigned _spare:31; } pdo_odbc_param; extern pdo_driver_t pdo_odbc_driver; @@ -172,6 +181,7 @@ extern SQLUINTEGER pdo_odbc_pool_mode; enum { PDO_ODBC_ATTR_USE_CURSOR_LIBRARY = PDO_ATTR_DRIVER_SPECIFIC, + PDO_ODBC_ATTR_ASSUME_UTF8 /* assume that input strings are UTF when feeding data to unicode columns */ }; /*
-- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: http://www.php.net/unsub.php