summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/mysqli/mysqli.c33
-rw-r--r--ext/mysqli/mysqli_mysqlnd.h1
-rw-r--r--ext/mysqli/mysqli_nonapi.c21
-rw-r--r--ext/mysqli/mysqli_prop.c2
-rw-r--r--ext/mysqli/php_mysqli_structs.h3
-rw-r--r--ext/mysqli/tests/mysqli_get_client_stats.phpt8
-rw-r--r--ext/mysqlnd/mysqlnd.c368
-rw-r--r--ext/mysqlnd/mysqlnd.h5
-rw-r--r--ext/mysqlnd/mysqlnd_enum_n_def.h1
-rw-r--r--ext/mysqlnd/mysqlnd_libmysql_compat.h3
-rw-r--r--ext/mysqlnd/mysqlnd_priv.h10
-rw-r--r--ext/mysqlnd/mysqlnd_ps.c179
-rw-r--r--ext/mysqlnd/mysqlnd_result.c829
-rw-r--r--ext/mysqlnd/mysqlnd_result.h5
-rw-r--r--ext/mysqlnd/mysqlnd_result_meta.c4
-rw-r--r--ext/mysqlnd/mysqlnd_structs.h97
-rw-r--r--ext/mysqlnd/mysqlnd_wireprotocol.c93
-rw-r--r--ext/mysqlnd/mysqlnd_wireprotocol.h9
18 files changed, 1380 insertions, 291 deletions
diff --git a/ext/mysqli/mysqli.c b/ext/mysqli/mysqli.c
index c7a41a896b..4cfdb50cbe 100644
--- a/ext/mysqli/mysqli.c
+++ b/ext/mysqli/mysqli.c
@@ -31,9 +31,6 @@
#include "php_mysqli_structs.h"
#include "zend_exceptions.h"
-#define MYSQLI_STORE_RESULT 0
-#define MYSQLI_USE_RESULT 1
-
ZEND_DECLARE_MODULE_GLOBALS(mysqli)
static PHP_GINIT_FUNCTION(mysqli);
@@ -688,8 +685,11 @@ PHP_MINIT_FUNCTION(mysqli)
REGISTER_LONG_CONSTANT("MYSQLI_CLIENT_FOUND_ROWS", CLIENT_FOUND_ROWS, CONST_CS | CONST_PERSISTENT);
/* for mysqli_query */
- REGISTER_LONG_CONSTANT("MYSQLI_STORE_RESULT", 0, CONST_CS | CONST_PERSISTENT);
- REGISTER_LONG_CONSTANT("MYSQLI_USE_RESULT", 1, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("MYSQLI_STORE_RESULT", MYSQLI_STORE_RESULT, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("MYSQLI_USE_RESULT", MYSQLI_USE_RESULT, CONST_CS | CONST_PERSISTENT);
+#if defined(HAVE_MYSQLND) && defined(MYSQLND_THREADING)
+ REGISTER_LONG_CONSTANT("MYSQLI_BG_STORE_RESULT", MYSQLI_BG_STORE_RESULT, CONST_CS | CONST_PERSISTENT);
+#endif
/* for mysqli_fetch_assoc */
REGISTER_LONG_CONSTANT("MYSQLI_ASSOC", MYSQLI_ASSOC, CONST_CS | CONST_PERSISTENT);
@@ -955,7 +955,7 @@ Parameters:
ZEND_FUNCTION(mysqli_result_construct)
{
MY_MYSQL *mysql;
- MYSQL_RES *result;
+ MYSQL_RES *result = NULL;
zval *mysql_link;
MYSQLI_RESOURCE *mysqli_resource;
long resmode = MYSQLI_STORE_RESULT;
@@ -970,10 +970,6 @@ ZEND_FUNCTION(mysqli_result_construct)
if (zend_parse_parameters(2 TSRMLS_CC, "Ol", &mysql_link, mysqli_link_class_entry, &resmode)==FAILURE) {
return;
}
- if (resmode != MYSQLI_USE_RESULT && resmode != MYSQLI_STORE_RESULT) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid value for resultmode");
- RETURN_FALSE;
- }
break;
default:
WRONG_PARAM_COUNT;
@@ -981,8 +977,21 @@ ZEND_FUNCTION(mysqli_result_construct)
MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &mysql_link, "mysqli_link", MYSQLI_STATUS_VALID);
- result = (resmode == MYSQLI_STORE_RESULT) ? mysql_store_result(mysql->mysql) :
- mysql_use_result(mysql->mysql);
+ switch (resmode) {
+ case MYSQLI_STORE_RESULT:
+ result = mysql_store_result(mysql->mysql);
+ break;
+ case MYSQLI_USE_RESULT:
+ result = mysql_use_result(mysql->mysql);
+ break;
+#if defined(HAVE_MYSQLND) && defined(MYSQLND_THREADING)
+ case MYSQLI_BG_STORE_RESULT:
+ result = mysqli_bg_store_result(mysql->mysql);
+ break;
+#endif
+ default:
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid value for resultmode");
+ }
if (!result) {
RETURN_FALSE;
diff --git a/ext/mysqli/mysqli_mysqlnd.h b/ext/mysqli/mysqli_mysqlnd.h
index 920f0d4b04..baa7745e81 100644
--- a/ext/mysqli/mysqli_mysqlnd.h
+++ b/ext/mysqli/mysqli_mysqlnd.h
@@ -37,5 +37,6 @@
#define mysqli_close(c, how) mysqlnd_close((c), (how))
#define mysqli_stmt_close(c, implicit) mysqlnd_stmt_close((c), (implicit))
#define mysqli_free_result(r, implicit) mysqlnd_free_result((r), (implicit))
+#define mysqli_bg_store_result(r) mysqlnd_bg_store_result((r))
#endif
diff --git a/ext/mysqli/mysqli_nonapi.c b/ext/mysqli/mysqli_nonapi.c
index 6a8c599469..f37ab726a1 100644
--- a/ext/mysqli/mysqli_nonapi.c
+++ b/ext/mysqli/mysqli_nonapi.c
@@ -459,7 +459,11 @@ PHP_FUNCTION(mysqli_query)
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Empty query");
RETURN_FALSE;
}
- if (resultmode != MYSQLI_USE_RESULT && resultmode != MYSQLI_STORE_RESULT) {
+ if (resultmode != MYSQLI_USE_RESULT && resultmode != MYSQLI_STORE_RESULT
+#if defined(HAVE_MYSQLND) && defined(MYSQLND_THREADING)
+ && resultmode != MYSQLI_BG_STORE_RESULT
+#endif
+ ) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid value for resultmode");
RETURN_FALSE;
}
@@ -468,6 +472,7 @@ PHP_FUNCTION(mysqli_query)
MYSQLI_DISABLE_MQ;
+
if (mysql_real_query(mysql->mysql, query, query_len)) {
MYSQLI_REPORT_MYSQL_ERROR(mysql->mysql);
RETURN_FALSE;
@@ -481,7 +486,19 @@ PHP_FUNCTION(mysqli_query)
RETURN_TRUE;
}
- result = (resultmode == MYSQLI_USE_RESULT) ? mysql_use_result(mysql->mysql) : mysql_store_result(mysql->mysql);
+ switch (resultmode) {
+ case MYSQLI_STORE_RESULT:
+ result = mysql_store_result(mysql->mysql);
+ break;
+ case MYSQLI_USE_RESULT:
+ result = mysql_use_result(mysql->mysql);
+ break;
+#if defined(HAVE_MYSQLND) && defined(MYSQLND_THREADING)
+ case MYSQLI_BG_STORE_RESULT:
+ result = mysqli_bg_store_result(mysql->mysql);
+ break;
+#endif
+ }
if (!result) {
php_mysqli_throw_sql_exception((char *)mysql_sqlstate(mysql->mysql), mysql_errno(mysql->mysql) TSRMLS_CC,
diff --git a/ext/mysqli/mysqli_prop.c b/ext/mysqli/mysqli_prop.c
index 62afad2ac1..1e06a694bf 100644
--- a/ext/mysqli/mysqli_prop.c
+++ b/ext/mysqli/mysqli_prop.c
@@ -211,7 +211,7 @@ static int result_type_read(mysqli_object *obj, zval **retval TSRMLS_DC)
if (!p) {
ZVAL_NULL(*retval);
} else {
- ZVAL_LONG(*retval, (p->data) ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT);
+ ZVAL_LONG(*retval, mysqli_result_is_unbuffered(p) ? MYSQLI_USE_RESULT:MYSQLI_STORE_RESULT);
}
return SUCCESS;
}
diff --git a/ext/mysqli/php_mysqli_structs.h b/ext/mysqli/php_mysqli_structs.h
index ec3291d8bd..7177e42d51 100644
--- a/ext/mysqli/php_mysqli_structs.h
+++ b/ext/mysqli/php_mysqli_structs.h
@@ -298,6 +298,9 @@ PHP_MYSQLI_EXPORT(zend_object_value) mysqli_objects_new(zend_class_entry * TSRML
#define MYSQLI_STORE_RESULT 0
#define MYSQLI_USE_RESULT 1
+#ifdef HAVE_MYSQLND
+#define MYSQLI_BG_STORE_RESULT 101
+#endif
/* for mysqli_fetch_assoc */
#define MYSQLI_ASSOC 1
diff --git a/ext/mysqli/tests/mysqli_get_client_stats.phpt b/ext/mysqli/tests/mysqli_get_client_stats.phpt
index 042f14bd99..10cad1e727 100644
--- a/ext/mysqli/tests/mysqli_get_client_stats.phpt
+++ b/ext/mysqli/tests/mysqli_get_client_stats.phpt
@@ -969,9 +969,9 @@ array(61) {
["mem_efree_count"]=>
string(1) "0"
["mem_malloc_count"]=>
- string(1) "0"
+ string(1) "1"
["mem_malloc_ammount"]=>
- string(1) "0"
+ string(%d) "%d"
["mem_calloc_count"]=>
string(1) "0"
["mem_calloc_ammount"]=>
@@ -1106,9 +1106,9 @@ array(61) {
[u"mem_efree_count"]=>
unicode(1) "0"
[u"mem_malloc_count"]=>
- unicode(1) "0"
+ unicode(1) "1"
[u"mem_malloc_ammount"]=>
- unicode(1) "0"
+ unicode(%d) "%d"
[u"mem_calloc_count"]=>
unicode(1) "0"
[u"mem_calloc_ammount"]=>
diff --git a/ext/mysqlnd/mysqlnd.c b/ext/mysqlnd/mysqlnd.c
index 5e32d7fda7..6e7441dcea 100644
--- a/ext/mysqlnd/mysqlnd.c
+++ b/ext/mysqlnd/mysqlnd.c
@@ -66,12 +66,205 @@ const char * mysqlnd_out_of_sync = "Commands out of sync; you can't run this com
MYSQLND_STATS *mysqlnd_global_stats = NULL;
static zend_bool mysqlnd_library_initted = FALSE;
+MYSQLND_MEMORY_POOL mysqlnd_memory_pool;
static enum_func_status mysqlnd_send_close(MYSQLND * conn TSRMLS_DC);
+#define MYSQLND_SILENT 1
+
+#ifdef MYSQLND_THREADED
+/* {{{ _mysqlnd_fetch_thread */
+void * _mysqlnd_fetch_thread(void *arg)
+{
+ MYSQLND *conn = (MYSQLND *) arg;
+ MYSQLND_RES * result = NULL;
+ void ***tsrm_ls = conn->tsrm_ls;
+#ifndef MYSQLND_SILENT
+ printf("conn=%p tsrm_ls=%p\n", conn, conn->tsrm_ls);
+#endif
+ do {
+ pthread_mutex_lock(&conn->LOCK_work);
+ while (conn->thread_killed == FALSE /* && there is work */) {
+#ifndef MYSQLND_SILENT
+ printf("Waiting for work in %s\n", __FUNCTION__);
+#endif
+ pthread_cond_wait(&conn->COND_work, &conn->LOCK_work);
+ }
+ if (conn->thread_killed == TRUE) {
+#ifndef MYSQLND_SILENT
+ printf("Thread killed in %s\n", __FUNCTION__);
+#endif
+ pthread_cond_signal(&conn->COND_thread_ended);
+ pthread_mutex_unlock(&conn->LOCK_work);
+ break;
+ }
+#ifndef MYSQLND_SILENT
+ printf("Got work in %s\n", __FUNCTION__);
+#endif
+ CONN_SET_STATE(conn, CONN_FETCHING_DATA);
+ result = conn->current_result;
+ conn->current_result = NULL;
+ pthread_mutex_unlock(&conn->LOCK_work);
+
+ mysqlnd_background_store_result_fetch_data(result TSRMLS_CC);
+
+ /* do fetch the data from the wire */
+
+ pthread_mutex_lock(&conn->LOCK_work);
+ CONN_SET_STATE(conn, CONN_READY);
+ pthread_cond_signal(&conn->COND_work_done);
+#ifndef MYSQLND_SILENT
+ printf("Signaling work done in %s\n", __FUNCTION__);
+#endif
+ pthread_mutex_unlock(&conn->LOCK_work);
+ } while (1);
+
+#ifndef MYSQLND_SILENT
+ printf("Exiting worker thread in %s\n", __FUNCTION__);
+#endif
+ return NULL;
+}
+/* }}} */
+#endif /* MYSQLND_THREADED */
+
+/************************************************************************************************/
+/* Let's don't use pool allocation for now */
+/* {{{ mysqlnd_mempool_free_chunk */
+static
+void mysqlnd_mempool_free_contents(MYSQLND_MEMORY_POOL * pool TSRMLS_DC)
+{
+ DBG_ENTER("mysqlnd_mempool_dtor");
+ uint i;
+ for (i = 0; i < pool->free_chunk_list_elements; i++) {
+ MYSQLND_MEMORY_POOL_CHUNK * chunk = pool->free_chunk_list[i];
+ chunk->free_chunk(chunk, FALSE TSRMLS_CC);
+ }
+
+ DBG_VOID_RETURN;
+}
+/* }}} */
+
+/* Let's don't use pool allocation for now */
+/* {{{ mysqlnd_mempool_free_chunk */
+static
+void mysqlnd_mempool_free_chunk(MYSQLND_MEMORY_POOL_CHUNK * chunk, zend_bool cache_it TSRMLS_DC)
+{
+ DBG_ENTER("mysqlnd_mempool_free_chunk");
+ MYSQLND_MEMORY_POOL * pool = chunk->pool;
+ if (chunk->from_pool) {
+ /* Try to back-off and guess if this is the last block allocated */
+ if (chunk->ptr == (pool->arena + (pool->arena_size - pool->free_size - chunk->size))) {
+ /*
+ This was the last allocation. Lucky us, we can free
+ a bit of memory from the pool. Next time we will return from the same ptr.
+ */
+ pool->free_size += chunk->size;
+ }
+ pool->refcount--;
+ } else {
+ mnd_free(chunk->ptr);
+ }
+ if (cache_it && pool->free_chunk_list_elements < MYSQLND_MEMORY_POOL_CHUNK_LIST_SIZE) {
+ chunk->ptr = NULL;
+ pool->free_chunk_list[pool->free_chunk_list_elements++] = chunk;
+ } else {
+ /* We did not cache it -> free it */
+ mnd_free(chunk);
+ }
+ DBG_VOID_RETURN;
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_mempool_resize_chunk */
+static void
+mysqlnd_mempool_resize_chunk(MYSQLND_MEMORY_POOL_CHUNK * chunk, uint size TSRMLS_DC)
+{
+ DBG_ENTER("mysqlnd_mempool_resize_chunk");
+ if (chunk->from_pool) {
+ MYSQLND_MEMORY_POOL * pool = chunk->pool;
+ /* Try to back-off and guess if this is the last block allocated */
+ if (chunk->ptr == (pool->arena + (pool->arena_size - pool->free_size - chunk->size))) {
+ /*
+ This was the last allocation. Lucky us, we can free
+ a bit of memory from the pool. Next time we will return from the same ptr.
+ */
+ if ((chunk->size + pool->free_size) < size) {
+ zend_uchar *new_ptr;
+ new_ptr = mnd_malloc(size);
+ memcpy(new_ptr, chunk->ptr, chunk->size);
+ chunk->ptr = new_ptr;
+ pool->free_size += chunk->size;
+ chunk->size = size;
+ chunk->pool = NULL; /* now we have no pool memory */
+ pool->refcount--;
+ } else {
+ /* If the chunk is > than asked size then free_memory increases, otherwise decreases*/
+ pool->free_size += (chunk->size - size);
+ }
+ } else {
+ /* Not last chunk, if the user asks for less, give it to him */
+ if (chunk->size >= size) {
+ ; /* nop */
+ } else {
+ zend_uchar *new_ptr;
+ new_ptr = mnd_malloc(size);
+ memcpy(new_ptr, chunk->ptr, chunk->size);
+ chunk->ptr = new_ptr;
+ chunk->size = size;
+ chunk->pool = NULL; /* now we have no pool memory */
+ pool->refcount--;
+ }
+ }
+ } else {
+ chunk->ptr = mnd_realloc(chunk->ptr, size);
+ }
+ DBG_VOID_RETURN;
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_mempool_get_chunk */
+static
+MYSQLND_MEMORY_POOL_CHUNK * mysqlnd_mempool_get_chunk(MYSQLND_MEMORY_POOL * pool, uint size TSRMLS_DC)
+{
+ MYSQLND_MEMORY_POOL_CHUNK *chunk = NULL;
+ DBG_ENTER("mysqlnd_mempool_get_chunk");
+
+ if (pool->free_chunk_list_elements) {
+ chunk = pool->free_chunk_list[--pool->free_chunk_list_elements];
+ } else {
+ chunk = mnd_malloc(sizeof(MYSQLND_MEMORY_POOL_CHUNK));
+ }
+
+ chunk->free_chunk = mysqlnd_mempool_free_chunk;
+ chunk->resize_chunk = mysqlnd_mempool_resize_chunk;
+ chunk->size = size;
+ /*
+ Should not go over MYSQLND_MAX_PACKET_SIZE, since we
+ expect non-arena memory in mysqlnd_wireprotocol.c . We
+ realloc the non-arena memory.
+ */
+ chunk->pool = pool;
+ if (size > pool->free_size) {
+ chunk->ptr = mnd_malloc(size);
+ chunk->from_pool = FALSE;
+ } else {
+ chunk->from_pool = TRUE;
+ ++pool->refcount;
+ chunk->ptr = pool->arena + (pool->arena_size - pool->free_size);
+ /* Last step, update free_size */
+ pool->free_size -= size;
+ }
+ DBG_RETURN(chunk);
+}
+/* }}} */
+/************************************************************************************************/
+
+
/* {{{ mysqlnd_library_init */
static
-void mysqlnd_library_init()
+void mysqlnd_library_init(TSRMLS_D)
{
if (mysqlnd_library_initted == FALSE) {
mysqlnd_library_initted = TRUE;
@@ -81,6 +274,13 @@ void mysqlnd_library_init()
#ifdef ZTS
mysqlnd_global_stats->LOCK_access = tsrm_mutex_alloc();
#endif
+ mysqlnd_memory_pool.arena_size = 16000;
+ mysqlnd_memory_pool.free_size = mysqlnd_memory_pool.arena_size;
+ mysqlnd_memory_pool.refcount = 0;
+ /* OOM ? */
+ mysqlnd_memory_pool.arena = mnd_malloc(mysqlnd_memory_pool.arena_size);
+ mysqlnd_memory_pool.get_chunk = mysqlnd_mempool_get_chunk;
+ mysqlnd_memory_pool.free_contents = mysqlnd_mempool_free_contents;
}
}
/* }}} */
@@ -88,9 +288,12 @@ void mysqlnd_library_init()
/* {{{ mysqlnd_library_end */
static
-void mysqlnd_library_end()
+void mysqlnd_library_end(TSRMLS_D)
{
if (mysqlnd_library_initted == TRUE) {
+ /* mnd_free will reference LOCK_access and won't crash...*/
+ mysqlnd_memory_pool.free_contents(&mysqlnd_memory_pool TSRMLS_CC);
+ free(mysqlnd_memory_pool.arena);
#ifdef ZTS
tsrm_mutex_free(mysqlnd_global_stats->LOCK_access);
#endif
@@ -229,6 +432,7 @@ MYSQLND_METHOD(mysqlnd_conn, free_contents)(MYSQLND *conn TSRMLS_DC)
mnd_pefree(conn->net.cmd_buffer.buffer, pers);
conn->net.cmd_buffer.buffer = NULL;
}
+
conn->charset = NULL;
conn->greet_charset = NULL;
@@ -246,6 +450,22 @@ MYSQLND_METHOD_PRIVATE(mysqlnd_conn, dtor)(MYSQLND *conn TSRMLS_DC)
conn->m->free_contents(conn TSRMLS_CC);
+#ifdef MYSQLND_THREADED
+ if (conn->thread_is_running) {
+ pthread_mutex_lock(&conn->LOCK_work);
+ conn->thread_killed = TRUE;
+ pthread_cond_signal(&conn->COND_work);
+ pthread_cond_wait(&conn->COND_thread_ended, &conn->LOCK_work);
+ pthread_mutex_unlock(&conn->LOCK_work);
+ }
+
+ tsrm_mutex_free(conn->LOCK_state);
+
+ pthread_cond_destroy(&conn->COND_work);
+ pthread_cond_destroy(&conn->COND_work_done);
+ pthread_mutex_destroy(&conn->LOCK_work);
+#endif
+
mnd_pefree(conn, conn->persistent);
DBG_VOID_RETURN;
@@ -363,7 +583,7 @@ mysqlnd_simple_command(MYSQLND *conn, enum php_mysqlnd_server_command command,
DBG_ENTER("mysqlnd_simple_command");
DBG_INF_FMT("command=%s ok_packet=%d silent=%d", mysqlnd_command_to_text[command], ok_packet, silent);
- switch (conn->state) {
+ switch (CONN_GET_STATE(conn)) {
case CONN_READY:
break;
case CONN_QUIT_SENT:
@@ -481,13 +701,13 @@ PHPAPI MYSQLND *mysqlnd_connect(MYSQLND *conn,
DBG_ENTER("mysqlnd_connect");
DBG_INF_FMT("host=%s user=%s db=%s port=%d flags=%d persistent=%d state=%d",
host?host:"", user?user:"", db?db:"", port, mysql_flags,
- conn? conn->persistent:0, conn? conn->state:-1);
+ conn? conn->persistent:0, conn? CONN_GET_STATE(conn):-1);
- DBG_INF_FMT("state=%d", conn->state);
- if (conn && conn->state > CONN_ALLOCED && conn->state ) {
+ DBG_INF_FMT("state=%d", CONN_GET_STATE(conn));
+ if (conn && CONN_GET_STATE(conn) > CONN_ALLOCED && CONN_GET_STATE(conn) ) {
DBG_INF("Connecting on a connected handle.");
- if (conn->state < CONN_QUIT_SENT) {
+ if (CONN_GET_STATE(conn) < CONN_QUIT_SENT) {
MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_CLOSE_IMPLICIT);
reconnect = TRUE;
mysqlnd_send_close(conn TSRMLS_CC);
@@ -551,7 +771,7 @@ PHPAPI MYSQLND *mysqlnd_connect(MYSQLND *conn,
self_alloced = TRUE;
}
- conn->state = CONN_ALLOCED;
+ CONN_SET_STATE(conn, CONN_ALLOCED);
conn->net.packet_no = 0;
if (conn->options.timeout_connect) {
@@ -663,7 +883,7 @@ PHPAPI MYSQLND *mysqlnd_connect(MYSQLND *conn,
conn->scramble = auth_packet->server_scramble_buf = mnd_pemalloc(SCRAMBLE_LENGTH, conn->persistent);
memcpy(auth_packet->server_scramble_buf, greet_packet.scramble_buf, SCRAMBLE_LENGTH);
if (!PACKET_WRITE(auth_packet, conn)) {
- conn->state = CONN_QUIT_SENT;
+ CONN_SET_STATE(conn, CONN_QUIT_SENT);
SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
goto err;
}
@@ -687,7 +907,7 @@ PHPAPI MYSQLND *mysqlnd_connect(MYSQLND *conn,
}
}
} else {
- conn->state = CONN_READY;
+ CONN_SET_STATE(conn, CONN_READY);
conn->user = pestrdup(user, conn->persistent);
conn->passwd = pestrndup(passwd, passwd_len, conn->persistent);
@@ -759,6 +979,23 @@ PHPAPI MYSQLND *mysqlnd_connect(MYSQLND *conn,
DBG_INF("unicode set");
}
#endif
+#ifdef MYSQLND_THREADED
+ {
+ pthread_t th;
+ pthread_attr_t connection_attrib;
+ conn->tsrm_ls = tsrm_ls;
+
+ pthread_attr_init(&connection_attrib);
+ pthread_attr_setdetachstate(&connection_attrib, PTHREAD_CREATE_DETACHED);
+
+ conn->thread_is_running = TRUE;
+ if (pthread_create(&th, &connection_attrib, _mysqlnd_fetch_thread, (void*)conn)) {
+ conn->thread_is_running = FALSE;
+ }
+ }
+#endif
+
+
DBG_RETURN(conn);
}
err:
@@ -1081,7 +1318,7 @@ MYSQLND_METHOD(mysqlnd_conn, kill)(MYSQLND *conn, unsigned int pid TSRMLS_DC)
SET_ERROR_AFF_ROWS(conn);
} else if (PASS == (ret = mysqlnd_simple_command(conn, COM_PROCESS_KILL, buff,
4, PROT_LAST, FALSE TSRMLS_CC))) {
- conn->state = CONN_QUIT_SENT;
+ CONN_SET_STATE(conn, CONN_QUIT_SENT);
}
DBG_RETURN(ret);
}
@@ -1154,7 +1391,7 @@ MYSQLND_METHOD(mysqlnd_conn, shutdown)(MYSQLND * const conn, unsigned long level
/* {{{ mysqlnd_send_close */
-enum_func_status
+static enum_func_status
mysqlnd_send_close(MYSQLND * conn TSRMLS_DC)
{
enum_func_status ret = PASS;
@@ -1163,7 +1400,7 @@ mysqlnd_send_close(MYSQLND * conn TSRMLS_DC)
DBG_INF_FMT("conn=%llu conn->net.stream->abstract=%p",
conn->thread_id, conn->net.stream? conn->net.stream->abstract:NULL);
- switch (conn->state) {
+ switch (CONN_GET_STATE(conn)) {
case CONN_READY:
DBG_INF("Connection clean, sending COM_QUIT");
ret = mysqlnd_simple_command(conn, COM_QUIT, NULL, 0, PROT_LAST,
@@ -1199,7 +1436,7 @@ mysqlnd_send_close(MYSQLND * conn TSRMLS_DC)
We hold one reference, and every other object which needs the
connection does increase it by 1.
*/
- conn->state = CONN_QUIT_SENT;
+ CONN_SET_STATE(conn, CONN_QUIT_SENT);
DBG_RETURN(ret);
}
@@ -1236,7 +1473,6 @@ MYSQLND_METHOD(mysqlnd_conn, close)(MYSQLND * conn, enum_connection_close_type c
ret = conn->m->free_reference(conn TSRMLS_CC);
-
DBG_RETURN(ret);
}
/* }}} */
@@ -1273,6 +1509,46 @@ MYSQLND_METHOD_PRIVATE(mysqlnd_conn, free_reference)(MYSQLND * const conn TSRMLS
/* }}} */
+/* {{{ mysqlnd_conn::get_state */
+#ifdef MYSQLND_THREADED
+static enum mysqlnd_connection_state
+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);
+ state = conn->state;
+ tsrm_mutex_unlock(conn->LOCK_state);
+ DBG_RETURN(state);
+}
+#else
+static enum mysqlnd_connection_state
+MYSQLND_METHOD_PRIVATE(mysqlnd_conn, get_state)(MYSQLND * const conn TSRMLS_DC)
+{
+ DBG_ENTER("mysqlnd_conn::get_state");
+ DBG_RETURN(conn->state);
+}
+#endif
+/* }}} */
+
+
+/* {{{ mysqlnd_conn::set_state */
+static void
+MYSQLND_METHOD_PRIVATE(mysqlnd_conn, set_state)(MYSQLND * const conn, enum mysqlnd_connection_state new_state TSRMLS_DC)
+{
+ DBG_ENTER("mysqlnd_conn::set_state");
+#ifdef MYSQLND_THREADED
+ tsrm_mutex_lock(conn->LOCK_state);
+#endif
+ conn->state = new_state;
+#ifdef MYSQLND_THREADED
+ tsrm_mutex_unlock(conn->LOCK_state);
+#endif
+ DBG_VOID_RETURN;
+}
+/* }}} */
+
+
/* {{{ mysqlnd_conn::field_count */
static unsigned int
MYSQLND_METHOD(mysqlnd_conn, field_count)(const MYSQLND * const conn)
@@ -1420,7 +1696,7 @@ MYSQLND_METHOD(mysqlnd_conn, next_result)(MYSQLND * const conn TSRMLS_DC)
DBG_ENTER("mysqlnd_conn::next_result");
DBG_INF_FMT("conn=%llu", conn->thread_id);
- if (conn->state != CONN_NEXT_RESULT_PENDING) {
+ if (CONN_GET_STATE(conn) != CONN_NEXT_RESULT_PENDING) {
DBG_RETURN(FAIL);
}
@@ -1433,7 +1709,7 @@ MYSQLND_METHOD(mysqlnd_conn, next_result)(MYSQLND * const conn TSRMLS_DC)
if (FAIL == (ret = mysqlnd_query_read_result_set_header(conn, NULL TSRMLS_CC))) {
DBG_ERR_FMT("Serious error. %s::%d", __FILE__, __LINE__);
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Serious error. PID=%d", getpid());
- conn->state = CONN_QUIT_SENT;
+ CONN_SET_STATE(conn, CONN_QUIT_SENT);
}
DBG_RETURN(ret);
@@ -1710,7 +1986,7 @@ MYSQLND_METHOD(mysqlnd_conn, use_result)(MYSQLND * const conn TSRMLS_DC)
}
/* Nothing to store for UPSERT/LOAD DATA */
- if (conn->last_query_type != QUERY_SELECT || conn->state != CONN_FETCHING_DATA) {
+ if (conn->last_query_type != QUERY_SELECT || CONN_GET_STATE(conn) != CONN_FETCHING_DATA) {
SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE,
mysqlnd_out_of_sync);
DBG_ERR("Command out of sync");
@@ -1743,7 +2019,7 @@ MYSQLND_METHOD(mysqlnd_conn, store_result)(MYSQLND * const conn TSRMLS_DC)
}
/* Nothing to store for UPSERT/LOAD DATA*/
- if (conn->last_query_type != QUERY_SELECT || conn->state != CONN_FETCHING_DATA) {
+ if (conn->last_query_type != QUERY_SELECT || CONN_GET_STATE(conn) != CONN_FETCHING_DATA) {
SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE,
mysqlnd_out_of_sync);
DBG_ERR("Command out of sync");
@@ -1761,6 +2037,44 @@ MYSQLND_METHOD(mysqlnd_conn, store_result)(MYSQLND * const conn TSRMLS_DC)
/* }}} */
+/* {{{ mysqlnd_conn::background_store_result */
+MYSQLND_RES *
+MYSQLND_METHOD(mysqlnd_conn, background_store_result)(MYSQLND * const conn TSRMLS_DC)
+{
+ MYSQLND_RES *result;
+
+ DBG_ENTER("mysqlnd_conn::store_result");
+ DBG_INF_FMT("conn=%llu", conn->thread_id);
+
+ if (!conn->current_result) {
+ DBG_RETURN(NULL);
+ }
+
+ /* Nothing to store for UPSERT/LOAD DATA*/
+ if (conn->last_query_type != QUERY_SELECT || CONN_GET_STATE(conn) != CONN_FETCHING_DATA) {
+ SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE,
+ mysqlnd_out_of_sync);
+ DBG_ERR("Command out of sync");
+ DBG_RETURN(NULL);
+ }
+
+ MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_BUFFERED_SETS);
+
+ result = conn->current_result;
+
+ result = result->m.background_store_result(result, conn, FALSE TSRMLS_CC);
+
+ /*
+ Should be here, because current_result is used by the fetching thread to get data info
+ The thread is contacted in mysqlnd_res::background_store_result().
+ */
+ conn->current_result = NULL;
+
+ DBG_RETURN(result);
+}
+/* }}} */
+
+
/* {{{ mysqlnd_conn::get_connection_stats */
static void
MYSQLND_METHOD(mysqlnd_conn, get_connection_stats)(const MYSQLND * const conn,
@@ -1784,6 +2098,7 @@ MYSQLND_CLASS_METHODS_START(mysqlnd_conn)
MYSQLND_METHOD(mysqlnd_conn, query),
MYSQLND_METHOD(mysqlnd_conn, use_result),
MYSQLND_METHOD(mysqlnd_conn, store_result),
+ MYSQLND_METHOD(mysqlnd_conn, background_store_result),
MYSQLND_METHOD(mysqlnd_conn, next_result),
MYSQLND_METHOD(mysqlnd_conn, more_results),
@@ -1829,6 +2144,8 @@ MYSQLND_CLASS_METHODS_START(mysqlnd_conn)
MYSQLND_METHOD_PRIVATE(mysqlnd_conn, get_reference),
MYSQLND_METHOD_PRIVATE(mysqlnd_conn, free_reference),
+ MYSQLND_METHOD_PRIVATE(mysqlnd_conn, get_state),
+ MYSQLND_METHOD_PRIVATE(mysqlnd_conn, set_state),
MYSQLND_CLASS_METHODS_END;
@@ -1846,6 +2163,15 @@ PHPAPI MYSQLND *_mysqlnd_init(zend_bool persistent TSRMLS_DC)
ret->m = & mysqlnd_mysqlnd_conn_methods;
ret->m->get_reference(ret);
+#ifdef MYSQLND_THREADED
+ ret->LOCK_state = tsrm_mutex_alloc();
+
+ pthread_mutex_init(&ret->LOCK_work, NULL);
+ pthread_cond_init(&ret->COND_work, NULL);
+ pthread_cond_init(&ret->COND_work_done, NULL);
+ pthread_cond_init(&ret->COND_thread_ended, NULL);
+#endif
+
DBG_RETURN(ret);
}
/* }}} */
@@ -1985,7 +2311,7 @@ static PHP_MINIT_FUNCTION(mysqlnd)
{
REGISTER_INI_ENTRIES();
- mysqlnd_library_init();
+ mysqlnd_library_init(TSRMLS_C);
return SUCCESS;
}
/* }}} */
@@ -1995,7 +2321,7 @@ static PHP_MINIT_FUNCTION(mysqlnd)
*/
static PHP_MSHUTDOWN_FUNCTION(mysqlnd)
{
- mysqlnd_library_end();
+ mysqlnd_library_end(TSRMLS_C);
UNREGISTER_INI_ENTRIES();
return SUCCESS;
diff --git a/ext/mysqlnd/mysqlnd.h b/ext/mysqlnd/mysqlnd.h
index d008adb8cd..2d649b2689 100644
--- a/ext/mysqlnd/mysqlnd.h
+++ b/ext/mysqlnd/mysqlnd.h
@@ -29,6 +29,9 @@
/* This forces inlining of some accessor functions */
#define MYSQLND_USE_OPTIMISATIONS 0
+
+//#define MYSQLND_THREADING
+
/* #define MYSQLND_STRING_TO_INT_CONVERSION */
/*
This force mysqlnd to do a single (or more depending on ammount of data)
@@ -98,6 +101,7 @@ void _mysqlnd_debug(const char *mode TSRMLS_DC);
#define mysqlnd_use_result(conn) (conn)->m->use_result((conn) TSRMLS_CC)
#define mysqlnd_store_result(conn) (conn)->m->store_result((conn) TSRMLS_CC)
+#define mysqlnd_bg_store_result(conn) (conn)->m->background_store_result((conn) TSRMLS_CC)
#define mysqlnd_next_result(conn) (conn)->m->next_result((conn) TSRMLS_CC)
#define mysqlnd_more_results(conn) (conn)->m->more_results((conn))
#define mysqlnd_free_result(r,e_or_i) ((MYSQLND_RES*)r)->m.free_result(((MYSQLND_RES*)(r)), (e_or_i) TSRMLS_CC)
@@ -242,6 +246,7 @@ PHPAPI ulong mysqlnd_old_escape_string(char *newstr, const char *escapestr, size
/* PS */
#define mysqlnd_stmt_init(conn) (conn)->m->stmt_init((conn) TSRMLS_CC)
#define mysqlnd_stmt_store_result(stmt) (!mysqlnd_stmt_field_count((stmt)) ? PASS:((stmt)->m->store_result((stmt) TSRMLS_CC)? PASS:FAIL))
+#define mysqlnd_stmt_bg_store_result(stmt) (!mysqlnd_stmt_field_count((stmt)) ? PASS:((stmt)->m->background_store_result((stmt) TSRMLS_CC)? PASS:FAIL))
#define mysqlnd_stmt_get_result(stmt) (stmt)->m->get_result((stmt) TSRMLS_CC)
#define mysqlnd_stmt_data_seek(stmt, row) (stmt)->m->seek_data((stmt), (row) TSRMLS_CC)
#define mysqlnd_stmt_prepare(stmt, q, qlen) (stmt)->m->prepare((stmt), (q), (qlen) TSRMLS_CC)
diff --git a/ext/mysqlnd/mysqlnd_enum_n_def.h b/ext/mysqlnd/mysqlnd_enum_n_def.h
index b5e773f718..c2aa48cdea 100644
--- a/ext/mysqlnd/mysqlnd_enum_n_def.h
+++ b/ext/mysqlnd/mysqlnd_enum_n_def.h
@@ -22,6 +22,7 @@
#ifndef MYSQLND_ENUM_N_DEF_H
#define MYSQLND_ENUM_N_DEF_H
+#define MYSQLND_MAX_PACKET_SIZE (256L*256L*256L-1)
#define MYSQLND_ERRMSG_SIZE 512
#define MYSQLND_SQLSTATE_LENGTH 5
diff --git a/ext/mysqlnd/mysqlnd_libmysql_compat.h b/ext/mysqlnd/mysqlnd_libmysql_compat.h
index 546bd12693..533a3bcd65 100644
--- a/ext/mysqlnd/mysqlnd_libmysql_compat.h
+++ b/ext/mysqlnd/mysqlnd_libmysql_compat.h
@@ -108,6 +108,7 @@
#define mysql_free_result(r) mysqlnd_free_result((r), FALSE)
#define mysql_store_result(r) mysqlnd_store_result((r))
#define mysql_use_result(r) mysqlnd_use_result((r))
+#define mysql_async_store_result(r) mysqlnd_async_store_result((r))
#define mysql_thread_id(r) mysqlnd_thread_id((r))
#define mysql_get_client_info() mysqlnd_get_client_info()
#define mysql_get_client_version() mysqlnd_get_client_version()
@@ -116,6 +117,6 @@
#define mysql_get_server_info(r) mysqlnd_get_server_info((r))
#define mysql_get_server_version(r) mysqlnd_get_server_version((r))
#define mysql_warning_count(r) mysqlnd_warning_count((r))
-#define mysql_eof(r) (((r)->unbuf && (r)->unbuf->eof_reached) || (r)->data)
+#define mysql_eof(r) (((r)->unbuf && (r)->unbuf->eof_reached) || (r)->stored_data)
#endif /* MYSQLND_LIBMYSQL_COMPAT_H */
diff --git a/ext/mysqlnd/mysqlnd_priv.h b/ext/mysqlnd/mysqlnd_priv.h
index 9762a60542..7fcc2cd769 100644
--- a/ext/mysqlnd/mysqlnd_priv.h
+++ b/ext/mysqlnd/mysqlnd_priv.h
@@ -156,6 +156,14 @@
#define SET_STMT_ERROR(stmt, a, b, c) SET_CLIENT_ERROR(stmt->error_info, a, b, c)
+#ifdef ZTS
+#define CONN_GET_STATE(c) (c)->m->get_state((c) TSRMLS_CC)
+#define CONN_SET_STATE(c, s) (c)->m->set_state((c), (s) TSRMLS_CC)
+#else
+#define CONN_GET_STATE(c) (c)->state
+#define CONN_SET_STATE(c, s) (c)->state = s
+#endif
+
/* PS stuff */
typedef void (*ps_field_fetch_func)(zval *zv, const MYSQLND_FIELD * const field,
@@ -175,6 +183,8 @@ extern struct st_mysqlnd_perm_bind mysqlnd_ps_fetch_functions[MYSQL_TYPE_LAST +
extern const char * mysqlnd_out_of_sync;
extern const char * mysqlnd_server_gone;
+extern MYSQLND_MEMORY_POOL mysqlnd_memory_pool;
+
enum_func_status mysqlnd_handle_local_infile(MYSQLND *conn, const char *filename, zend_bool *is_warning TSRMLS_DC);
diff --git a/ext/mysqlnd/mysqlnd_ps.c b/ext/mysqlnd/mysqlnd_ps.c
index 3f06f4beb0..ca5be769c0 100644
--- a/ext/mysqlnd/mysqlnd_ps.c
+++ b/ext/mysqlnd/mysqlnd_ps.c
@@ -83,7 +83,74 @@ MYSQLND_METHOD(mysqlnd_stmt, store_result)(MYSQLND_STMT * const stmt TSRMLS_DC)
}
/* Nothing to store for UPSERT/LOAD DATA*/
- if (conn->state != CONN_FETCHING_DATA ||
+ if (CONN_GET_STATE(conn) != CONN_FETCHING_DATA ||
+ stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE)
+ {
+ SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
+ UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
+ DBG_RETURN(NULL);
+ }
+
+ stmt->default_rset_handler = stmt->m->store_result;
+
+ SET_EMPTY_ERROR(stmt->error_info);
+ SET_EMPTY_ERROR(stmt->conn->error_info);
+ MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_PS_BUFFERED_SETS);
+
+ result = stmt->result;
+ result->type = MYSQLND_RES_PS_BUF;
+ result->m.fetch_row = mysqlnd_fetch_stmt_row_buffered;
+ result->m.fetch_lengths = NULL;/* makes no sense */
+ result->zval_cache = mysqlnd_palloc_get_thd_cache_reference(conn->zval_cache);
+
+ /* Create room for 'next_extend' rows */
+
+ ret = mysqlnd_store_result_fetch_data(conn, result, result->meta,
+ TRUE, to_cache TSRMLS_CC);
+
+ if (PASS == ret) {
+ /* libmysql API docs say it should be so for SELECT statements */
+ stmt->upsert_status.affected_rows = stmt->result->stored_data->row_count;
+
+ stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED;
+ } else {
+ conn->error_info = result->stored_data->error_info;
+ stmt->result->m.free_result_contents(stmt->result TSRMLS_CC);
+ mnd_efree(stmt->result);
+ stmt->result = NULL;
+ stmt->state = MYSQLND_STMT_PREPARED;
+ }
+
+ DBG_RETURN(result);
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_stmt::background_store_result */
+static MYSQLND_RES *
+MYSQLND_METHOD(mysqlnd_stmt, background_store_result)(MYSQLND_STMT * const stmt TSRMLS_DC)
+{
+ enum_func_status ret;
+ MYSQLND *conn = stmt->conn;
+ MYSQLND_RES *result;
+ zend_bool to_cache = FALSE;
+
+ DBG_ENTER("mysqlnd_stmt::store_result");
+ DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
+
+ /* be compliant with libmysql - NULL will turn */
+ if (!stmt->field_count) {
+ DBG_RETURN(NULL);
+ }
+
+ if (stmt->cursor_exists) {
+ /* Silently convert buffered to unbuffered, for now */
+ MYSQLND_RES * res = stmt->m->use_result(stmt TSRMLS_CC);
+ DBG_RETURN(res);
+ }
+
+ /* Nothing to store for UPSERT/LOAD DATA*/
+ if (CONN_GET_STATE(conn) != CONN_FETCHING_DATA ||
stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE)
{
SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
@@ -112,16 +179,15 @@ MYSQLND_METHOD(mysqlnd_stmt, store_result)(MYSQLND_STMT * const stmt TSRMLS_DC)
}
ret = mysqlnd_store_result_fetch_data(conn, result, result->meta,
- TRUE, stmt->update_max_length,
- to_cache TSRMLS_CC);
+ TRUE, to_cache TSRMLS_CC);
if (PASS == ret) {
/* libmysql API docs say it should be so for SELECT statements */
- stmt->upsert_status.affected_rows = stmt->result->data->row_count;
+ stmt->upsert_status.affected_rows = stmt->result->stored_data->row_count;
stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED;
} else {
- conn->error_info = result->data->error_info;
+ conn->error_info = result->stored_data->error_info;
stmt->result->m.free_result_contents(stmt->result TSRMLS_CC);
mnd_efree(stmt->result);
stmt->result = NULL;
@@ -132,7 +198,6 @@ MYSQLND_METHOD(mysqlnd_stmt, store_result)(MYSQLND_STMT * const stmt TSRMLS_DC)
}
/* }}} */
-
/* {{{ mysqlnd_stmt::get_result */
static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_stmt, get_result)(MYSQLND_STMT * const stmt TSRMLS_DC)
@@ -155,7 +220,7 @@ MYSQLND_METHOD(mysqlnd_stmt, get_result)(MYSQLND_STMT * const stmt TSRMLS_DC)
}
/* Nothing to store for UPSERT/LOAD DATA*/
- if (conn->state != CONN_FETCHING_DATA || stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE) {
+ if (CONN_GET_STATE(conn) != CONN_FETCHING_DATA || stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE) {
SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
DBG_RETURN(NULL);
@@ -170,14 +235,8 @@ MYSQLND_METHOD(mysqlnd_stmt, get_result)(MYSQLND_STMT * const stmt TSRMLS_DC)
result->meta = stmt->result->meta->m->clone_metadata(stmt->result->meta, FALSE TSRMLS_CC);
- /* Not set for SHOW statements at PREPARE stage */
- if (stmt->result->conn) {
- stmt->result->conn->m->free_reference(stmt->result->conn TSRMLS_CC);
- stmt->result->conn = NULL; /* store result does not reference the connection */
- }
-
if ((result = result->m.store_result(result, conn, TRUE TSRMLS_CC))) {
- stmt->upsert_status.affected_rows = result->data->row_count;
+ stmt->upsert_status.affected_rows = result->stored_data->row_count;
stmt->state = MYSQLND_STMT_PREPARED;
result->type = MYSQLND_RES_PS_BUF;
} else {
@@ -458,7 +517,7 @@ MYSQLND_METHOD(mysqlnd_stmt, execute)(MYSQLND_STMT * const stmt TSRMLS_DC)
if (ret == FAIL) {
stmt->error_info = conn->error_info;
stmt->upsert_status.affected_rows = conn->upsert_status.affected_rows;
- if (conn->state == CONN_QUIT_SENT) {
+ if (CONN_GET_STATE(conn) == CONN_QUIT_SENT) {
/* close the statement here, the connection has been closed */
}
} else {
@@ -500,7 +559,7 @@ MYSQLND_METHOD(mysqlnd_stmt, execute)(MYSQLND_STMT * const stmt TSRMLS_DC)
if (stmt->upsert_status.server_status & SERVER_STATUS_CURSOR_EXISTS) {
stmt->cursor_exists = TRUE;
- conn->state = CONN_READY;
+ CONN_SET_STATE(conn, CONN_READY);
/* Only cursor read */
stmt->default_rset_handler = stmt->m->use_result;
DBG_INF("use_result");
@@ -539,17 +598,45 @@ mysqlnd_fetch_stmt_row_buffered(MYSQLND_RES *result, void *param, unsigned int f
{
unsigned int i;
MYSQLND_STMT *stmt = (MYSQLND_STMT *) param;
+ uint field_count = result->meta->field_count;
+ MYSQLND_RES_BUFFERED *set = result->stored_data;
DBG_ENTER("mysqlnd_fetch_stmt_row_buffered");
DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
/* If we haven't read everything */
- if (result->data->data_cursor &&
- (result->data->data_cursor - result->data->data) < result->data->row_count)
+ if (set->data_cursor &&
+ (set->data_cursor - set->data) < (set->row_count * field_count))
{
/* The user could have skipped binding - don't crash*/
if (stmt->result_bind) {
- zval **current_row = *result->data->data_cursor;
+ zval **current_row = set->data_cursor;
+
+ if (NULL == current_row[0]) {
+ set->initialized_rows++;
+ uint64 row_num = (set->data_cursor - set->data) / field_count;
+ result->m.row_decoder(set->row_buffers[row_num],
+ current_row,
+ result->meta->field_count,
+ result->meta->fields,
+ result->conn TSRMLS_CC);
+ if (stmt->update_max_length) {
+ for (i = 0; i < result->field_count; i++) {
+ /*
+ NULL fields are 0 length, 0 is not more than 0
+ String of zero size, definitely can't be the next max_length.
+ Thus for NULL and zero-length we are quite efficient.
+ */
+ if (Z_TYPE_P(current_row[i]) >= IS_STRING) {
+ unsigned long len = Z_STRLEN_P(current_row[i]);
+ if (result->meta->fields[i].max_length < len) {
+ result->meta->fields[i].max_length = len;
+ }
+ }
+ }
+ }
+ }
+
for (i = 0; i < result->field_count; i++) {
/* Clean what we copied last time */
#ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
@@ -578,13 +665,13 @@ mysqlnd_fetch_stmt_row_buffered(MYSQLND_RES *result, void *param, unsigned int f
}
}
}
- result->data->data_cursor++;
+ set->data_cursor += field_count;
*fetched_anything = TRUE;
/* buffered result sets don't have a connection */
MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_PS_BUF);
DBG_INF("row fetched");
} else {
- result->data->data_cursor = NULL;
+ set->data_cursor = NULL;
*fetched_anything = FALSE;
DBG_INF("no more data");
}
@@ -612,7 +699,7 @@ mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int
DBG_INF("eof reached");
DBG_RETURN(PASS);
}
- if (result->conn->state != CONN_FETCHING_DATA) {
+ if (CONN_GET_STATE(result->conn) != CONN_FETCHING_DATA) {
SET_CLIENT_ERROR(result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
DBG_ERR("command out of sync");
@@ -638,6 +725,12 @@ mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int
row_packet->fields = NULL;
row_packet->row_buffer = NULL;
+ result->m.row_decoder(result->unbuf->last_row_buffer,
+ result->unbuf->last_row_data,
+ row_packet->field_count,
+ row_packet->fields_metadata,
+ result->conn TSRMLS_CC);
+
for (i = 0; i < field_count; i++) {
if (stmt->result_bind[i].bound == TRUE) {
zval *data = result->unbuf->last_row_data[i];
@@ -676,7 +769,7 @@ mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int
the bound variables. Thus we need to do part of what it does or Zend will
report leaks.
*/
- mnd_efree(row_packet->row_buffer);
+ row_packet->row_buffer->free_chunk(row_packet->row_buffer, TRUE TSRMLS_CC);
row_packet->row_buffer = NULL;
}
} else if (ret == FAIL) {
@@ -685,7 +778,7 @@ mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int
stmt->error_info = row_packet->error_info;
}
*fetched_anything = FALSE;
- result->conn->state = CONN_READY;
+ CONN_SET_STATE(result->conn, CONN_READY);
result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
} else if (row_packet->eof) {
DBG_INF("EOF");
@@ -698,9 +791,9 @@ mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int
destroying the result object
*/
if (result->conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) {
- result->conn->state = CONN_NEXT_RESULT_PENDING;
+ CONN_SET_STATE(result->conn, CONN_NEXT_RESULT_PENDING);
} else {
- result->conn->state = CONN_READY;
+ CONN_SET_STATE(result->conn, CONN_READY);
}
*fetched_anything = FALSE;
}
@@ -722,8 +815,8 @@ MYSQLND_METHOD(mysqlnd_stmt, use_result)(MYSQLND_STMT *stmt TSRMLS_DC)
DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
if (!stmt->field_count ||
- (!stmt->cursor_exists && conn->state != CONN_FETCHING_DATA) ||
- (stmt->cursor_exists && conn->state != CONN_READY) ||
+ (!stmt->cursor_exists && CONN_GET_STATE(conn) != CONN_FETCHING_DATA) ||
+ (stmt->cursor_exists && CONN_GET_STATE(conn) != CONN_READY) ||
(stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE))
{
SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
@@ -740,7 +833,6 @@ MYSQLND_METHOD(mysqlnd_stmt, use_result)(MYSQLND_STMT *stmt TSRMLS_DC)
result->m.use_result(stmt->result, TRUE TSRMLS_CC);
result->m.fetch_row = stmt->cursor_exists? mysqlnd_fetch_stmt_row_cursor:
mysqlnd_stmt_fetch_row_unbuffered;
-
stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED;
DBG_INF_FMT("%p", result);
@@ -798,9 +890,17 @@ mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, unsigned int fla
result->unbuf->last_row_data = row_packet->fields;
result->unbuf->last_row_buffer = row_packet->row_buffer;
+
+
row_packet->fields = NULL;
row_packet->row_buffer = NULL;
if (!row_packet->skip_extraction) {
+ result->m.row_decoder(result->unbuf->last_row_buffer,
+ result->unbuf->last_row_data,
+ row_packet->field_count,
+ row_packet->fields_metadata,
+ result->conn TSRMLS_CC);
+
/* If no result bind, do nothing. We consumed the data */
for (i = 0; i < field_count; i++) {
if (stmt->result_bind[i].bound == TRUE) {
@@ -833,7 +933,7 @@ mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, unsigned int fla
/* We asked for one row, the next one should be EOF, eat it */
ret = PACKET_READ(row_packet, result->conn);
if (row_packet->row_buffer) {
- mnd_efree(row_packet->row_buffer);
+ row_packet->row_buffer->free_chunk(row_packet->row_buffer, TRUE TSRMLS_CC);
row_packet->row_buffer = NULL;
}
MYSQLND_INC_CONN_STATISTIC(&stmt->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_PS_CURSOR);
@@ -961,7 +1061,7 @@ MYSQLND_METHOD(mysqlnd_stmt, reset)(MYSQLND_STMT * const stmt TSRMLS_DC)
/* Now the line should be free, if it wasn't */
int4store(cmd_buf, stmt->stmt_id);
- if (conn->state == CONN_READY &&
+ if (CONN_GET_STATE(conn) == CONN_READY &&
FAIL == (ret = mysqlnd_simple_command(conn, COM_STMT_RESET, (char *)cmd_buf,
sizeof(cmd_buf), PROT_OK_PACKET,
FALSE TSRMLS_CC))) {
@@ -1026,7 +1126,7 @@ MYSQLND_METHOD(mysqlnd_stmt, send_long_data)(MYSQLND_STMT * const stmt, unsigned
one by one to the wire.
*/
- if (conn->state == CONN_READY) {
+ if (CONN_GET_STATE(conn) == CONN_READY) {
stmt->param_bind[param_no].flags |= MYSQLND_PARAM_BIND_BLOB_USED;
cmd_buf = mnd_emalloc(packet_len = STMT_ID_LENGTH + 2 + length);
@@ -1149,6 +1249,8 @@ MYSQLND_METHOD(mysqlnd_stmt, bind_param)(MYSQLND_STMT * const stmt,
static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt, refresh_bind_param)(MYSQLND_STMT * const stmt TSRMLS_DC)
{
+ unsigned int i = 0;
+
DBG_ENTER("mysqlnd_stmt::refresh_bind_param");
DBG_INF_FMT("stmt=%lu param_count=%u", stmt->stmt_id, stmt->param_count);
@@ -1332,13 +1434,15 @@ MYSQLND_METHOD(mysqlnd_stmt, result_metadata)(MYSQLND_STMT * const stmt TSRMLS_D
DBG_ENTER("mysqlnd_stmt::result_metadata");
DBG_INF_FMT("stmt=%u field_count=%u", stmt->stmt_id, stmt->field_count);
- if (!stmt->field_count || !stmt->conn || !stmt->result ||
- !stmt->result->meta)
- {
+ if (!stmt->field_count || !stmt->conn || !stmt->result || !stmt->result->meta) {
DBG_INF("NULL");
DBG_RETURN(NULL);
}
+ if (stmt->update_max_length && stmt->result->stored_data) {
+ /* stored result, we have to update the max_length before we clone the meta data :( */
+ mysqlnd_res_initialize_result_set_rest(stmt->result TSRMLS_CC);
+ }
/*
TODO: This implementation is kind of a hack,
find a better way to do it. In different functions I have put
@@ -1472,7 +1576,7 @@ MYSQLND_METHOD(mysqlnd_stmt, free_result)(MYSQLND_STMT * const stmt TSRMLS_DC)
stmt->state = MYSQLND_STMT_PREPARED;
/* Line is free! */
- stmt->conn->state = CONN_READY;
+ CONN_SET_STATE(stmt->conn, CONN_READY);
DBG_RETURN(PASS);
}
@@ -1622,7 +1726,7 @@ MYSQLND_METHOD(mysqlnd_stmt, close)(MYSQLND_STMT * const stmt, zend_bool implici
STAT_FREE_RESULT_EXPLICIT);
int4store(cmd_buf, stmt->stmt_id);
- if (conn->state == CONN_READY &&
+ if (CONN_GET_STATE(conn) == CONN_READY &&
FAIL == mysqlnd_simple_command(conn, COM_STMT_CLOSE, (char *)cmd_buf, sizeof(cmd_buf),
PROT_LAST /* COM_STMT_CLOSE doesn't send an OK packet*/,
FALSE TSRMLS_CC)) {
@@ -1679,6 +1783,7 @@ struct st_mysqlnd_stmt_methods mysqlnd_stmt_methods = {
MYSQLND_METHOD(mysqlnd_stmt, execute),
MYSQLND_METHOD(mysqlnd_stmt, use_result),
MYSQLND_METHOD(mysqlnd_stmt, store_result),
+ MYSQLND_METHOD(mysqlnd_stmt, background_store_result),
MYSQLND_METHOD(mysqlnd_stmt, get_result),
MYSQLND_METHOD(mysqlnd_stmt, free_result),
MYSQLND_METHOD(mysqlnd_stmt, data_seek),
diff --git a/ext/mysqlnd/mysqlnd_result.c b/ext/mysqlnd/mysqlnd_result.c
index dad993ea2a..dccfdd7056 100644
--- a/ext/mysqlnd/mysqlnd_result.c
+++ b/ext/mysqlnd/mysqlnd_result.c
@@ -32,6 +32,45 @@
#define MYSQLND_SILENT
+/* {{{ mysqlnd_res_initialize_result_set_rest */
+void mysqlnd_res_initialize_result_set_rest(MYSQLND_RES * const result TSRMLS_DC)
+{
+ uint i;
+ zval **data_cursor = result->stored_data->data;
+ zval **data_begin = result->stored_data->data;
+ uint field_count = result->meta->field_count;
+ uint row_count = result->stored_data->row_count;
+ if (!data_cursor || row_count == result->stored_data->initialized_rows) {
+ return;
+ }
+ while ((data_cursor - data_begin) < (row_count * field_count)) {
+ if (NULL == data_cursor[0]) {
+ result->stored_data->initialized_rows++;
+ result->m.row_decoder(
+ result->stored_data->row_buffers[(data_cursor - data_begin) / field_count],
+ data_cursor,
+ result->meta->field_count,
+ result->meta->fields,
+ result->conn TSRMLS_CC);
+ for (i = 0; i < result->field_count; i++) {
+ /*
+ NULL fields are 0 length, 0 is not more than 0
+ String of zero size, definitely can't be the next max_length.
+ Thus for NULL and zero-length we are quite efficient.
+ */
+ if (Z_TYPE_P(data_cursor[i]) >= IS_STRING) {
+ unsigned long len = Z_STRLEN_P(data_cursor[i]);
+ if (result->meta->fields[i].max_length < len) {
+ result->meta->fields[i].max_length = len;
+ }
+ }
+ }
+ }
+ data_cursor += field_count;
+ }
+}
+/* }}} */
+
/* {{{ mysqlnd_unbuffered_free_last_data */
void mysqlnd_unbuffered_free_last_data(MYSQLND_RES *result TSRMLS_DC)
@@ -70,7 +109,7 @@ void mysqlnd_unbuffered_free_last_data(MYSQLND_RES *result TSRMLS_DC)
}
if (unbuf->last_row_buffer) {
/* Nothing points to this buffer now, free it */
- efree(unbuf->last_row_buffer);
+ unbuf->last_row_buffer->free_chunk(unbuf->last_row_buffer, TRUE TSRMLS_CC);
unbuf->last_row_buffer = NULL;
}
@@ -82,21 +121,24 @@ void mysqlnd_unbuffered_free_last_data(MYSQLND_RES *result TSRMLS_DC)
void mysqlnd_free_buffered_data(MYSQLND_RES *result TSRMLS_DC)
{
MYSQLND_THD_ZVAL_PCACHE *zval_cache = result->zval_cache;
- MYSQLND_RES_BUFFERED *set = result->data;
+ MYSQLND_RES_BUFFERED *set = result->stored_data;
unsigned int field_count = result->field_count;
- unsigned int row;
+ int row;
DBG_ENTER("mysqlnd_free_buffered_data");
- DBG_INF_FMT("Freeing "MYSQLND_LLU_SPEC" row(s)", result->data->row_count);
+ DBG_INF_FMT("Freeing "MYSQLND_LLU_SPEC" row(s)", set->row_count);
DBG_INF_FMT("before: real_usage=%lu usage=%lu", zend_memory_usage(TRUE TSRMLS_CC), zend_memory_usage(FALSE TSRMLS_CC));
- for (row = 0; row < result->data->row_count; row++) {
- unsigned int col;
- zval **current_row = current_row = set->data[row];
- zend_uchar *current_buffer = set->row_buffers[row];
+ for (row = set->row_count - 1; row >= 0; row--) {
+ zval **current_row = set->data + row * field_count;
+ MYSQLND_MEMORY_POOL_CHUNK *current_buffer = set->row_buffers[row];
+ int col;
- for (col = 0; col < field_count; col++) {
+ for (col = field_count - 1; col >=0 ; col--) {
zend_bool copy_ctor_called;
+ if (current_row[0] == NULL) {
+ break;/* row that was never initialized */
+ }
mysqlnd_palloc_zval_ptr_dtor(&(current_row[col]), zval_cache,
result->type, &copy_ctor_called TSRMLS_CC);
#if MYSQLND_DEBUG_MEMORY
@@ -108,8 +150,7 @@ void mysqlnd_free_buffered_data(MYSQLND_RES *result TSRMLS_DC)
#if MYSQLND_DEBUG_MEMORY
DBG_INF("Freeing current_row & current_buffer");
#endif
- pefree(current_row, set->persistent);
- pefree(current_buffer, set->persistent);
+ current_buffer->free_chunk(current_buffer, TRUE TSRMLS_CC);
}
DBG_INF("Freeing data & row_buffer");
pefree(set->data, set->persistent);
@@ -121,6 +162,7 @@ void mysqlnd_free_buffered_data(MYSQLND_RES *result TSRMLS_DC)
if (set->qcache) {
mysqlnd_qcache_free_cache_reference(&set->qcache);
}
+
DBG_INF("Freeing set");
pefree(set, set->persistent);
@@ -129,22 +171,105 @@ void mysqlnd_free_buffered_data(MYSQLND_RES *result TSRMLS_DC)
}
/* }}} */
+#ifdef MYSQLND_THREADED
+/* {{{ mysqlnd_free_background_buffered_data */
+void mysqlnd_free_background_buffered_data(MYSQLND_RES *result TSRMLS_DC)
+{
+ MYSQLND_THD_ZVAL_PCACHE *zval_cache = result->zval_cache;
+ MYSQLND_RES_BG_BUFFERED *set = result->bg_stored_data;
+ unsigned int field_count = result->field_count;
+ int row;
+
+ DBG_ENTER("mysqlnd_free_buffered_data");
+ DBG_INF_FMT("Freeing "MYSQLND_LLU_SPEC" row(s)", set->row_count);
+
+ do {
+ tsrm_mutex_lock(set->LOCK);
+ if (set->bg_fetch_finished) {
+ tsrm_mutex_unlock(set->LOCK);
+ break;
+ }
+ tsrm_mutex_unlock(set->LOCK);
+#if HAVE_USLEEP
+ usleep(2000);
+#else
+ {
+ volatile int i;
+ for (i = 0; i < 1000; i++);
+ }
+#endif
+ } while (1);
+
+ DBG_INF_FMT("before: real_usage=%lu usage=%lu", zend_memory_usage(TRUE TSRMLS_CC), zend_memory_usage(FALSE TSRMLS_CC));
+ for (row = set->row_count - 1; row >= 0; row--) {
+ MYSQLND_MEMORY_POOL_CHUNK *current_buffer = set->row_buffers[row];
+ /* It could be the case that the user fetched no rows - then no set->data */
+ if (row < set->data_size && set->data[row]) {
+ zval **current_row = set->data[row];
+ unsigned int col;
+
+ for (col = 0; col < field_count; col++) {
+ zend_bool copy_ctor_called;
+ mysqlnd_palloc_zval_ptr_dtor(&(current_row[col]), zval_cache,
+ result->type, &copy_ctor_called TSRMLS_CC);
+#if MYSQLND_DEBUG_MEMORY
+ DBG_INF_FMT("Copy_ctor_called=%d", copy_ctor_called);
+#endif
+ MYSQLND_INC_GLOBAL_STATISTIC(copy_ctor_called? STAT_COPY_ON_WRITE_PERFORMED:
+ STAT_COPY_ON_WRITE_SAVED);
+ }
+#if MYSQLND_DEBUG_MEMORY
+ DBG_INF("Freeing current_row & current_buffer");
+#endif
+ pefree(current_row, set->persistent);
+ }
+ current_buffer->free_chunk(current_buffer, TRUE TSRMLS_CC);
+ }
+ DBG_INF("Freeing data & row_buffer");
+ pefree(set->data, set->persistent);
+ pefree(set->row_buffers, set->persistent);
+ set->data = NULL;
+ set->row_buffers = NULL;
+ set->data_cursor = NULL;
+ set->row_count = 0;
+ if (set->qcache) {
+ mysqlnd_qcache_free_cache_reference(&set->qcache);
+ }
+
+ if (set->LOCK) {
+ tsrm_mutex_free(set->LOCK);
+ }
+
+ DBG_INF("Freeing set");
+ pefree(set, set->persistent);
+
+ DBG_INF_FMT("after: real_usage=%lu usage=%lu", zend_memory_usage(TRUE TSRMLS_CC), zend_memory_usage(FALSE TSRMLS_CC));
+ DBG_VOID_RETURN;
+}
+/* }}} */
+#endif /* MYSQL_THREADING */
/* {{{ mysqlnd_res::free_result_buffers */
void
MYSQLND_METHOD(mysqlnd_res, free_result_buffers)(MYSQLND_RES *result TSRMLS_DC)
{
DBG_ENTER("mysqlnd_res::free_result_buffers");
- DBG_INF_FMT("%s", result->unbuf? "unbuffered":(result->data? "buffered":"unknown"));
+ DBG_INF_FMT("%s", result->unbuf? "unbuffered":(result->stored_data? "buffered":"unknown"));
if (result->unbuf) {
mysqlnd_unbuffered_free_last_data(result TSRMLS_CC);
efree(result->unbuf);
result->unbuf = NULL;
- } else if (result->data) {
+ } else if (result->stored_data) {
mysqlnd_free_buffered_data(result TSRMLS_CC);
- result->data = NULL;
+ result->stored_data = NULL;
}
+#ifdef MYSQLND_THREADED
+ else if (result->bg_stored_data) {
+ mysqlnd_free_background_buffered_data(result TSRMLS_CC);
+ result->bg_stored_data = NULL;
+ }
+#endif
if (result->lengths) {
efree(result->lengths);
@@ -193,11 +318,6 @@ static
void mysqlnd_internal_free_result(MYSQLND_RES *result TSRMLS_DC)
{
DBG_ENTER("mysqlnd_internal_free_result");
- /*
- result->conn is an address if this is an unbuffered query.
- In this case, decrement the reference counter in the connection
- object and if needed free the latter.
- */
if (result->conn) {
result->conn->m->free_reference(result->conn TSRMLS_CC);
result->conn = NULL;
@@ -295,9 +415,9 @@ mysqlnd_query_read_result_set_header(MYSQLND *conn, MYSQLND_STMT *stmt TSRMLS_DC
zend_bool is_warning;
DBG_INF("LOAD DATA");
conn->last_query_type = QUERY_LOAD_LOCAL;
- conn->state = CONN_SENDING_LOAD_DATA;
+ CONN_SET_STATE(conn, CONN_SENDING_LOAD_DATA);
ret = mysqlnd_handle_local_infile(conn, rset_header.info_or_local_file, &is_warning TSRMLS_CC);
- conn->state = (ret == PASS || is_warning == TRUE)? CONN_READY:CONN_QUIT_SENT;
+ CONN_SET_STATE(conn, (ret == PASS || is_warning == TRUE)? CONN_READY:CONN_QUIT_SENT);
MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_NON_RSET_QUERY);
break;
}
@@ -314,9 +434,9 @@ mysqlnd_query_read_result_set_header(MYSQLND *conn, MYSQLND_STMT *stmt TSRMLS_DC
conn->persistent);
/* Result set can follow UPSERT statement, check server_status */
if (conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) {
- conn->state = CONN_NEXT_RESULT_PENDING;
+ CONN_SET_STATE(conn, CONN_NEXT_RESULT_PENDING);
} else {
- conn->state = CONN_READY;
+ CONN_SET_STATE(conn, CONN_READY);
}
ret = PASS;
MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_NON_RSET_QUERY);
@@ -332,7 +452,7 @@ mysqlnd_query_read_result_set_header(MYSQLND *conn, MYSQLND_STMT *stmt TSRMLS_DC
MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_RSET_QUERY);
memset(&conn->upsert_status, 0, sizeof(conn->upsert_status));
conn->last_query_type = QUERY_SELECT;
- conn->state = CONN_FETCHING_DATA;
+ CONN_SET_STATE(conn, CONN_FETCHING_DATA);
/* PS has already allocated it */
if (!stmt) {
conn->field_count = rset_header.field_count;
@@ -405,11 +525,11 @@ mysqlnd_query_read_result_set_header(MYSQLND *conn, MYSQLND_STMT *stmt TSRMLS_DC
stat = STAT_NO_INDEX_USED;
}
if (stat != STAT_LAST) {
- char *backtrace = mysqlnd_get_backtrace(TSRMLS_C);
#if A0
+ char *backtrace = mysqlnd_get_backtrace(TSRMLS_C);
php_log_err(backtrace TSRMLS_CC);
-#endif
efree(backtrace);
+#endif
MYSQLND_INC_CONN_STATISTIC(&conn->stats, stat);
}
}
@@ -440,6 +560,7 @@ unsigned long * mysqlnd_fetch_lengths_buffered(MYSQLND_RES * const result)
{
unsigned int i;
zval **previous_row;
+ MYSQLND_RES_BUFFERED *set = result->stored_data;
/*
If:
@@ -447,15 +568,52 @@ unsigned long * mysqlnd_fetch_lengths_buffered(MYSQLND_RES * const result)
- first row has not been read
- last_row has been read
*/
- if (result->data->data_cursor == NULL ||
- result->data->data_cursor == result->data->data ||
- ((result->data->data_cursor - result->data->data) > result->data->row_count))
+ if (set->data_cursor == NULL ||
+ set->data_cursor == set->data ||
+ ((set->data_cursor - set->data) > (set->row_count * result->meta->field_count) ))
{
return NULL;/* No rows or no more rows */
}
- previous_row = *(result->data->data_cursor - 1);
- for (i = 0; i < result->field_count; i++) {
+ previous_row = set->data_cursor - result->meta->field_count;
+ for (i = 0; i < result->meta->field_count; i++) {
+ result->lengths[i] = (Z_TYPE_P(previous_row[i]) == IS_NULL)? 0:Z_STRLEN_P(previous_row[i]);
+ }
+
+ return result->lengths;
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_fetch_lengths_async_buffered */
+/*
+ Do lazy initialization for buffered results. As PHP strings have
+ length inside, this function makes not much sense in the context
+ of PHP, to be called as separate function. But let's have it for
+ completeness.
+*/
+static
+unsigned long * mysqlnd_fetch_lengths_async_buffered(MYSQLND_RES * const result)
+{
+ int i;
+ zval **previous_row;
+ MYSQLND_RES_BG_BUFFERED *set = result->bg_stored_data;
+
+ /*
+ If:
+ - unbuffered result
+ - first row has not been read
+ - last_row has been read
+ */
+ if (set->data_cursor == NULL ||
+ set->data_cursor == set->data ||
+ ((set->data_cursor - set->data) > set->row_count))
+ {
+ return NULL;/* No rows or no more rows */
+ }
+
+ previous_row = *(set->data_cursor - 1);
+ for (i = 0; i < result->meta->field_count; i++) {
result->lengths[i] = (Z_TYPE_P(previous_row[i]) == IS_NULL)? 0:Z_STRLEN_P(previous_row[i]);
}
@@ -492,13 +650,13 @@ mysqlnd_fetch_row_unbuffered_c(MYSQLND_RES *result TSRMLS_DC)
php_mysql_packet_row *row_packet = result->row_packet;
unsigned long *lengths = result->lengths;
- DBG_ENTER("mysqlnd_fetch_row_unbuffered");
+ DBG_ENTER("mysqlnd_fetch_row_unbuffered_c");
if (result->unbuf->eof_reached) {
/* No more rows obviously */
DBG_RETURN(retrow);
}
- if (result->conn->state != CONN_FETCHING_DATA) {
+ if (CONN_GET_STATE(result->conn) != CONN_FETCHING_DATA) {
SET_CLIENT_ERROR(result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
DBG_RETURN(retrow);
@@ -511,12 +669,8 @@ mysqlnd_fetch_row_unbuffered_c(MYSQLND_RES *result TSRMLS_DC)
mysqlnd_unbuffered_free_last_data() before it. The function returns always true.
*/
if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) {
- MYSQLND_FIELD *field = result->meta->fields;
-
result->unbuf->row_count++;
- retrow = mnd_malloc(result->field_count * sizeof(char *));
-
mysqlnd_unbuffered_free_last_data(result TSRMLS_CC);
result->unbuf->last_row_data = row_packet->fields;
@@ -526,25 +680,38 @@ mysqlnd_fetch_row_unbuffered_c(MYSQLND_RES *result TSRMLS_DC)
MYSQLND_INC_CONN_STATISTIC(&result->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF);
- for (i = 0; i < field_count; i++, field++) {
- zval *data = result->unbuf->last_row_data[i];
- int len;
+ if (!row_packet->skip_extraction) {
+ MYSQLND_FIELD *field = result->meta->fields;
+ struct mysqlnd_field_hash_key *zend_hash_key = result->meta->zend_hash_keys;
+
+ result->m.row_decoder(result->unbuf->last_row_buffer,
+ result->unbuf->last_row_data,
+ row_packet->field_count,
+ row_packet->fields_metadata,
+ result->conn TSRMLS_CC);
- if (Z_TYPE_P(data) != IS_NULL) {
- convert_to_string(data);
- retrow[i] = Z_STRVAL_P(data);
- len = Z_STRLEN_P(data);
- } else {
- retrow[i] = NULL;
- len = 0;
- }
+ retrow = mnd_malloc(result->field_count * sizeof(char *));
- if (lengths) {
- lengths[i] = len;
- }
+ for (i = 0; i < field_count; i++, field++, zend_hash_key++) {
+ zval *data = result->unbuf->last_row_data[i];
+ int len;
+
+ if (Z_TYPE_P(data) != IS_NULL) {
+ convert_to_string(data);
+ retrow[i] = Z_STRVAL_P(data);
+ len = Z_STRLEN_P(data);
+ } else {
+ retrow[i] = NULL;
+ len = 0;
+ }
+
+ if (lengths) {
+ lengths[i] = len;
+ }
- if (field->max_length < len) {
- field->max_length = len;
+ if (field->max_length < len) {
+ field->max_length = len;
+ }
}
}
} else if (ret == FAIL) {
@@ -552,7 +719,7 @@ mysqlnd_fetch_row_unbuffered_c(MYSQLND_RES *result TSRMLS_DC)
result->conn->error_info = row_packet->error_info;
DBG_ERR_FMT("errorno=%d error=%s", row_packet->error_info.error_no, row_packet->error_info.error);
}
- result->conn->state = CONN_READY;
+ CONN_SET_STATE(result->conn, CONN_READY);
result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
} else if (row_packet->eof) {
/* Mark the connection as usable again */
@@ -565,9 +732,9 @@ mysqlnd_fetch_row_unbuffered_c(MYSQLND_RES *result TSRMLS_DC)
destroying the result object
*/
if (result->conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) {
- result->conn->state = CONN_NEXT_RESULT_PENDING;
+ CONN_SET_STATE(result->conn, CONN_NEXT_RESULT_PENDING);
} else {
- result->conn->state = CONN_READY;
+ CONN_SET_STATE(result->conn, CONN_READY);
}
mysqlnd_unbuffered_free_last_data(result TSRMLS_CC);
}
@@ -597,7 +764,7 @@ mysqlnd_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int flag
*fetched_anything = FALSE;
DBG_RETURN(PASS);
}
- if (result->conn->state != CONN_FETCHING_DATA) {
+ if (CONN_GET_STATE(result->conn) != CONN_FETCHING_DATA) {
SET_CLIENT_ERROR(result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
DBG_RETURN(FAIL);
@@ -620,6 +787,7 @@ mysqlnd_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int flag
row_packet->fields = NULL;
row_packet->row_buffer = NULL;
+
MYSQLND_INC_CONN_STATISTIC(&result->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF);
if (!row_packet->skip_extraction) {
@@ -627,6 +795,12 @@ mysqlnd_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int flag
MYSQLND_FIELD *field = result->meta->fields;
struct mysqlnd_field_hash_key *zend_hash_key = result->meta->zend_hash_keys;
+ result->m.row_decoder(result->unbuf->last_row_buffer,
+ result->unbuf->last_row_data,
+ row_packet->field_count,
+ row_packet->fields_metadata,
+ result->conn TSRMLS_CC);
+
for (i = 0; i < field_count; i++, field++, zend_hash_key++) {
zval *data = result->unbuf->last_row_data[i];
int len = (Z_TYPE_P(data) == IS_NULL)? 0:Z_STRLEN_P(data);
@@ -686,7 +860,7 @@ mysqlnd_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int flag
DBG_ERR_FMT("errorno=%d error=%s", row_packet->error_info.error_no, row_packet->error_info.error);
}
*fetched_anything = FALSE;
- result->conn->state = CONN_READY;
+ CONN_SET_STATE(result->conn, CONN_READY);
result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
} else if (row_packet->eof) {
/* Mark the connection as usable again */
@@ -699,9 +873,9 @@ mysqlnd_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int flag
destroying the result object
*/
if (result->conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) {
- result->conn->state = CONN_NEXT_RESULT_PENDING;
+ CONN_SET_STATE(result->conn, CONN_NEXT_RESULT_PENDING);
} else {
- result->conn->state = CONN_READY;
+ CONN_SET_STATE(result->conn, CONN_READY);
}
mysqlnd_unbuffered_free_last_data(result TSRMLS_CC);
*fetched_anything = FALSE;
@@ -727,13 +901,14 @@ MYSQLND_METHOD(mysqlnd_res, use_result)(MYSQLND_RES * const result, zend_bool ps
result->m.fetch_row = result->m.fetch_row_normal_unbuffered;
result->m.fetch_lengths = mysqlnd_fetch_lengths_unbuffered;
result->lengths = mnd_ecalloc(result->field_count, sizeof(unsigned long));
+ result->m.row_decoder = php_mysqlnd_rowp_read_text_protocol;
} else {
result->type = MYSQLND_RES_PS_UNBUF;
/* result->m.fetch_row() will be set in mysqlnd_ps.c */
result->m.fetch_lengths = NULL; /* makes no sense */
result->lengths = NULL;
+ result->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol;
}
-
result->unbuf = mnd_ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED));
/*
@@ -758,19 +933,47 @@ static MYSQLND_ROW_C
mysqlnd_fetch_row_buffered_c(MYSQLND_RES *result TSRMLS_DC)
{
MYSQLND_ROW_C ret = NULL;
- unsigned int i;
+ MYSQLND_RES_BUFFERED *set = result->stored_data;
DBG_ENTER("mysqlnd_fetch_row_buffered_c");
/* If we haven't read everything */
- if (result->data->data_cursor &&
- (result->data->data_cursor - result->data->data) < result->data->row_count)
+ if (set->data_cursor &&
+ (set->data_cursor - set->data) < (set->row_count * result->meta->field_count))
{
- zval **current_row = *result->data->data_cursor;
+ zval **current_row = set->data_cursor;
+ MYSQLND_FIELD *field = result->meta->fields;
+ struct mysqlnd_field_hash_key *zend_hash_key = result->meta->zend_hash_keys;
+ unsigned int i;
+
+ if (NULL == current_row[0]) {
+ set->initialized_rows++;
+ uint64 row_num = (set->data_cursor - set->data) / result->meta->field_count;
+ result->m.row_decoder(set->row_buffers[row_num],
+ current_row,
+ result->meta->field_count,
+ result->meta->fields,
+ result->conn TSRMLS_CC);
+ for (i = 0; i < result->field_count; i++) {
+ /*
+ NULL fields are 0 length, 0 is not more than 0
+ String of zero size, definitely can't be the next max_length.
+ Thus for NULL and zero-length we are quite efficient.
+ */
+ if (Z_TYPE_P(current_row[i]) >= IS_STRING) {
+ unsigned long len = Z_STRLEN_P(current_row[i]);
+ if (field->max_length < len) {
+ field->max_length = len;
+ }
+ }
+ }
+ }
ret = mnd_malloc(result->field_count * sizeof(char *));
- for (i = 0; i < result->field_count; i++) {
+
+ for (i = 0; i < result->field_count; i++, field++, zend_hash_key++) {
zval *data = current_row[i];
+
if (Z_TYPE_P(data) != IS_NULL) {
convert_to_string(data);
ret[i] = Z_STRVAL_P(data);
@@ -778,10 +981,10 @@ mysqlnd_fetch_row_buffered_c(MYSQLND_RES *result TSRMLS_DC)
ret[i] = NULL;
}
}
- result->data->data_cursor++;
+ set->data_cursor += result->meta->field_count;
MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF);
} else {
- result->data->data_cursor = NULL;
+ set->data_cursor = NULL;
DBG_INF("EOF reached");
}
DBG_RETURN(ret);
@@ -796,18 +999,42 @@ mysqlnd_fetch_row_buffered(MYSQLND_RES *result, void *param, unsigned int flags,
{
unsigned int i;
zval *row = (zval *) param;
+ MYSQLND_RES_BUFFERED *set = result->stored_data;
DBG_ENTER("mysqlnd_fetch_row_buffered");
DBG_INF_FMT("flags=%u row=%p", flags, row);
/* If we haven't read everything */
- if (result->data->data_cursor &&
- (result->data->data_cursor - result->data->data) < result->data->row_count)
+ if (set->data_cursor &&
+ (set->data_cursor - set->data) < (set->row_count * result->meta->field_count))
{
- zval **current_row = *result->data->data_cursor;
+ zval **current_row = set->data_cursor;
MYSQLND_FIELD *field = result->meta->fields;
struct mysqlnd_field_hash_key *zend_hash_key = result->meta->zend_hash_keys;
+ if (NULL == current_row[0]) {
+ set->initialized_rows++;
+ uint64 row_num = (set->data_cursor - set->data) / result->meta->field_count;
+ result->m.row_decoder(set->row_buffers[row_num],
+ current_row,
+ result->meta->field_count,
+ result->meta->fields,
+ result->conn TSRMLS_CC);
+ for (i = 0; i < result->field_count; i++) {
+ /*
+ NULL fields are 0 length, 0 is not more than 0
+ String of zero size, definitely can't be the next max_length.
+ Thus for NULL and zero-length we are quite efficient.
+ */
+ if (Z_TYPE_P(current_row[i]) >= IS_STRING) {
+ unsigned long len = Z_STRLEN_P(current_row[i]);
+ if (field->max_length < len) {
+ field->max_length = len;
+ }
+ }
+ }
+ }
+
for (i = 0; i < result->field_count; i++, field++, zend_hash_key++) {
zval *data = current_row[i];
@@ -855,11 +1082,11 @@ mysqlnd_fetch_row_buffered(MYSQLND_RES *result, void *param, unsigned int flags,
}
}
}
- result->data->data_cursor++;
+ set->data_cursor += result->meta->field_count;
*fetched_anything = TRUE;
MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF);
} else {
- result->data->data_cursor = NULL;
+ set->data_cursor = NULL;
*fetched_anything = FALSE;
DBG_INF("EOF reached");
}
@@ -869,78 +1096,62 @@ mysqlnd_fetch_row_buffered(MYSQLND_RES *result, void *param, unsigned int flags,
/* }}} */
-#define STORE_RESULT_PREALLOCATED_SET 32
+#define STORE_RESULT_PREALLOCATED_SET 10
/* {{{ mysqlnd_store_result_fetch_data */
enum_func_status
mysqlnd_store_result_fetch_data(MYSQLND * const conn, MYSQLND_RES *result,
MYSQLND_RES_METADATA *meta,
zend_bool binary_protocol,
- zend_bool update_max_length,
zend_bool to_cache TSRMLS_DC)
{
enum_func_status ret;
- php_mysql_packet_row row_packet;
+ php_mysql_packet_row *row_packet;
unsigned int next_extend = STORE_RESULT_PREALLOCATED_SET, free_rows;
MYSQLND_RES_BUFFERED *set;
DBG_ENTER("mysqlnd_store_result_fetch_data");
- DBG_INF_FMT("conn=%llu binary_proto=%d update_max_len=%d to_cache=%d",
- conn->thread_id, binary_protocol, update_max_length, to_cache);
+ DBG_INF_FMT("conn=%llu binary_proto=%d to_cache=%d",
+ conn->thread_id, binary_protocol, to_cache);
free_rows = next_extend;
- result->data = set = mnd_pecalloc(1, sizeof(MYSQLND_RES_BUFFERED), to_cache);
- set->data = mnd_pemalloc(STORE_RESULT_PREALLOCATED_SET * sizeof(zval **), to_cache);
- set->row_buffers= mnd_pemalloc(STORE_RESULT_PREALLOCATED_SET * sizeof(zend_uchar *), to_cache);
+ result->stored_data = set = mnd_pecalloc(1, sizeof(MYSQLND_RES_BUFFERED), to_cache);
+ set->row_buffers= mnd_pemalloc(STORE_RESULT_PREALLOCATED_SET * sizeof(MYSQLND_MEMORY_POOL_CHUNK *), to_cache);
set->persistent = to_cache;
set->qcache = to_cache? mysqlnd_qcache_get_cache_reference(conn->qcache):NULL;
set->references = 1;
- PACKET_INIT_ALLOCA(row_packet, PROT_ROW_PACKET);
- row_packet.field_count = meta->field_count;
- row_packet.binary_protocol = binary_protocol;
- row_packet.fields_metadata = meta->fields;
- row_packet.bit_fields_count = meta->bit_fields_count;
- row_packet.bit_fields_total_len = meta->bit_fields_total_len;
- /* Let the row packet fill our buffer and skip additional malloc + memcpy */
- while (FAIL != (ret = PACKET_READ_ALLOCA(row_packet, conn)) && !row_packet.eof) {
- unsigned int i;
- zval **current_row;
+ PACKET_INIT(row_packet, PROT_ROW_PACKET, php_mysql_packet_row *);
+ row_packet->field_count = meta->field_count;
+ row_packet->binary_protocol = binary_protocol;
+ row_packet->fields_metadata = meta->fields;
+ row_packet->bit_fields_count = meta->bit_fields_count;
+ row_packet->bit_fields_total_len = meta->bit_fields_total_len;
+
+ row_packet->skip_extraction = TRUE; /* let php_mysqlnd_rowp_read() not allocate row_packet->fields, we will do it */
+
+ while (FAIL != (ret = PACKET_READ(row_packet, conn)) && !row_packet->eof) {
if (!free_rows) {
uint64 total_rows = free_rows = next_extend = next_extend * 5 / 3; /* extend with 33% */
total_rows += set->row_count;
- set->data = mnd_perealloc(set->data, total_rows * sizeof(zval **), set->persistent);
-
set->row_buffers = mnd_perealloc(set->row_buffers,
- total_rows * sizeof(zend_uchar *), set->persistent);
+ total_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *),
+ set->persistent);
}
free_rows--;
- current_row = set->data[set->row_count] = row_packet.fields;
- set->row_buffers[set->row_count] = row_packet.row_buffer;
+ set->row_buffers[set->row_count] = row_packet->row_buffer;
+
+ result->m.row_decoder = binary_protocol? php_mysqlnd_rowp_read_binary_protocol:
+ php_mysqlnd_rowp_read_text_protocol;
+
set->row_count++;
/* So row_packet's destructor function won't efree() it */
- row_packet.fields = NULL;
- row_packet.row_buffer = NULL;
-
+ row_packet->fields = NULL;
+ row_packet->row_buffer = NULL;
- if (update_max_length == TRUE) {
- for (i = 0; i < row_packet.field_count; i++) {
- /*
- NULL fields are 0 length, 0 is not more than 0
- String of zero size, definitely can't be the next max_length.
- Thus for NULL and zero-length we are quite efficient.
- */
- if (Z_TYPE_P(current_row[i]) >= IS_STRING) {
- unsigned long len = Z_STRLEN_P(current_row[i]);
- if (meta->fields[i].max_length < len) {
- meta->fields[i].max_length = len;
- }
- }
- }
- }
/*
No need to FREE_ALLOCA as we can reuse the
'lengths' and 'fields' arrays. For lengths its absolutely safe.
@@ -948,42 +1159,41 @@ mysqlnd_store_result_fetch_data(MYSQLND * const conn, MYSQLND_RES *result,
transfered above.
*/
}
+ /* Overflow ? */
+ set->data = mnd_pecalloc(set->row_count * meta->field_count, sizeof(zval *), to_cache);
MYSQLND_INC_CONN_STATISTIC_W_VALUE(&conn->stats,
binary_protocol? STAT_ROWS_BUFFERED_FROM_CLIENT_PS:
STAT_ROWS_BUFFERED_FROM_CLIENT_NORMAL,
set->row_count);
/* Finally clean */
- if (row_packet.eof) {
- conn->upsert_status.warning_count = row_packet.warning_count;
- conn->upsert_status.server_status = row_packet.server_status;
+ if (row_packet->eof) {
+ conn->upsert_status.warning_count = row_packet->warning_count;
+ conn->upsert_status.server_status = row_packet->server_status;
}
/* save some memory */
if (free_rows) {
- set->data = mnd_perealloc(set->data,
- (size_t) set->row_count * sizeof(zval **),
- set->persistent);
set->row_buffers = mnd_perealloc(set->row_buffers,
- (size_t) set->row_count * sizeof(zend_uchar *),
- set->persistent);
+ set->row_count * sizeof(MYSQLND_MEMORY_POOL_CHUNK *),
+ set->persistent);
}
if (conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) {
- conn->state = CONN_NEXT_RESULT_PENDING;
+ CONN_SET_STATE(conn, CONN_NEXT_RESULT_PENDING);
} else {
- conn->state = CONN_READY;
+ CONN_SET_STATE(conn, CONN_READY);
}
if (ret == FAIL) {
- set->error_info = row_packet.error_info;
+ set->error_info = row_packet->error_info;
} else {
/* Position at the first row */
set->data_cursor = set->data;
/* libmysql's documentation says it should be so for SELECT statements */
- conn->upsert_status.affected_rows = result->data->row_count;
+ conn->upsert_status.affected_rows = set->row_count;
}
- PACKET_FREE_ALLOCA(row_packet);
+ PACKET_FREE(row_packet);
DBG_INF_FMT("ret=%s row_count=%u warns=%u status=%u", ret == PASS? "PASS":"FAIL",
set->row_count, conn->upsert_status.warning_count, conn->upsert_status.server_status);
@@ -1004,22 +1214,23 @@ MYSQLND_METHOD(mysqlnd_res, store_result)(MYSQLND_RES * result,
DBG_ENTER("mysqlnd_res::store_result");
DBG_INF_FMT("conn=%d ps_protocol=%d", conn->thread_id, ps_protocol);
- result->conn = NULL; /* store result does not reference the connection */
+ /* We need the conn because we are doing lazy zval initialization in buffered_fetch_row */
+ result->conn = conn->m->get_reference(conn);
result->type = MYSQLND_RES_NORMAL;
result->m.fetch_row = result->m.fetch_row_normal_buffered;
result->m.fetch_lengths = mysqlnd_fetch_lengths_buffered;
- conn->state = CONN_FETCHING_DATA;
+ CONN_SET_STATE(conn, CONN_FETCHING_DATA);
result->lengths = mnd_ecalloc(result->field_count, sizeof(unsigned long));
ret = mysqlnd_store_result_fetch_data(conn, result, result->meta,
- ps_protocol, TRUE, to_cache TSRMLS_CC);
+ ps_protocol, to_cache TSRMLS_CC);
if (PASS == ret) {
/* libmysql's documentation says it should be so for SELECT statements */
- conn->upsert_status.affected_rows = result->data->row_count;
+ conn->upsert_status.affected_rows = result->stored_data->row_count;
} else {
- conn->error_info = result->data->error_info;
+ conn->error_info = result->stored_data->error_info;
result->m.free_result_internal(result TSRMLS_CC);
result = NULL;
}
@@ -1028,6 +1239,302 @@ MYSQLND_METHOD(mysqlnd_res, store_result)(MYSQLND_RES * result,
}
/* }}} */
+#ifdef MYSQLND_THREADED
+/* {{{ mysqlnd_fetch_row_async_buffered */
+static enum_func_status
+mysqlnd_fetch_row_async_buffered(MYSQLND_RES *result, void *param, unsigned int flags,
+ zend_bool *fetched_anything TSRMLS_DC)
+{
+ unsigned int i;
+ zval *row = (zval *) param;
+ MYSQLND_RES_BG_BUFFERED *set = result->bg_stored_data;
+
+ DBG_ENTER("mysqlnd_fetch_row_async_buffered");
+ DBG_INF_FMT("flags=%u row=%p", flags, row);
+
+ do {
+ tsrm_mutex_lock(set->LOCK);
+ if (set->bg_fetch_finished == TRUE) {
+ break;
+ }
+ if (!set->data_cursor || (set->data_cursor - set->data) < (set->row_count)) {
+#if HAVE_USLEEP
+ tsrm_mutex_unlock(set->LOCK);
+ usleep(2000);
+#else
+ volatile int i = 0;
+ for (int i = 0; i < 100; i++);
+#endif
+ } else {
+ break;
+ }
+ } while (1);
+
+ /* At the point we are still under LOCK */
+ if (set->data_cursor && (set->data_cursor - set->data) < (set->row_count)) {
+ uint64 row_num = set->data_cursor - set->data;
+ zval **current_row = *set->data_cursor++;
+ set->initialized_rows++;
+ /* We don't forget to release the lock */
+ tsrm_mutex_unlock(set->LOCK);
+
+ if (set->decode_in_foreground == TRUE) {
+ MYSQLND_MEMORY_POOL_CHUNK *current_buffer = set->row_buffers[row_num];
+ result->m.row_decoder(current_buffer,
+ current_row,
+ result->meta->field_count,
+ result->meta->fields,
+ result->conn TSRMLS_CC);
+
+ for (i = 0; i < result->field_count; i++) {
+ /*
+ NULL fields are 0 length, 0 is not more than 0
+ String of zero size, definitely can't be the next max_length.
+ Thus for NULL and zero-length we are quite efficient.
+ */
+ if (Z_TYPE_P(current_row[i]) >= IS_STRING) {
+ unsigned long len = Z_STRLEN_P(current_row[i]);
+ if (result->meta->fields[i].max_length < len) {
+ result->meta->fields[i].max_length = len;
+ }
+ }
+ }
+ }
+
+
+ for (i = 0; i < result->field_count; i++) {
+ zval *data = current_row[i];
+
+ /*
+ Let us later know what to do with this zval. If ref_count > 1, we will just
+ decrease it, otherwise free it. zval_ptr_dtor() make this very easy job.
+ */
+ Z_ADDREF_P(data);
+
+ if ((flags & MYSQLND_FETCH_BOTH) == MYSQLND_FETCH_BOTH) {
+ Z_ADDREF_P(data);
+ }
+ if (flags & MYSQLND_FETCH_NUM) {
+ zend_hash_next_index_insert(Z_ARRVAL_P(row), &data, sizeof(zval *), NULL);
+ }
+ if (flags & MYSQLND_FETCH_ASSOC) {
+ /* zend_hash_quick_update needs length + trailing zero */
+ /* QQ: Error handling ? */
+ /*
+ zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether
+ the index is a numeric and convert it to it. This however means constant
+ hashing of the column name, which is not needed as it can be precomputed.
+ */
+ if (result->meta->zend_hash_keys[i].is_numeric == FALSE) {
+#if PHP_MAJOR_VERSION >= 6
+ if (UG(unicode)) {
+ zend_u_hash_quick_update(Z_ARRVAL_P(row), IS_UNICODE,
+ result->meta->zend_hash_keys[i].ustr,
+ result->meta->zend_hash_keys[i].ulen + 1,
+ result->meta->zend_hash_keys[i].key,
+ (void *) &data, sizeof(zval *), NULL);
+ } else
+#endif
+ {
+ zend_hash_quick_update(Z_ARRVAL_P(row),
+ result->meta->fields[i].name,
+ result->meta->fields[i].name_length + 1,
+ result->meta->zend_hash_keys[i].key,
+ (void *) &data, sizeof(zval *), NULL);
+ }
+ } else {
+ zend_hash_index_update(Z_ARRVAL_P(row),
+ result->meta->zend_hash_keys[i].key,
+ (void *) &data, sizeof(zval *), NULL);
+ }
+ }
+ }
+ *fetched_anything = TRUE;
+ MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF);
+ } else {
+ set->data_cursor = NULL;
+ /* We don't forget to release the lock */
+ tsrm_mutex_unlock(set->LOCK);
+ *fetched_anything = FALSE;
+ DBG_INF("EOF reached");
+ }
+
+ DBG_INF_FMT("ret=PASS fetched=%d", *fetched_anything);
+ DBG_RETURN(PASS);
+}
+/* }}} */
+
+/* {{{ mysqlnd_background_store_result_fetch_data */
+enum_func_status
+mysqlnd_background_store_result_fetch_data(MYSQLND_RES *result TSRMLS_DC)
+{
+ enum_func_status ret;
+ php_mysql_packet_row *row_packet;
+ unsigned int next_extend = STORE_RESULT_PREALLOCATED_SET, free_rows;
+ MYSQLND_RES_BG_BUFFERED *set = result->bg_stored_data;
+ MYSQLND *conn = result->conn;
+
+ DBG_ENTER("mysqlnd_background_store_result_fetch_data");
+
+ free_rows = next_extend;
+
+ PACKET_INIT(row_packet, PROT_ROW_PACKET, php_mysql_packet_row *);
+ row_packet->field_count = result->meta->field_count;
+ row_packet->binary_protocol = result->m.row_decoder == php_mysqlnd_rowp_read_binary_protocol;
+ row_packet->fields_metadata = result->meta->fields;
+ row_packet->bit_fields_count = result->meta->bit_fields_count;
+ row_packet->bit_fields_total_len= result->meta->bit_fields_total_len;
+
+// row_packet->skip_extraction = TRUE; /* let php_mysqlnd_rowp_read() not allocate row_packet->fields, we will do it */
+
+ while (FAIL != (ret = PACKET_READ(row_packet, conn)) && !row_packet->eof) {
+ tsrm_mutex_lock(set->LOCK);
+ if (!free_rows) {
+ uint64 total_rows = free_rows = next_extend = next_extend * 5 / 3; /* extend with 33% */
+ uint64 old_size;
+ total_rows += set->row_count;
+
+ old_size = set->data_size;
+ set->data_size = total_rows;
+ set->data = mnd_perealloc(set->data, set->data_size * sizeof(zval **), set->persistent);
+// memset(set->data + old_size, 0, (set->data_size - old_size) * sizeof(zval **));
+ set->row_buffers = mnd_perealloc(set->row_buffers,
+ total_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *),
+ set->persistent);
+ }
+ set->row_buffers[set->row_count] = row_packet->row_buffer;
+ set->data[set->row_count] = row_packet->fields;
+
+ if (set->decode_in_foreground == FALSE) {
+ uint i;
+ result->m.row_decoder(set->row_buffers[set->row_count],
+ set->data[set->row_count],
+ result->meta->field_count,
+ result->meta->fields,
+ result->conn TSRMLS_CC);
+
+ for (i = 0; i < result->field_count; i++) {
+ /*
+ NULL fields are 0 length, 0 is not more than 0
+ String of zero size, definitely can't be the next max_length.
+ Thus for NULL and zero-length we are quite efficient.
+ */
+ if (Z_TYPE_P(set->data[set->row_count][i]) >= IS_STRING) {
+ unsigned long len = Z_STRLEN_P(set->data[set->row_count][i]);
+ if (result->meta->fields[i].max_length < len) {
+ result->meta->fields[i].max_length = len;
+ }
+ }
+ }
+ }
+ set->row_count++;
+
+ tsrm_mutex_unlock(set->LOCK);
+ free_rows--;
+
+ /* So row_packet's destructor function won't efree() it */
+ row_packet->row_buffer = NULL;
+ row_packet->fields = NULL;
+
+ /*
+ No need to FREE_ALLOCA as we can reuse the
+ 'lengths' and 'fields' arrays. For lengths its absolutely safe.
+ 'fields' is reused because the ownership of the strings has been
+ transfered above.
+ */
+ }
+// MYSQLND_INC_CONN_STATISTIC_W_VALUE(&conn->stats,
+// binary_protocol? STAT_ROWS_BUFFERED_FROM_CLIENT_PS:
+// STAT_ROWS_BUFFERED_FROM_CLIENT_NORMAL,
+// set->row_count);
+
+ tsrm_mutex_lock(set->LOCK);
+ /* Finally clean */
+ if (row_packet->eof) {
+ set->upsert_status.warning_count = row_packet->warning_count;
+ set->upsert_status.server_status = row_packet->server_status;
+ }
+ /* save some memory */
+ if (free_rows) {
+ set->data_size = set->row_count;
+ set->data = mnd_perealloc(set->data,
+ (size_t) set->data_size * sizeof(zval **), set->persistent);
+ set->row_buffers = mnd_perealloc(set->row_buffers,
+ set->row_count * sizeof(MYSQLND_MEMORY_POOL_CHUNK *),
+ set->persistent);
+ }
+ if (ret == FAIL) {
+ set->error_info = row_packet->error_info;
+ } else {
+ /* Position at the first row */
+ set->data_cursor = set->data;
+
+ /* libmysql's documentation says it should be so for SELECT statements */
+ conn->upsert_status.affected_rows = set->row_count;
+ set->upsert_status.affected_rows = set->row_count;
+ }
+ set->bg_fetch_finished = TRUE;
+ tsrm_mutex_unlock(set->LOCK);
+
+ PACKET_FREE(row_packet);
+
+ if (conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) {
+ CONN_SET_STATE(conn, CONN_NEXT_RESULT_PENDING);
+ } else {
+ CONN_SET_STATE(conn, CONN_READY);
+ }
+ DBG_INF_FMT("ret=%s row_count=%u warns=%u status=%u", ret == PASS? "PASS":"FAIL",
+ set->row_count, conn->upsert_status.warning_count, conn->upsert_status.server_status);
+ DBG_RETURN(ret);
+}
+/* }}} */
+#endif
+
+
+/* {{{ mysqlnd_res::background_store_result */
+MYSQLND_RES *
+MYSQLND_METHOD(mysqlnd_res, background_store_result)(MYSQLND_RES * result, MYSQLND * const conn, zend_bool ps TSRMLS_DC)
+{
+#ifndef MYSQLND_THREADED
+ return (result->m.store_result(result, conn, ps TSRMLS_CC));
+#else
+ enum_func_status ret;
+ zend_bool to_cache = FALSE;
+
+ DBG_ENTER("mysqlnd_res::background_store_result");
+ DBG_INF_FMT("conn=%d ps_protocol=%d", conn->thread_id, ps);
+
+ /* We need the conn because we are doing lazy zval initialization in buffered_fetch_row */
+ result->conn = conn->m->get_reference(conn);
+ result->type = MYSQLND_RES_NORMAL;
+ result->m.fetch_row = mysqlnd_fetch_row_async_buffered;
+ result->m.fetch_lengths = mysqlnd_fetch_lengths_async_buffered;
+
+ result->bg_stored_data = mnd_pecalloc(1, sizeof(MYSQLND_RES_BG_BUFFERED), to_cache);
+ result->bg_stored_data->data_size = STORE_RESULT_PREALLOCATED_SET;
+ result->bg_stored_data->data = mnd_pecalloc(result->bg_stored_data->data_size, sizeof(zval **), to_cache);
+ result->bg_stored_data->row_buffers = mnd_pemalloc(STORE_RESULT_PREALLOCATED_SET * sizeof(MYSQLND_MEMORY_POOL_CHUNK *), to_cache);
+ result->bg_stored_data->persistent = to_cache;
+ result->bg_stored_data->qcache = to_cache? mysqlnd_qcache_get_cache_reference(conn->qcache):NULL;
+ result->bg_stored_data->references = 1;
+
+ result->bg_stored_data->LOCK = tsrm_mutex_alloc();
+
+ result->m.row_decoder = ps? php_mysqlnd_rowp_read_binary_protocol:
+ php_mysqlnd_rowp_read_text_protocol;
+
+ CONN_SET_STATE(conn, CONN_FETCHING_DATA);
+ result->bg_stored_data->decode_in_foreground = FALSE;
+
+ result->lengths = mnd_ecalloc(result->field_count, sizeof(unsigned long));
+
+ ret = mysqlnd_background_store_result_fetch_data(result TSRMLS_CC);
+
+ DBG_RETURN(result);
+#endif
+}
+/* }}} */
+
/* {{{ mysqlnd_res::skip_result */
static enum_func_status
@@ -1041,7 +1548,7 @@ MYSQLND_METHOD(mysqlnd_res, skip_result)(MYSQLND_RES * const result TSRMLS_DC)
A PS could be prepared - there is metadata and thus a stmt->result but the
fetch_row function isn't actually set (NULL), thus we have to skip these.
*/
- if (!result->data && result->conn && result->unbuf &&
+ if (!result->stored_data && result->unbuf &&
!result->unbuf->eof_reached && result->m.fetch_row)
{
DBG_INF("skipping result");
@@ -1086,15 +1593,15 @@ MYSQLND_METHOD(mysqlnd_res, data_seek)(MYSQLND_RES *result, uint64 row TSRMLS_DC
DBG_ENTER("mysqlnd_res::data_seek");
DBG_INF_FMT("row=%lu", row);
- if (!result->data) {
+ if (!result->stored_data) {
return FAIL;
}
/* libmysql just moves to the end, it does traversing of a linked list */
- if (row >= result->data->row_count) {
- result->data->data_cursor = NULL;
+ if (row >= result->stored_data->row_count) {
+ result->stored_data->data_cursor = NULL;
} else {
- result->data->data_cursor = result->data->data + row;
+ result->stored_data->data_cursor = result->stored_data->data + row * result->meta->field_count;
}
DBG_RETURN(PASS);
@@ -1102,21 +1609,21 @@ MYSQLND_METHOD(mysqlnd_res, data_seek)(MYSQLND_RES *result, uint64 row TSRMLS_DC
/* }}} */
-/* {{{ mysqlnd_res::num_fields */
+/* {{{ mysqlnd_res::num_rows */
static uint64
-MYSQLND_METHOD(mysqlnd_res, num_rows)(const MYSQLND_RES * const res)
+MYSQLND_METHOD(mysqlnd_res, num_rows)(const MYSQLND_RES * const result)
{
/* Be compatible with libmysql. We count row_count, but will return 0 */
- return res->data? res->data->row_count:0;
+ return result->stored_data? result->stored_data->row_count:0;
}
/* }}} */
/* {{{ mysqlnd_res::num_fields */
static unsigned int
-MYSQLND_METHOD(mysqlnd_res, num_fields)(const MYSQLND_RES * const res)
+MYSQLND_METHOD(mysqlnd_res, num_fields)(const MYSQLND_RES * const result)
{
- return res->field_count;
+ return result->field_count;
}
/* }}} */
@@ -1126,18 +1633,33 @@ static MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res, fetch_field)(MYSQLND_RES * const result TSRMLS_DC)
{
DBG_ENTER("mysqlnd_res::fetch_field");
- DBG_RETURN(result->meta? result->meta->m->fetch_field(result->meta TSRMLS_CC):NULL);
+ if (result->meta) {
+ if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
+ /* we have to initialize the rest to get the updated max length */
+ mysqlnd_res_initialize_result_set_rest(result TSRMLS_CC);
+ }
+ DBG_RETURN(result->meta->m->fetch_field(result->meta TSRMLS_CC));
+ }
+ DBG_RETURN(NULL);
}
/* }}} */
/* {{{ mysqlnd_res::fetch_field_direct */
static MYSQLND_FIELD *
-MYSQLND_METHOD(mysqlnd_res, fetch_field_direct)(const MYSQLND_RES * const result,
+MYSQLND_METHOD(mysqlnd_res, fetch_field_direct)(MYSQLND_RES * const result,
MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC)
{
DBG_ENTER("mysqlnd_res::fetch_field_direct");
- DBG_RETURN(result->meta? result->meta->m->fetch_field_direct(result->meta, fieldnr TSRMLS_CC):NULL);
+ if (result->meta) {
+ if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
+ /* we have to initialized the rest to get the updated max length */
+ mysqlnd_res_initialize_result_set_rest(result TSRMLS_CC);
+ }
+ DBG_RETURN(result->meta->m->fetch_field_direct(result->meta, fieldnr TSRMLS_CC));
+ }
+
+ DBG_RETURN(NULL);
}
/* }}} */
@@ -1238,23 +1760,24 @@ MYSQLND_METHOD(mysqlnd_res, fetch_all)(MYSQLND_RES *result, unsigned int flags,
{
zval *row;
ulong i = 0;
+ MYSQLND_RES_BUFFERED *set = result->stored_data;
DBG_ENTER("mysqlnd_res::fetch_all");
DBG_INF_FMT("flags=%u", flags);
/* mysqlnd_res::fetch_all works with buffered resultsets only */
- if (result->conn || !result->data ||
- !result->data->row_count || !result->data->data_cursor ||
- result->data->data_cursor >= result->data->data + result->data->row_count)
+ if (!set ||
+ !set->row_count || !set->data_cursor ||
+ set->data_cursor >= set->data + set->row_count)
{
RETVAL_NULL();
DBG_VOID_RETURN;
}
- mysqlnd_array_init(return_value, (uint) result->data->row_count);
+ mysqlnd_array_init(return_value, (uint) set->row_count);
- while (result->data->data_cursor &&
- (result->data->data_cursor - result->data->data) < result->data->row_count)
+ while (set->data_cursor &&
+ (set->data_cursor - set->data) < (set->row_count * result->meta->field_count))
{
MAKE_STD_ZVAL(row);
mysqlnd_fetch_into(result, flags, row, MYSQLND_MYSQLI);
@@ -1266,7 +1789,7 @@ MYSQLND_METHOD(mysqlnd_res, fetch_all)(MYSQLND_RES *result, unsigned int flags,
/* }}} */
-/* {{{ mysqlnd_res::fetch_into */
+/* {{{ mysqlnd_res::fetch_field_data */
static void
MYSQLND_METHOD(mysqlnd_res, fetch_field_data)(MYSQLND_RES *result, unsigned int offset,
zval *return_value TSRMLS_DC)
@@ -1324,6 +1847,7 @@ MYSQLND_RES *mysqlnd_result_init(unsigned int field_count, MYSQLND_THD_ZVAL_PCAC
ret->m.use_result = MYSQLND_METHOD(mysqlnd_res, use_result);
ret->m.store_result = MYSQLND_METHOD(mysqlnd_res, store_result);
+ ret->m.background_store_result = MYSQLND_METHOD(mysqlnd_res, background_store_result);
ret->m.free_result = MYSQLND_METHOD(mysqlnd_res, free_result);
ret->m.seek_data = MYSQLND_METHOD(mysqlnd_res, data_seek);
ret->m.num_rows = MYSQLND_METHOD(mysqlnd_res, num_rows);
@@ -1345,6 +1869,7 @@ MYSQLND_RES *mysqlnd_result_init(unsigned int field_count, MYSQLND_THD_ZVAL_PCAC
ret->m.read_result_metadata = MYSQLND_METHOD(mysqlnd_res, read_result_metadata);
ret->m.fetch_row_normal_buffered = mysqlnd_fetch_row_buffered;
ret->m.fetch_row_normal_unbuffered = mysqlnd_fetch_row_unbuffered;
+ ret->m.row_decoder = NULL;
DBG_RETURN(ret);
}
diff --git a/ext/mysqlnd/mysqlnd_result.h b/ext/mysqlnd/mysqlnd_result.h
index 8135963671..570f1a5d0d 100644
--- a/ext/mysqlnd/mysqlnd_result.h
+++ b/ext/mysqlnd/mysqlnd_result.h
@@ -31,11 +31,14 @@ enum_func_status
mysqlnd_store_result_fetch_data(MYSQLND * const conn, MYSQLND_RES *result,
MYSQLND_RES_METADATA *meta,
zend_bool binary_protocol,
- zend_bool update_max_length,
zend_bool to_cache TSRMLS_DC);
enum_func_status mysqlnd_query_read_result_set_header(MYSQLND *conn, MYSQLND_STMT *stmt TSRMLS_DC);
+void mysqlnd_res_initialize_result_set_rest(MYSQLND_RES * const result TSRMLS_DC);
+
+
+enum_func_status mysqlnd_background_store_result_fetch_data(MYSQLND_RES *result TSRMLS_DC);
#endif /* MYSQLND_RESULT_H */
/*
diff --git a/ext/mysqlnd/mysqlnd_result_meta.c b/ext/mysqlnd/mysqlnd_result_meta.c
index fac775de29..56906514af 100644
--- a/ext/mysqlnd/mysqlnd_result_meta.c
+++ b/ext/mysqlnd/mysqlnd_result_meta.c
@@ -421,8 +421,8 @@ MYSQLND_RES_METADATA *mysqlnd_result_meta_init(unsigned int field_count TSRMLS_D
/* +1 is to have empty marker at the end */
ret = mnd_ecalloc(1, sizeof(MYSQLND_RES_METADATA));
ret->field_count = field_count;
- ret->fields = ecalloc(field_count + 1, sizeof(MYSQLND_FIELD));
- ret->zend_hash_keys = ecalloc(field_count, sizeof(struct mysqlnd_field_hash_key));
+ ret->fields = mnd_ecalloc(field_count + 1, sizeof(MYSQLND_FIELD));
+ ret->zend_hash_keys = mnd_ecalloc(field_count, sizeof(struct mysqlnd_field_hash_key));
ret->m = & mysqlnd_mysqlnd_res_meta_methods;
DBG_INF_FMT("meta=%p", ret);
diff --git a/ext/mysqlnd/mysqlnd_structs.h b/ext/mysqlnd/mysqlnd_structs.h
index 85785b14bd..4d81c287cc 100644
--- a/ext/mysqlnd/mysqlnd_structs.h
+++ b/ext/mysqlnd/mysqlnd_structs.h
@@ -23,6 +23,39 @@
#ifndef MYSQLND_STRUCTS_H
#define MYSQLND_STRUCTS_H
+typedef struct st_mysqlnd_memory_pool MYSQLND_MEMORY_POOL;
+typedef struct st_mysqlnd_memory_pool_chunk MYSQLND_MEMORY_POOL_CHUNK;
+typedef struct st_mysqlnd_memory_pool_chunk_llist MYSQLND_MEMORY_POOL_CHUNK_LLIST;
+
+
+#define MYSQLND_MEMORY_POOL_CHUNK_LIST_SIZE 100
+
+struct st_mysqlnd_memory_pool
+{
+ zend_uchar *arena;
+ uint refcount;
+ uint arena_size;
+ uint free_size;
+
+ MYSQLND_MEMORY_POOL_CHUNK* free_chunk_list[MYSQLND_MEMORY_POOL_CHUNK_LIST_SIZE];
+ uint free_chunk_list_elements;
+
+ MYSQLND_MEMORY_POOL_CHUNK* (*get_chunk)(MYSQLND_MEMORY_POOL * pool, uint size TSRMLS_DC);
+ void (*free_contents)(MYSQLND_MEMORY_POOL * pool TSRMLS_DC);
+};
+
+struct st_mysqlnd_memory_pool_chunk
+{
+ uint64 app;
+ MYSQLND_MEMORY_POOL *pool;
+ zend_uchar *ptr;
+ uint size;
+ void (*resize_chunk)(MYSQLND_MEMORY_POOL_CHUNK * chunk, uint size TSRMLS_DC);
+ void (*free_chunk)(MYSQLND_MEMORY_POOL_CHUNK * chunk, zend_bool cache_it TSRMLS_DC);
+ zend_bool from_pool;
+};
+
+
typedef struct st_mysqlnd_cmd_buffer
{
zend_uchar *buffer;
@@ -162,6 +195,7 @@ typedef struct st_mysqlnd_result_bind MYSQLND_RESULT_BIND;
typedef struct st_mysqlnd_result_metadata MYSQLND_RES_METADATA;
typedef struct st_mysqlnd_buffered_result MYSQLND_RES_BUFFERED;
+typedef struct st_mysqlnd_background_buffered_result MYSQLND_RES_BG_BUFFERED;
typedef struct st_mysqlnd_unbuffered_result MYSQLND_RES_UNBUFFERED;
typedef struct st_mysqlnd_debug MYSQLND_DEBUG;
@@ -205,6 +239,7 @@ struct st_mysqlnd_conn_methods
enum_func_status (*query)(MYSQLND *conn, const char *query, unsigned int query_len TSRMLS_DC);
MYSQLND_RES * (*use_result)(MYSQLND * const conn TSRMLS_DC);
MYSQLND_RES * (*store_result)(MYSQLND * const conn TSRMLS_DC);
+ MYSQLND_RES * (*background_store_result)(MYSQLND * const conn TSRMLS_DC);
enum_func_status (*next_result)(MYSQLND * const conn TSRMLS_DC);
zend_bool (*more_results)(const MYSQLND * const conn);
@@ -249,6 +284,8 @@ struct st_mysqlnd_conn_methods
MYSQLND * (*get_reference)(MYSQLND * const conn);
enum_func_status (*free_reference)(MYSQLND * const conn TSRMLS_DC);
+ enum mysqlnd_connection_state (*get_state)(MYSQLND * const conn TSRMLS_DC);
+ void (*set_state)(MYSQLND * const conn, enum mysqlnd_connection_state new_state TSRMLS_DC);
};
@@ -260,6 +297,7 @@ struct st_mysqlnd_res_methods
MYSQLND_RES * (*use_result)(MYSQLND_RES * const result, zend_bool ps_protocol TSRMLS_DC);
MYSQLND_RES * (*store_result)(MYSQLND_RES * result, MYSQLND * const conn, zend_bool ps TSRMLS_DC);
+ MYSQLND_RES * (*background_store_result)(MYSQLND_RES * result, MYSQLND * const conn, zend_bool ps TSRMLS_DC);
void (*fetch_into)(MYSQLND_RES *result, unsigned int flags, zval *return_value, enum_mysqlnd_extension ext TSRMLS_DC ZEND_FILE_LINE_DC);
MYSQLND_ROW_C (*fetch_row_c)(MYSQLND_RES *result TSRMLS_DC);
void (*fetch_all)(MYSQLND_RES *result, unsigned int flags, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC);
@@ -271,7 +309,7 @@ struct st_mysqlnd_res_methods
MYSQLND_FIELD_OFFSET (*seek_field)(MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET field_offset);
MYSQLND_FIELD_OFFSET (*field_tell)(const MYSQLND_RES * const result);
MYSQLND_FIELD * (*fetch_field)(MYSQLND_RES * const result TSRMLS_DC);
- MYSQLND_FIELD * (*fetch_field_direct)(const MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC);
+ MYSQLND_FIELD * (*fetch_field_direct)(MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC);
enum_func_status (*read_result_metadata)(MYSQLND_RES *result, MYSQLND *conn TSRMLS_DC);
unsigned long * (*fetch_lengths)(MYSQLND_RES * const result);
@@ -279,6 +317,9 @@ struct st_mysqlnd_res_methods
enum_func_status (*free_result)(MYSQLND_RES * result, zend_bool implicit TSRMLS_DC);
void (*free_result_internal)(MYSQLND_RES *result TSRMLS_DC);
void (*free_result_contents)(MYSQLND_RES *result TSRMLS_DC);
+
+ /* for decoding - binary or text protocol */
+ void (*row_decoder)(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, uint field_count, MYSQLND_FIELD *fields_metadata, MYSQLND *conn TSRMLS_DC);
};
@@ -299,6 +340,7 @@ struct st_mysqlnd_stmt_methods
enum_func_status (*execute)(MYSQLND_STMT * const stmt TSRMLS_DC);
MYSQLND_RES * (*use_result)(MYSQLND_STMT * const stmt TSRMLS_DC);
MYSQLND_RES * (*store_result)(MYSQLND_STMT * const stmt TSRMLS_DC);
+ MYSQLND_RES * (*background_store_result)(MYSQLND_STMT * const stmt TSRMLS_DC);
MYSQLND_RES * (*get_result)(MYSQLND_STMT * const stmt TSRMLS_DC);
enum_func_status (*free_result)(MYSQLND_STMT * const stmt TSRMLS_DC);
enum_func_status (*seek_data)(const MYSQLND_STMT * const stmt, uint64 row TSRMLS_DC);
@@ -405,6 +447,20 @@ struct st_mysqlnd_connection
/* stats */
MYSQLND_STATS stats;
+#ifdef ZTS
+ MUTEX_T LOCK_state;
+
+ pthread_cond_t COND_work_done;
+
+ pthread_mutex_t LOCK_work;
+ pthread_cond_t COND_work;
+ pthread_cond_t COND_thread_ended;
+ zend_bool thread_is_running;
+ zend_bool thread_killed;
+ void *** tsrm_ls;
+#endif
+
+
struct st_mysqlnd_conn_methods *m;
};
@@ -436,18 +492,43 @@ struct st_mysqlnd_result_metadata
};
-struct st_mysqlnd_buffered_result
+struct st_mysqlnd_background_buffered_result
{
zval ***data;
+ uint64 data_size;
zval ***data_cursor;
- zend_uchar **row_buffers;
+ MYSQLND_MEMORY_POOL_CHUNK **row_buffers;
+ uint64 row_count;
+ uint64 initialized_rows;
+ zend_bool persistent;
+
+ MYSQLND_QCACHE *qcache;
+ unsigned int references;
+
+ zend_bool decode_in_foreground;
+
+#ifdef ZTS
+ zend_bool bg_fetch_finished;
+ MUTEX_T LOCK;
+#endif
+
+ mysqlnd_error_info error_info;
+ mysqlnd_upsert_status upsert_status;
+};
+
+
+struct st_mysqlnd_buffered_result
+{
+ zval **data;
+ zval **data_cursor;
+ MYSQLND_MEMORY_POOL_CHUNK **row_buffers;
uint64 row_count;
+ uint64 initialized_rows;
zend_bool persistent;
MYSQLND_QCACHE *qcache;
unsigned int references;
- zend_bool async_invalid;
mysqlnd_error_info error_info;
};
@@ -456,7 +537,7 @@ struct st_mysqlnd_unbuffered_result
{
/* For unbuffered (both normal and PS) */
zval **last_row_data;
- zend_uchar *last_row_buffer;
+ MYSQLND_MEMORY_POOL_CHUNK *last_row_buffer;
uint64 row_count;
zend_bool eof_reached;
@@ -475,9 +556,9 @@ struct st_mysqlnd_res
MYSQLND_RES_METADATA *meta;
/* To be used with store_result() - both normal and PS */
- MYSQLND_RES_BUFFERED *data;
-
- MYSQLND_RES_UNBUFFERED *unbuf;
+ MYSQLND_RES_BUFFERED *stored_data;
+ MYSQLND_RES_BG_BUFFERED *bg_stored_data;
+ MYSQLND_RES_UNBUFFERED *unbuf;
/*
Column lengths of current row - both buffered and unbuffered.
diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c
index cc8b29ad3b..6015aac6eb 100644
--- a/ext/mysqlnd/mysqlnd_wireprotocol.c
+++ b/ext/mysqlnd/mysqlnd_wireprotocol.c
@@ -43,12 +43,11 @@
#define MYSQLND_DUMP_HEADER_N_BODY2
#define MYSQLND_DUMP_HEADER_N_BODY_FULL2
-#define MYSQLND_MAX_PACKET_SIZE (256L*256L*256L-1)
#define PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_size, packet_type) \
{ \
if (FAIL == mysqlnd_read_header((conn), &((packet)->header) TSRMLS_CC)) {\
- conn->state = CONN_QUIT_SENT; \
+ CONN_SET_STATE(conn, CONN_QUIT_SENT); \
SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);\
php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", mysqlnd_server_gone); \
DBG_ERR_FMT("Can't read %s's header", (packet_type)); \
@@ -60,7 +59,7 @@
}\
if (!mysqlnd_read_body((conn), (buf), \
MIN((buf_size), (packet)->header.size) TSRMLS_CC)) { \
- conn->state = CONN_QUIT_SENT; \
+ CONN_SET_STATE(conn, CONN_QUIT_SENT); \
SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);\
php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", mysqlnd_server_gone); \
DBG_ERR_FMT("Empty %s packet body", (packet_type)); \
@@ -490,7 +489,7 @@ size_t mysqlnd_read_body(MYSQLND *conn, zend_uchar *buf, size_t size TSRMLS_DC)
net->stream->chunk_size = MIN(size, conn->options.net_read_buffer_size);
do {
size -= (ret = php_stream_read(net->stream, p, size));
- if (size || iter++) {
+ if (size > 0 || iter++) {
DBG_INF_FMT("read=%d buf=%p p=%p chunk_size=%d left=%d",
ret, buf, p , net->stream->chunk_size, size);
}
@@ -1234,13 +1233,13 @@ void php_mysqlnd_rset_field_free_mem(void *_packet, zend_bool alloca TSRMLS_DC)
static enum_func_status
-php_mysqlnd_read_row_ex(MYSQLND *conn, zend_uchar **buf, int buf_size,
- size_t *data_size, zend_bool persistent_alloc,
+php_mysqlnd_read_row_ex(MYSQLND *conn, MYSQLND_MEMORY_POOL_CHUNK **buffer,
+ uint64 *data_size, zend_bool persistent_alloc,
unsigned int prealloc_more_bytes TSRMLS_DC)
{
enum_func_status ret = PASS;
mysqlnd_packet_header header;
- zend_uchar *new_buf = NULL, *p = *buf;
+ zend_uchar *p = NULL;
zend_bool first_iteration = TRUE;
DBG_ENTER("php_mysqlnd_read_row_ex");
@@ -1262,13 +1261,14 @@ php_mysqlnd_read_row_ex(MYSQLND *conn, zend_uchar **buf, int buf_size,
*data_size += header.size;
- if (first_iteration && header.size > buf_size) {
+ if (first_iteration) {
first_iteration = FALSE;
/*
We need a trailing \0 for the last string, in case of text-mode,
to be able to implement read-only variables. Thus, we add + 1.
*/
- p = new_buf = mnd_pemalloc(*data_size + 1, persistent_alloc);
+ *buffer = mysqlnd_memory_pool.get_chunk(&mysqlnd_memory_pool, *data_size + 1 TSRMLS_CC);
+ p = (*buffer)->ptr;
} else if (!first_iteration) {
/* Empty packet after MYSQLND_MAX_PACKET_SIZE packet. That's ok, break */
if (!header.size) {
@@ -1281,9 +1281,9 @@ php_mysqlnd_read_row_ex(MYSQLND *conn, zend_uchar **buf, int buf_size,
We need a trailing \0 for the last string, in case of text-mode,
to be able to implement read-only variables.
*/
- new_buf = mnd_perealloc(new_buf, *data_size + 1, persistent_alloc);
+ (*buffer)->resize_chunk((*buffer), *data_size + 1 TSRMLS_CC);
/* The position could have changed, recalculate */
- p = new_buf + (*data_size - header.size);
+ p = (*buffer)->ptr + (*data_size - header.size);
}
if (!mysqlnd_read_body(conn, p, header.size TSRMLS_CC)) {
@@ -1297,8 +1297,9 @@ php_mysqlnd_read_row_ex(MYSQLND *conn, zend_uchar **buf, int buf_size,
break;
}
}
- if (ret == PASS && new_buf) {
- *buf = new_buf;
+ if (ret == FAIL) {
+ (*buffer)->free_chunk((*buffer), TRUE TSRMLS_CC);
+ *buffer = NULL;
}
*data_size -= prealloc_more_bytes;
DBG_RETURN(ret);
@@ -1306,11 +1307,11 @@ php_mysqlnd_read_row_ex(MYSQLND *conn, zend_uchar **buf, int buf_size,
/* {{{ php_mysqlnd_rowp_read_binary_protocol */
-static
-void php_mysqlnd_rowp_read_binary_protocol(php_mysql_packet_row *packet, MYSQLND *conn,
- zend_uchar *p, size_t data_size TSRMLS_DC)
+void php_mysqlnd_rowp_read_binary_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields,
+ uint field_count, MYSQLND_FIELD *fields_metadata, MYSQLND *conn TSRMLS_DC)
{
- unsigned int i;
+ int i;
+ zend_uchar *p = row_buffer->ptr;
zend_uchar *null_ptr, bit;
zval **current_field, **end_field, **start_field;
zend_bool as_unicode = conn->options.numeric_and_datetime_as_unicode;
@@ -1319,14 +1320,14 @@ void php_mysqlnd_rowp_read_binary_protocol(php_mysql_packet_row *packet, MYSQLND
DBG_ENTER("php_mysqlnd_rowp_read_binary_protocol");
- end_field = (current_field = start_field = packet->fields) + packet->field_count;
+ end_field = (current_field = start_field = fields) + field_count;
/* skip the first byte, not 0xFE -> 0x0, status */
p++;
null_ptr= p;
- p += (packet->field_count + 9)/8; /* skip null bits */
- bit = 4; /* first 2 bits are reserved */
+ p += (field_count + 9)/8; /* skip null bits */
+ bit = 4; /* first 2 bits are reserved */
for (i = 0; current_field < end_field; current_field++, i++) {
#if 1
@@ -1344,8 +1345,8 @@ void php_mysqlnd_rowp_read_binary_protocol(php_mysql_packet_row *packet, MYSQLND
if (*null_ptr & bit) {
ZVAL_NULL(*current_field);
} else {
- enum_mysqlnd_field_types type = packet->fields_metadata[i].type;
- mysqlnd_ps_fetch_functions[type].func(*current_field, &packet->fields_metadata[i],
+ enum_mysqlnd_field_types type = fields_metadata[i].type;
+ mysqlnd_ps_fetch_functions[type].func(*current_field, &fields_metadata[i],
0, &p, as_unicode TSRMLS_CC);
}
if (!((bit<<=1) & 255)) {
@@ -1353,8 +1354,6 @@ void php_mysqlnd_rowp_read_binary_protocol(php_mysql_packet_row *packet, MYSQLND
null_ptr++;
}
}
- /* Normal queries: The buffer has one more byte at the end, because we need it */
- packet->row_buffer[data_size] = '\0';
DBG_VOID_RETURN;
}
@@ -1362,14 +1361,15 @@ void php_mysqlnd_rowp_read_binary_protocol(php_mysql_packet_row *packet, MYSQLND
/* {{{ php_mysqlnd_rowp_read_text_protocol */
-static
-void php_mysqlnd_rowp_read_text_protocol(php_mysql_packet_row *packet, MYSQLND *conn,
- zend_uchar *p, size_t data_size TSRMLS_DC)
+void php_mysqlnd_rowp_read_text_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields,
+ uint field_count, MYSQLND_FIELD *fields_metadata, MYSQLND *conn TSRMLS_DC)
{
- unsigned int i;
- zend_bool last_field_was_string;
+ int i;
+ zend_bool last_field_was_string = FALSE;
zval **current_field, **end_field, **start_field;
- zend_uchar *bit_area = packet->row_buffer + data_size + 1; /* we allocate from here */
+ zend_uchar *p = row_buffer->ptr;
+ size_t data_size = row_buffer->app;
+ zend_uchar *bit_area = (zend_uchar*) row_buffer->ptr + data_size + 1; /* we allocate from here */
zend_bool as_unicode = conn->options.numeric_and_datetime_as_unicode;
#ifdef MYSQLND_STRING_TO_INT_CONVERSION
zend_bool as_int = conn->options.int_and_year_as_int;
@@ -1377,7 +1377,7 @@ void php_mysqlnd_rowp_read_text_protocol(php_mysql_packet_row *packet, MYSQLND *
DBG_ENTER("php_mysqlnd_rowp_read_text_protocol");
- end_field = (current_field = start_field = packet->fields) + packet->field_count;
+ end_field = (current_field = start_field = fields) + field_count;
for (i = 0; current_field < end_field; current_field++, i++) {
/* Don't reverse the order. It is significant!*/
void *obj;
@@ -1418,7 +1418,7 @@ void php_mysqlnd_rowp_read_text_protocol(php_mysql_packet_row *packet, MYSQLND *
} else {
#if PHP_MAJOR_VERSION >= 6 || defined(MYSQLND_STRING_TO_INT_CONVERSION)
struct st_mysqlnd_perm_bind perm_bind =
- mysqlnd_ps_fetch_functions[packet->fields_metadata[i].type];
+ mysqlnd_ps_fetch_functions[fields_metadata[i].type];
#endif
#ifdef MYSQLND_STRING_TO_INT_CONVERSION
@@ -1453,7 +1453,7 @@ void php_mysqlnd_rowp_read_text_protocol(php_mysql_packet_row *packet, MYSQLND *
*(p + len) = save;
} else
#endif
- if (packet->fields_metadata[i].type == MYSQL_TYPE_BIT) {
+ if (fields_metadata[i].type == MYSQL_TYPE_BIT) {
/*
BIT fields are specially handled. As they come as bit mask, we have
to convert it to human-readable representation. As the bits take
@@ -1464,7 +1464,7 @@ void php_mysqlnd_rowp_read_text_protocol(php_mysql_packet_row *packet, MYSQLND *
Definitely not nice, _hackish_ :(, but works.
*/
zend_uchar *start = bit_area;
- ps_fetch_from_1_to_8_bytes(*current_field, &(packet->fields_metadata[i]),
+ ps_fetch_from_1_to_8_bytes(*current_field, &(fields_metadata[i]),
0, &p, as_unicode, len TSRMLS_CC);
/*
We have advanced in ps_fetch_from_1_to_8_bytes. We should go back because
@@ -1532,7 +1532,7 @@ void php_mysqlnd_rowp_read_text_protocol(php_mysql_packet_row *packet, MYSQLND *
which will make with this `if` an `else if`.
*/
if ((perm_bind.is_possibly_blob == TRUE &&
- packet->fields_metadata[i].charsetnr == MYSQLND_BINARY_CHARSET_NR) ||
+ fields_metadata[i].charsetnr == MYSQLND_BINARY_CHARSET_NR) ||
(!as_unicode && perm_bind.can_ret_as_str_in_uni == TRUE))
{
/* BLOB - no conversion please */
@@ -1561,7 +1561,7 @@ void php_mysqlnd_rowp_read_text_protocol(php_mysql_packet_row *packet, MYSQLND *
}
if (last_field_was_string) {
/* Normal queries: The buffer has one more byte at the end, because we need it */
- packet->row_buffer[data_size] = '\0';
+ row_buffer->ptr[data_size] = '\0';
}
DBG_VOID_RETURN;
@@ -1580,10 +1580,10 @@ php_mysqlnd_rowp_read(void *_packet, MYSQLND *conn TSRMLS_DC)
MYSQLND_NET *net = &conn->net;
zend_uchar *p;
enum_func_status ret = PASS;
- size_t data_size = 0;
size_t old_chunk_size = net->stream->chunk_size;
php_mysql_packet_row *packet= (php_mysql_packet_row *) _packet;
size_t post_alloc_for_bit_fields = 0;
+ uint64 data_size = 0;
DBG_ENTER("php_mysqlnd_rowp_read");
@@ -1593,17 +1593,18 @@ php_mysqlnd_rowp_read(void *_packet, MYSQLND *conn TSRMLS_DC)
packet->bit_fields_total_len + packet->bit_fields_count;
}
- ret = php_mysqlnd_read_row_ex(conn, &packet->row_buffer, 0, &data_size,
+ ret = php_mysqlnd_read_row_ex(conn, &packet->row_buffer, &data_size,
packet->persistent_alloc, post_alloc_for_bit_fields
TSRMLS_CC);
if (FAIL == ret) {
goto end;
}
- /* packet->row_buffer is of size 'data_size + 1' */
+ /* packet->row_buffer->ptr is of size 'data_size + 1' */
packet->header.size = data_size;
+ packet->row_buffer->app = data_size;
- if ((*(p = packet->row_buffer)) == 0xFF) {
+ if ((*(p = packet->row_buffer->ptr)) == 0xFF) {
/*
Error message as part of the result set,
not good but we should not hang. See:
@@ -1646,14 +1647,8 @@ php_mysqlnd_rowp_read(void *_packet, MYSQLND *conn TSRMLS_DC)
but mostly like old-API unbuffered and thus will populate this array with
value.
*/
- packet->fields = (zval **) mnd_pemalloc(packet->field_count * sizeof(zval *),
- packet->persistent_alloc);
- }
-
- if (packet->binary_protocol) {
- php_mysqlnd_rowp_read_binary_protocol(packet, conn, p, data_size TSRMLS_CC);
- } else {
- php_mysqlnd_rowp_read_text_protocol(packet, conn, p, data_size TSRMLS_CC);
+ packet->fields = (zval **) mnd_pecalloc(packet->field_count, sizeof(zval *),
+ packet->persistent_alloc);
}
} else {
MYSQLND_INC_CONN_STATISTIC(&conn->stats,
@@ -1675,7 +1670,7 @@ void php_mysqlnd_rowp_free_mem(void *_packet, zend_bool alloca TSRMLS_DC)
{
php_mysql_packet_row *p= (php_mysql_packet_row *) _packet;
if (p->row_buffer) {
- mnd_pefree(p->row_buffer, p->persistent_alloc);
+ p->row_buffer->free_chunk(p->row_buffer, TRUE TSRMLS_CC);
p->row_buffer = NULL;
}
/*
diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.h b/ext/mysqlnd/mysqlnd_wireprotocol.h
index d9c733428d..7d3fe83ab3 100644
--- a/ext/mysqlnd/mysqlnd_wireprotocol.h
+++ b/ext/mysqlnd/mysqlnd_wireprotocol.h
@@ -258,7 +258,7 @@ struct st_php_mysql_packet_row {
mysqlnd_2b warning_count;
mysqlnd_2b server_status;
- zend_uchar *row_buffer;
+ struct st_mysqlnd_memory_pool_chunk *row_buffer;
zend_bool skip_extraction;
zend_bool binary_protocol;
@@ -323,6 +323,13 @@ zend_uchar * php_mysqlnd_net_store_length(zend_uchar *packet, uint64 length);
extern char * const mysqlnd_empty_string;
+
+void php_mysqlnd_rowp_read_binary_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields,
+ uint field_count, MYSQLND_FIELD *fields_metadata, MYSQLND *conn TSRMLS_DC);
+
+void php_mysqlnd_rowp_read_text_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields,
+ uint field_count, MYSQLND_FIELD *fields_metadata, MYSQLND *conn TSRMLS_DC);
+
#endif /* MYSQLND_WIREPROTOCOL_H */
/*