summaryrefslogtreecommitdiff
path: root/ext/mysqlnd
diff options
context:
space:
mode:
authorAndrey Hristov <andrey@php.net>2008-01-28 18:27:49 +0000
committerAndrey Hristov <andrey@php.net>2008-01-28 18:27:49 +0000
commit18c8e6501b2c1f9fc96dda7c2304523d46bf13a4 (patch)
treef41112affbf103b699c603b36e354be143969cc3 /ext/mysqlnd
parent618a29411d2f58a9bf55488f2d7220639ec92c23 (diff)
downloadphp-git-18c8e6501b2c1f9fc96dda7c2304523d46bf13a4.tar.gz
MFB: More optimizations - less MM calls
Clearly separated fetching (physical reading) from decoding phases (data interpretation). Threaded fetching added but disabled as needs more work for Windows. For Linux needs some touches to add pthreads if this is enabled, probably with a compile-time switch. The code reorganisation makes it easy to add also async API, similar to cURL's one.
Diffstat (limited to 'ext/mysqlnd')
-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.c830
-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
12 files changed, 1332 insertions, 272 deletions
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..bdbe39ee71 100644
--- a/ext/mysqlnd/mysqlnd_result.c
+++ b/ext/mysqlnd/mysqlnd_result.c
@@ -19,6 +19,7 @@
*/
/* $Id$ */
+
#include "php.h"
#include "mysqlnd.h"
#include "mysqlnd_wireprotocol.h"
@@ -32,6 +33,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 +110,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 +122,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 +151,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 +163,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 +172,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 +319,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 +416,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 +435,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 +453,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 +526,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 +561,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 +569,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 +651,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 +670,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 +681,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;
- 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;
- }
+ 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 (lengths) {
- lengths[i] = len;
- }
+ retrow = mnd_malloc(result->field_count * sizeof(char *));
+
+ for (i = 0; i < field_count; i++, field++, zend_hash_key++) {
+ zval *data = result->unbuf->last_row_data[i];
+ int len;
- if (field->max_length < len) {
- field->max_length = 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;
+ }
}
}
} else if (ret == FAIL) {
@@ -552,7 +720,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 +733,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 +765,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 +788,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 +796,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 +861,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 +874,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 +902,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 +934,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 +982,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 +1000,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 +1083,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 +1097,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 +1160,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 +1215,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 +1240,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 +1549,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 +1594,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 +1610,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 +1634,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 +1761,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 +1790,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 +1848,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 +1870,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 */
/*