]> granicus.if.org Git - php/commitdiff
- Don't modify the variables which are passed for parameter binding.
authorAndrey Hristov <andrey@php.net>
Thu, 20 Mar 2008 14:03:30 +0000 (14:03 +0000)
committerAndrey Hristov <andrey@php.net>
Thu, 20 Mar 2008 14:03:30 +0000 (14:03 +0000)
  We need to clone them, if there will be a transformation (convert_to_xxx)
  which will change the origin
  (bug#44390 bind_param / bind_result and Object member variables)
- Make mysqlnd more compatible to libmysql, in this case if the execute of
  a statement fails set the state of the statement back to PREPARED
- A test case to check the case of a failing statement.

ext/mysqli/mysqli_api.c
ext/mysqli/tests/mysqli_stmt_datatype_change.phpt [new file with mode: 0644]
ext/mysqlnd/mysqlnd.c
ext/mysqlnd/mysqlnd_ps.c
ext/mysqlnd/mysqlnd_ps_codec.c

index 5f0af971f84e2cba1cacd4ceea7e1efd8f049052..84daabf40b81005880aad7842f04c1bc1de6e6f8 100644 (file)
@@ -689,6 +689,22 @@ PHP_FUNCTION(mysqli_error)
 }
 /* }}} */
 
+#ifndef MYSQLI_USE_MYSQLND
+/* {{{ php_mysqli_stmt_copy_it */
+static void
+php_mysqli_stmt_copy_it(zval *** copies, zval *original, uint param_count, uint current)
+{
+       if (!*copies) {
+               *copies = ecalloc(param_count, sizeof(zval *));                                 
+       }
+       MAKE_STD_ZVAL((*copies)[current]);
+       *(*copies)[current] = *original;
+       Z_SET_REFCOUNT_P((*copies)[current], 1);
+       zval_copy_ctor((*copies)[current]);
+}
+/* }}} */
+#endif
+
 /* {{{ proto bool mysqli_stmt_execute(object stmt)
    Execute a prepared statement */
 PHP_FUNCTION(mysqli_stmt_execute)
@@ -697,6 +713,7 @@ PHP_FUNCTION(mysqli_stmt_execute)
        zval            *mysql_stmt;
 #ifndef MYSQLI_USE_MYSQLND
        unsigned int    i;
+       zval            **copies = NULL;
 #endif
 
        if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) {
@@ -705,23 +722,48 @@ PHP_FUNCTION(mysqli_stmt_execute)
        MYSQLI_FETCH_RESOURCE(stmt, MY_STMT *, &mysql_stmt, "mysqli_stmt", MYSQLI_STATUS_VALID);
 
 #ifndef MYSQLI_USE_MYSQLND
+       if (stmt->param.var_cnt) {
+               int j;
+               for (i = 0; i < stmt->param.var_cnt; i++) {
+                       for (j = i + 1; j < stmt->param.var_cnt; j++) {
+                               /* Oops, someone binding the same variable - clone */
+                               if (stmt->param.vars[j] == stmt->param.vars[i]) {
+                                       php_mysqli_stmt_copy_it(&copies, stmt->param.vars[i], stmt->param.var_cnt, i);
+                                       break;
+                               }
+                       }
+               }
+       }
        for (i = 0; i < stmt->param.var_cnt; i++) {
                if (stmt->param.vars[i]) {
                        if ( !(stmt->param.is_null[i] = (stmt->param.vars[i]->type == IS_NULL)) ) {
+                               zval *the_var = copies && copies[i]? copies[i]:stmt->param.vars[i];
                                switch (stmt->stmt->params[i].buffer_type) {
                                        case MYSQL_TYPE_VAR_STRING:
-                                               convert_to_string_ex(&stmt->param.vars[i]);
-                                               stmt->stmt->params[i].buffer = Z_STRVAL_PP(&stmt->param.vars[i]);
-                                               stmt->stmt->params[i].buffer_length = Z_STRLEN_PP(&stmt->param.vars[i]);
+                                               if (the_var == stmt->param.vars[i] && Z_TYPE_P(stmt->param.vars[i]) != IS_STRING) {
+                                                       php_mysqli_stmt_copy_it(&copies, stmt->param.vars[i], stmt->param.var_cnt, i);
+                                                       the_var = copies[i];
+                                               }
+                                               convert_to_string_ex(&the_var);
+                                               stmt->stmt->params[i].buffer = Z_STRVAL_P(the_var);
+                                               stmt->stmt->params[i].buffer_length = Z_STRLEN_P(the_var);
                                                break;
                                        case MYSQL_TYPE_DOUBLE:
-                                               convert_to_double_ex(&stmt->param.vars[i]);
-                                               stmt->stmt->params[i].buffer = &Z_LVAL_PP(&stmt->param.vars[i]);
+                                               if (the_var == stmt->param.vars[i] && Z_TYPE_P(stmt->param.vars[i]) != IS_DOUBLE) {
+                                                       php_mysqli_stmt_copy_it(&copies, stmt->param.vars[i], stmt->param.var_cnt, i);
+                                                       the_var = copies[i];
+                                               }
+                                               convert_to_double_ex(&the_var);
+                                               stmt->stmt->params[i].buffer = &Z_DVAL_P(the_var);
                                                break;
                                        case MYSQL_TYPE_LONGLONG:
                                        case MYSQL_TYPE_LONG:
-                                               convert_to_long_ex(&stmt->param.vars[i]);
-                                               stmt->stmt->params[i].buffer = &Z_LVAL_PP(&stmt->param.vars[i]);
+                                               if (the_var == stmt->param.vars[i] && Z_TYPE_P(stmt->param.vars[i]) != IS_LONG) {
+                                                       php_mysqli_stmt_copy_it(&copies, stmt->param.vars[i], stmt->param.var_cnt, i);
+                                                       the_var = copies[i];
+                                               }
+                                               convert_to_long_ex(&the_var);
+                                               stmt->stmt->params[i].buffer = &Z_LVAL_P(the_var);
                                                break;
                                        default:
                                                break;
@@ -738,6 +780,17 @@ PHP_FUNCTION(mysqli_stmt_execute)
                RETVAL_TRUE;
        }
 
+#ifndef MYSQLI_USE_MYSQLND
+       if (copies) {
+               for (i = 0; i < stmt->param.var_cnt; i++) {
+                       if (copies[i]) {
+                               zval_ptr_dtor(&copies[i]);
+                       }
+               }
+               efree(copies);
+       }
+#endif
+
        if (MyG(report_mode) & MYSQLI_REPORT_INDEX) {
                php_mysqli_report_index(stmt->query, mysqli_stmt_server_status(stmt->stmt) TSRMLS_CC);
        }
diff --git a/ext/mysqli/tests/mysqli_stmt_datatype_change.phpt b/ext/mysqli/tests/mysqli_stmt_datatype_change.phpt
new file mode 100644 (file)
index 0000000..75153cc
--- /dev/null
@@ -0,0 +1,151 @@
+--TEST--
+mysqli_stmt_bind_param() - playing with references
+--SKIPIF--
+<?php
+require_once('skipif.inc');
+require_once('skipifemb.inc');
+require_once('skipifconnectfailure.inc');
+?>
+--FILE--
+<?php
+       include "connect.inc";
+       require('table.inc');
+
+       if (!$c1 = mysqli_connect($host, $user, $passwd, $db, $port, $socket)) {
+               printf("Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n",
+                       $host, $user, $db, $port, $socket);
+               exit(1);
+       }
+       if (!$c2 = mysqli_connect($host, $user, $passwd, $db, $port, $socket)) {
+               printf("Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n",
+                       $host, $user, $db, $port, $socket);
+               exit(1);
+       }
+
+       $c1->query("use $db");
+       $c2->query("use $db");
+       $c1->query("drop table if exists type_change");
+       $c1->query("create table type_change(a int, b char(10))");
+       $c1->query("insert into type_change values (1, 'one'), (2, 'two')"); 
+       $s1 = $c1->prepare("select a from type_change order by a");
+       var_dump($s1);
+       var_dump($s1->execute(), $s1->bind_result($col1));
+       echo "---- Row 1\n";
+       var_dump($s1->fetch());
+       var_dump($col1);
+       echo "---- Row 2\n";
+       var_dump($s1->fetch());
+       var_dump($col1);
+       echo "---- Row 3\n";
+       var_dump($s1->fetch());
+       echo "----\n";
+
+       echo "ALTER\n";
+       var_dump($c2->query("alter table type_change drop a"));
+       var_dump($s1->execute());
+       var_dump($c1->error);
+
+       echo "---- Row 1\n";
+       var_dump($s1->fetch());
+       var_dump($col1);
+       echo "---- Row 2\n";
+       var_dump($s1->fetch());
+       var_dump($col1);
+       echo "---- Row 3\n";
+       var_dump($s1->fetch());
+       echo "----\n";
+
+       echo "done!";
+?>
+--EXPECTF--
+object(mysqli_stmt)#%d (%d) {
+  ["affected_rows"]=>
+  int(0)
+  ["insert_id"]=>
+  int(0)
+  ["num_rows"]=>
+  int(0)
+  ["param_count"]=>
+  int(0)
+  ["field_count"]=>
+  int(1)
+  ["errno"]=>
+  int(0)
+  ["error"]=>
+  string(0) ""
+  ["sqlstate"]=>
+  string(5) "00000"
+  ["id"]=>
+  int(1)
+}
+bool(true)
+bool(true)
+---- Row 1
+bool(true)
+int(1)
+---- Row 2
+bool(true)
+int(2)
+---- Row 3
+NULL
+----
+ALTER
+bool(true)
+bool(false)
+string(34) "Unknown column 'a' in 'field list'"
+---- Row 1
+bool(false)
+int(2)
+---- Row 2
+bool(false)
+int(2)
+---- Row 3
+bool(false)
+----
+done!
+--UEXPECTF--
+object(mysqli_stmt)#%d (%d) {
+  [u"affected_rows"]=>
+  int(0)
+  [u"insert_id"]=>
+  int(0)
+  [u"num_rows"]=>
+  int(0)
+  [u"param_count"]=>
+  int(0)
+  [u"field_count"]=>
+  int(1)
+  [u"errno"]=>
+  int(0)
+  [u"error"]=>
+  unicode(0) ""
+  [u"sqlstate"]=>
+  unicode(5) "00000"
+  [u"id"]=>
+  int(1)
+}
+bool(true)
+bool(true)
+---- Row 1
+bool(true)
+int(1)
+---- Row 2
+bool(true)
+int(2)
+---- Row 3
+NULL
+----
+ALTER
+bool(true)
+bool(false)
+unicode(34) "Unknown column 'a' in 'field list'"
+---- Row 1
+bool(false)
+int(2)
+---- Row 2
+bool(false)
+int(2)
+---- Row 3
+bool(false)
+----
+done!
index b3ac202441fcc167442a11b8a8a2780d79fd6b04..82702254bc297c834a04889a9699a5888176144c 100644 (file)
@@ -591,6 +591,7 @@ PHPAPI MYSQLND *mysqlnd_connect(MYSQLND *conn,
                if (hashed_details) {
                        mnd_efree(hashed_details);
                }
+               errcode = CR_CONNECTION_ERROR;
                goto err;
        }
 
@@ -748,7 +749,7 @@ PHPAPI MYSQLND *mysqlnd_connect(MYSQLND *conn,
                conn->net.cmd_buffer.length = 128L*1024L;
                conn->net.cmd_buffer.buffer = mnd_pemalloc(conn->net.cmd_buffer.length, conn->persistent);
 
-        mysqlnd_local_infile_default(conn);
+               mysqlnd_local_infile_default(conn);
                {
                        uint buf_size;
                        buf_size = MYSQLND_G(net_read_buffer_size); /* this is long, cast to uint*/
@@ -807,11 +808,12 @@ err:
                DBG_ERR_FMT("[%d] %.64s (trying to connect via %s)", errcode, errstr, conn->scheme);
                SET_CLIENT_ERROR(conn->error_info, errcode? errcode:CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, errstr);
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "[%d] %.64s (trying to connect via %s)", errcode, errstr, conn->scheme);
-
-               mnd_efree(errstr);
+               /* no mnd_ since we don't allocate it */
+               efree(errstr);
        }
        if (conn->scheme) {
-               mnd_pefree(conn->scheme, conn->persistent);
+               /* no mnd_ since we don't allocate it */
+               pefree(conn->scheme, conn->persistent);
                conn->scheme = NULL;
        }
 
@@ -1315,7 +1317,7 @@ MYSQLND_METHOD_PRIVATE(mysqlnd_conn, get_state)(MYSQLND * const conn TSRMLS_DC)
 {
        enum mysqlnd_connection_state state;
        DBG_ENTER("mysqlnd_conn::get_state");
-       tsrm_mutex_lock(conn->LOCK_state);
+       tsrm_mutex_lock(conn->LOCK_state);
        state = conn->state;
        tsrm_mutex_unlock(conn->LOCK_state);
        DBG_RETURN(state);
index 94df285e665e9c2d66cb75dff97787919de701d8..c64dee5cabd42c568d40bec226ec1a5774fa457b 100644 (file)
@@ -522,6 +522,7 @@ MYSQLND_METHOD(mysqlnd_stmt, execute)(MYSQLND_STMT * const stmt TSRMLS_DC)
                if (CONN_GET_STATE(conn) == CONN_QUIT_SENT) {
                        /* close the statement here, the connection has been closed */
                }
+               stmt->state = MYSQLND_STMT_PREPARED;
        } else {
                SET_EMPTY_ERROR(stmt->error_info);
                SET_EMPTY_ERROR(stmt->conn->error_info);
index dfc2cecddfbaf9bdff40b2a75858f6c281e9ac82..078ec93024c27b95dc36ae210472174ee5b6cf69 100644 (file)
@@ -650,14 +650,30 @@ void _mysqlnd_init_ps_subsystem()
 /* }}} */
 
 
+/* {{{ mysqlnd_stmt_copy_it */
+static void
+mysqlnd_stmt_copy_it(zval *** copies, zval *original, uint param_count, uint current)
+{
+       if (!*copies) {
+               *copies = ecalloc(param_count, sizeof(zval *));                                 
+       }
+       MAKE_STD_ZVAL((*copies)[current]);
+       *(*copies)[current] = *original;
+       Z_SET_REFCOUNT_P((*copies)[current], 1);
+       zval_copy_ctor((*copies)[current]);
+}
+/* }}} */
+
+
 /* {{{ mysqlnd_stmt_execute_store_params */
-void
+static void
 mysqlnd_stmt_execute_store_params(MYSQLND_STMT *stmt, zend_uchar **buf, zend_uchar **p,
                                                                  size_t *buf_len, unsigned int null_byte_offset TSRMLS_DC)
 {
        unsigned int i = 0;
        unsigned left = (*buf_len - (*p - *buf));
        unsigned int data_size = 0;
+       zval **copies = NULL;/* if there are different types */
 
 /* 1. Store type information */
        if (stmt->send_types_to_server) {
@@ -689,26 +705,44 @@ mysqlnd_stmt_execute_store_params(MYSQLND_STMT *stmt, zend_uchar **buf, zend_uch
 /* 2. Store data */
        /* 2.1 Calculate how much space we need */
        for (i = 0; i < stmt->param_count; i++) {
+               unsigned int j;
+               zval *the_var = stmt->param_bind[i].zv;
                if (stmt->param_bind[i].zv &&
                        Z_TYPE_P(stmt->param_bind[i].zv) == IS_NULL) {
                        continue;
                }
+               for (j = i + 1; j < stmt->param_count; j++) {
+                       if (stmt->param_bind[j].zv == stmt->param_bind[i].zv) {
+                               /* Double binding of the same zval, make a copy */
+                               mysqlnd_stmt_copy_it(&copies, stmt->param_bind[i].zv, stmt->param_count, i);
+                               break; 
+                       }
+               }
 
                switch (stmt->param_bind[i].type) {
                        case MYSQL_TYPE_DOUBLE:
                                data_size += 8;
+                               if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_DOUBLE) {
+                                       if (!copies || !copies[i]) {
+                                               mysqlnd_stmt_copy_it(&copies, stmt->param_bind[i].zv, stmt->param_count, i);
+                                       }
+                               }
                                break;
 #if SIZEOF_LONG==8  
                        case MYSQL_TYPE_LONGLONG:
                                data_size += 8;
-                               break;
 #elif SIZEOF_LONG==4
                        case MYSQL_TYPE_LONG:
                                data_size += 4;
-                               break;
 #else
 #error "Should not happen"
 #endif
+                               if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_LONG) {
+                                       if (!copies || !copies[i]) {
+                                               mysqlnd_stmt_copy_it(&copies, stmt->param_bind[i].zv, stmt->param_count, i);
+                                       }
+                               }
+                               break;
                        case MYSQL_TYPE_LONG_BLOB:
                                if (!(stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED)) {
                                        /*
@@ -721,8 +755,25 @@ mysqlnd_stmt_execute_store_params(MYSQLND_STMT *stmt, zend_uchar **buf, zend_uch
                                break;
                        case MYSQL_TYPE_VAR_STRING:
                                data_size += 8; /* max 8 bytes for size */
-                               convert_to_string_ex(&stmt->param_bind[i].zv);
-                               data_size += Z_STRLEN_P(stmt->param_bind[i].zv);
+#if PHP_MAJOR_VERSION < 6
+                               if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_STRING)
+#elif PHP_MAJOR_VERSION >= 6
+                               if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_STRING || 
+                                       (UG(unicode) && Z_TYPE_P(stmt->param_bind[i].zv) == IS_UNICODE))
+#endif
+                               {
+                                       if (!copies || !copies[i]) {
+                                               mysqlnd_stmt_copy_it(&copies, stmt->param_bind[i].zv, stmt->param_count, i);
+                                       }
+                                       the_var = copies[i];
+#if PHP_MAJOR_VERSION >= 6
+                                       if (UG(unicode) && Z_TYPE_P(the_var) == IS_UNICODE) {
+                                               zval_unicode_to_string_ex(the_var, UG(utf8_conv) TSRMLS_CC);
+                                       }
+#endif
+                               }
+                               convert_to_string_ex(&the_var);
+                               data_size += Z_STRLEN_P(the_var);
                                break;
                }
 
@@ -743,7 +794,7 @@ mysqlnd_stmt_execute_store_params(MYSQLND_STMT *stmt, zend_uchar **buf, zend_uch
 
        /* 2.3 Store the actual data */
        for (i = 0; i < stmt->param_count; i++) {
-               zval *data = stmt->param_bind[i].zv;
+               zval *data = copies && copies[i]? copies[i]: stmt->param_bind[i].zv;
                /* Handle long data */
                if (stmt->param_bind[i].zv && Z_TYPE_P(data) == IS_NULL) {
                        (*buf + null_byte_offset)[i/8] |= (zend_uchar) (1 << (i & 7));
@@ -791,7 +842,6 @@ mysqlnd_stmt_execute_store_params(MYSQLND_STMT *stmt, zend_uchar **buf, zend_uch
                                                memcpy(*p, Z_STRVAL_P(data), len);
                                                (*p) += len;
                                        }
-
                                        break;
                                default:
                                        /* Won't happen, but set to NULL */
@@ -800,6 +850,14 @@ mysqlnd_stmt_execute_store_params(MYSQLND_STMT *stmt, zend_uchar **buf, zend_uch
                        }
                }
        }
+       if (copies) {
+               for (i = 0; i < stmt->param_count; i++) {
+                       if (copies[i]) {
+                               zval_ptr_dtor(&copies[i]);
+                       }
+               }
+               efree(copies);  
+       }
 }
 /* }}} */