diff options
author | Elizabeth Marie Smith <auroraeosrose@php.net> | 2008-03-13 16:55:09 +0000 |
---|---|---|
committer | Elizabeth Marie Smith <auroraeosrose@php.net> | 2008-03-13 16:55:09 +0000 |
commit | 77e914e6dcb511603cd3544ac2251fc1503a982e (patch) | |
tree | d1e8d3d42a398683023d7f6c1d7b776e7ceb8579 /ext/pdo_odbc/odbc_stmt.c | |
parent | 5ea77bc64f5e79e0aa2a2944bd25f391a32a9fd9 (diff) | |
download | php-git-77e914e6dcb511603cd3544ac2251fc1503a982e.tar.gz |
[DOC] adds new odbc driver specific ODBC_ATTR_ASSUME_UTF8 to deal with unicode coming out of Microsoft SQL Server and is only relevant on windows, use it if unicode output from the server is garbled and it will autoconvert text - original patch by wez
Diffstat (limited to 'ext/pdo_odbc/odbc_stmt.c')
-rwxr-xr-x | ext/pdo_odbc/odbc_stmt.c | 243 |
1 files changed, 221 insertions, 22 deletions
diff --git a/ext/pdo_odbc/odbc_stmt.c b/ext/pdo_odbc/odbc_stmt.c index ccee63062e..f8042c529f 100755 --- a/ext/pdo_odbc/odbc_stmt.c +++ b/ext/pdo_odbc/odbc_stmt.c @@ -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 *stmt TSRMLS_DC) } free_cols(stmt, S TSRMLS_CC); - + if (S->convbuf) { + efree(S->convbuf); + } efree(S); return 1; @@ -79,18 +174,44 @@ static int odbc_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC) 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 assume 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 +334,12 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p 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 +352,10 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p 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 +439,21 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p } 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,17 +463,36 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p case PDO_PARAM_EVT_EXEC_POST: P = param->driver_data; if (P->outbuf) { - switch (P->len) { - case SQL_NULL_DATA: - zval_dtor(param->parameter); - ZVAL_NULL(param->parameter); - 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'; + if (P->outbuf) { + unsigned long ulen; + char *srcbuf; + unsigned long srclen; + + switch (P->len) { + case SQL_NULL_DATA: + zval_dtor(param->parameter); + ZVAL_NULL(param->parameter); + break; + default: + convert_to_string(param->parameter); + 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; @@ -342,9 +504,9 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p static int odbc_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, long offset TSRMLS_DC) { - pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; RETCODE rc; SQLSMALLINT odbcori; + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; switch (ori) { case PDO_FETCH_ORI_NEXT: odbcori = SQL_FETCH_NEXT; break; @@ -412,6 +574,7 @@ static int odbc_stmt_describe(pdo_stmt_t *stmt, int colno TSRMLS_DC) 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; @@ -423,8 +586,10 @@ static int odbc_stmt_describe(pdo_stmt_t *stmt, int colno TSRMLS_DC) S->cols[colno].data = emalloc(colsize+1); S->cols[colno].is_long = 0; - rc = SQLBindCol(S->stmt, colno+1, SQL_C_CHAR, S->cols[colno].data, - S->cols[colno].datalen+1, &S->cols[colno].fetched_len); + 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) { pdo_odbc_stmt_error("SQLBindCol"); @@ -445,6 +610,7 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, unsigned l { 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->is_long) { @@ -457,8 +623,8 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, unsigned l * 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, - 256, &C->fetched_len); + 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; @@ -520,6 +686,9 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, unsigned l *ptr = buf; *caller_frees = 1; *len = used; + if (C->is_unicode) { + goto unicode_conv; + } return 1; } @@ -540,6 +709,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? */ @@ -547,6 +719,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) @@ -565,6 +757,9 @@ static int odbc_stmt_set_param(pdo_stmt_t *stmt, long attr, zval *val TSRMLS_DC) 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"; @@ -593,6 +788,10 @@ static int odbc_stmt_get_attr(pdo_stmt_t *stmt, long attr, zval *val TSRMLS_DC) 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"; |