]> granicus.if.org Git - php/commitdiff
Handle changing column count in mysqlnd result binding
authorNikita Popov <nikita.ppv@gmail.com>
Tue, 8 Dec 2020 15:58:30 +0000 (16:58 +0100)
committerNikita Popov <nikita.ppv@gmail.com>
Wed, 9 Dec 2020 09:30:23 +0000 (10:30 +0100)
If the count changes from prepare to execute and result_bind is
alreadly allocated, reallocate it there.

This is something of a hack. It would be cleaner to require that
result bindings are registered only after execute, when the final
result set fields are known. But mysqli at least directly exposes
this to the user, so we have no guarantee.

ext/mysqlnd/mysqlnd_ps.c
ext/pdo_mysql/tests/pdo_mysql_stmt_variable_columncount.phpt

index 10f20eede8799a0080cdb75e597917f4a6606a47..b97bccbe6050e840aba8f77af2d8a6dc573954b7 100644 (file)
@@ -36,7 +36,6 @@ enum_func_status mysqlnd_stmt_execute_generate_request(MYSQLND_STMT * const s, z
 enum_func_status mysqlnd_stmt_execute_batch_generate_request(MYSQLND_STMT * const s, zend_uchar ** request, size_t *request_len, zend_bool * free_buffer);
 
 static void mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const stmt);
-static void mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const stmt, const unsigned int param_no);
 
 /* {{{ mysqlnd_stmt::store_result */
 static MYSQLND_RES *
@@ -542,7 +541,27 @@ mysqlnd_stmt_execute_parse_response(MYSQLND_STMT * const s, enum_mysqlnd_parse_e
                        stmt->result->conn = conn->m->get_reference(conn);
                }
 
-               /* Update stmt->field_count as SHOW sets it to 0 at prepare */
+               /* If the field count changed, update the result_bind structure. Ideally result_bind
+                * would only ever be created after execute, in which case the size cannot change anymore,
+                * but at least in mysqli this does not seem enforceable. */
+               if (stmt->result_bind && conn->field_count != stmt->field_count) {
+                       if (conn->field_count < stmt->field_count) {
+                               /* Number of columns decreased, free bindings. */
+                               for (unsigned i = conn->field_count; i < stmt->field_count; i++) {
+                                       zval_ptr_dtor(&stmt->result_bind[i].zv);
+                               }
+                       }
+                       stmt->result_bind =
+                               mnd_erealloc(stmt->result_bind, conn->field_count * sizeof(MYSQLND_RESULT_BIND));
+                       if (conn->field_count > stmt->field_count) {
+                               /* Number of columns increase, initialize new ones. */
+                               for (unsigned i = stmt->field_count; i < conn->field_count; i++) {
+                                       ZVAL_UNDEF(&stmt->result_bind[i].zv);
+                                       stmt->result_bind[i].bound = false;
+                               }
+                       }
+               }
+
                stmt->field_count = stmt->result->field_count = conn->field_count;
                if (stmt->result->stored_data) {
                        stmt->result->stored_data->lengths = NULL;
@@ -1577,22 +1596,13 @@ MYSQLND_METHOD(mysqlnd_stmt, bind_one_result)(MYSQLND_STMT * const s, unsigned i
        SET_EMPTY_ERROR(conn->error_info);
 
        if (stmt->field_count) {
-               mysqlnd_stmt_separate_one_result_bind(s, param_no);
-               /* Guaranteed is that stmt->result_bind is NULL */
                if (!stmt->result_bind) {
                        stmt->result_bind = mnd_ecalloc(stmt->field_count, sizeof(MYSQLND_RESULT_BIND));
-               } else {
-                       stmt->result_bind = mnd_erealloc(stmt->result_bind, stmt->field_count * sizeof(MYSQLND_RESULT_BIND));
                }
-               if (!stmt->result_bind) {
-                       DBG_RETURN(FAIL);
+               if (stmt->result_bind[param_no].bound) {
+                       zval_ptr_dtor(&stmt->result_bind[param_no].zv);
                }
                ZVAL_NULL(&stmt->result_bind[param_no].zv);
-               /*
-                 Don't update is_ref !!! it's not our job
-                 Otherwise either 009.phpt or mysqli_stmt_bind_result.phpt
-                 will fail.
-               */
                stmt->result_bind[param_no].bound = TRUE;
        }
        DBG_INF("PASS");
@@ -1968,37 +1978,6 @@ mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const s)
 /* }}} */
 
 
-/* {{{ mysqlnd_stmt_separate_one_result_bind */
-static void
-mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const s, const unsigned int param_no)
-{
-       MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
-       DBG_ENTER("mysqlnd_stmt_separate_one_result_bind");
-       if (!stmt) {
-               DBG_VOID_RETURN;
-       }
-       DBG_INF_FMT("stmt=%lu result_bind=%p field_count=%u param_no=%u", stmt->stmt_id, stmt->result_bind, stmt->field_count, param_no);
-
-       if (!stmt->result_bind) {
-               DBG_VOID_RETURN;
-       }
-
-       /*
-         Because only the bound variables can point to our internal buffers, then
-         separate or free only them. Free is possible because the user could have
-         lost reference.
-       */
-       /* Let's try with no cache */
-       if (stmt->result_bind[param_no].bound == TRUE) {
-               DBG_INF_FMT("%u has refcount=%u", param_no, Z_REFCOUNTED(stmt->result_bind[param_no].zv)? Z_REFCOUNT(stmt->result_bind[param_no].zv) : 0);
-               zval_ptr_dtor(&stmt->result_bind[param_no].zv);
-       }
-
-       DBG_VOID_RETURN;
-}
-/* }}} */
-
-
 /* {{{ mysqlnd_stmt::free_stmt_result */
 static void
 MYSQLND_METHOD(mysqlnd_stmt, free_stmt_result)(MYSQLND_STMT * const s)
index 1c492dfc061cc95c538d37e89c04f1d449e41975..110710dd118c62459b70db3aee1d90d03b523d15 100644 (file)
@@ -1,7 +1,5 @@
 --TEST--
 MySQL Prepared Statements and different column counts
---XFAIL--
-nextRowset() problem with stored proc & emulation mode & mysqlnd
 --SKIPIF--
 <?php
 require_once(__DIR__ . DIRECTORY_SEPARATOR . 'skipif.inc');
@@ -25,10 +23,8 @@ if ($version < 50000)
     $db = MySQLPDOTest::factory();
 
     function check_result($offset, $stmt, $columns) {
-
-        do {
-                $row = $stmt->fetch(PDO::FETCH_ASSOC);
-        } while ($stmt->nextRowSet());
+        $row = $stmt->fetch(PDO::FETCH_ASSOC);
+        $stmt->nextRowSet();
 
         if (!isset($row['one']) || ($row['one'] != 1)) {
                 printf("[%03d + 1] Expecting array('one' => 1), got %s\n", $offset, var_export($row, true));