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