]> granicus.if.org Git - php/commitdiff
[DOC] adds new odbc driver specific ODBC_ATTR_ASSUME_UTF8 to deal with unicode coming...
authorElizabeth Marie Smith <auroraeosrose@php.net>
Thu, 13 Mar 2008 16:55:09 +0000 (16:55 +0000)
committerElizabeth Marie Smith <auroraeosrose@php.net>
Thu, 13 Mar 2008 16:55:09 +0000 (16:55 +0000)
ext/pdo_odbc/odbc_driver.c
ext/pdo_odbc/odbc_stmt.c
ext/pdo_odbc/pdo_odbc.c
ext/pdo_odbc/php_pdo_odbc_int.h

index a0c1bc748e1d8bfe75480fbaf1e29a4f7f40a619..8c7ca0b246553b95af7847ceab3592dbcd109597 100755 (executable)
@@ -153,6 +153,7 @@ static int odbc_handle_preparer(pdo_dbh_t *dbh, const char *sql, long sql_len, p
        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 */
@@ -335,8 +336,24 @@ static int odbc_handle_rollback(pdo_dbh_t *dbh TSRMLS_DC)
        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);
@@ -348,6 +365,9 @@ static int odbc_handle_get_attr(pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC)
                case PDO_ATTR_SERVER_INFO:
                case PDO_ATTR_CONNECTION_STATUS:
                        break;
+               case PDO_ODBC_ATTR_ASSUME_UTF8:
+                       ZVAL_BOOL(val, H->assume_utf8 ? 1 : 0);
+                       return 1;
 
        }
        return 0;
@@ -361,7 +381,7 @@ static struct pdo_dbh_methods odbc_methods = {
        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 ccee63062e70160745743aae090e9dd4fa424e39..f8042c529f53d7a483ff68f0d9bfd029c3f46d6e 100755 (executable)
 #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*)&param);
                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, &param->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";
index 299d3a069503a48393d3bed395e4592abe152703..38c305075c6147e3498fa850bfea6a3637ace27a 100755 (executable)
@@ -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 e640a644c8c6db983404b0c3d96ce5061ad03a63..bed5c29f6528527a516b7611efd8f4653e3d274e 100755 (executable)
@@ -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 {
@@ -138,6 +140,8 @@ typedef struct {
        SWORD   coltype;
        char colname[128];
        unsigned is_long;
+       unsigned is_unicode:1;
+       unsigned _spare:31;
 } pdo_odbc_column;
 
 typedef struct {
@@ -145,14 +149,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;
@@ -173,6 +182,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-8 when feeding data to unicode columns */
 };
 
 /*