cvs: php-src(PHP_5_3) /ext/mysqli mysqli.c mysqli_mysqlnd.h mysqli_nonapi.c mysqli_prop.c php_mysqli_structs.h /ext/mysqli/tests
mysqli_get_client_stats.phpt /ext/mysqlnd mysqlnd.c mysqlnd.h mysqlnd_enum_n_def.h mysqlnd_libmysql_compat.h mysqlnd_priv.h mysqlnd_ps.c
mysqlnd_result.c mysqlnd_result.h mysqlnd_result_meta.c mysqlnd_structs.h mysqlnd_wireprotocol.c mysqlnd_wireprotocol.h

From: Date: Mon, 28 Jan 2008 18:25:51 +0000
Subject: cvs: php-src(PHP_5_3) /ext/mysqli mysqli.c mysqli_mysqlnd.h mysqli_nonapi.c mysqli_prop.c php_mysqli_structs.h /ext/mysqli/tests
mysqli_get_client_stats.phpt /ext/mysqlnd mysqlnd.c mysqlnd.h mysqlnd_enum_n_def.h mysqlnd_libmysql_compat.h mysqlnd_priv.h mysqlnd_ps.c
mysqlnd_result.c mysqlnd_result.h mysqlnd_result_meta.c mysqlnd_structs.h mysqlnd_wireprotocol.c mysqlnd_wireprotocol.h
Groups: php.cvs 
Request: Send a blank email to php-cvs+get-48059@lists.php.net to get a copy of this message
andrey Mon Jan 28 18:25:51 2008 UTC Modified files: (Branch: PHP_5_3) /php-src/ext/mysqli mysqli.c mysqli_mysqlnd.h mysqli_nonapi.c mysqli_prop.c php_mysqli_structs.h /php-src/ext/mysqli/tests mysqli_get_client_stats.phpt /php-src/ext/mysqlnd mysqlnd.c mysqlnd.h mysqlnd_enum_n_def.h mysqlnd_libmysql_compat.h mysqlnd_priv.h mysqlnd_ps.c mysqlnd_result.c mysqlnd_result.h mysqlnd_result_meta.c mysqlnd_structs.h mysqlnd_wireprotocol.c mysqlnd_wireprotocol.h Log: More optimizations - less MM calls Clearly separated fetching (physical reading) from decoding phases (data interpretation). Threaded fetching added but disabled as needs more work for Windows. For Linux needs some touches to add pthreads if this is enabled, probably with a compile-time switch. The code reorganisation makes it easy to add also async API, similar to cURL's one.

http://cvs.php.net/viewvc.cgi/php-src/ext/mysqli/mysqli.c?r1=1.72.2.16.2.17.2.9&r2=1.72.2.16.2.17.2.10&diff_format=u Index: php-src/ext/mysqli/mysqli.c diff -u php-src/ext/mysqli/mysqli.c:1.72.2.16.2.17.2.9 php-src/ext/mysqli/mysqli.c:1.72.2.16.2.17.2.10 --- php-src/ext/mysqli/mysqli.c:1.72.2.16.2.17.2.9 Thu Jan 3 14:33:46 2008 +++ php-src/ext/mysqli/mysqli.c Mon Jan 28 18:25:49 2008 @@ -15,7 +15,7 @@ | Author: Georg Richter <georg@php.net> | +----------------------------------------------------------------------+ - $Id: mysqli.c,v 1.72.2.16.2.17.2.9 2008/01/03 14:33:46 johannes Exp $ + $Id: mysqli.c,v 1.72.2.16.2.17.2.10 2008/01/28 18:25:49 andrey Exp $ */ #ifdef HAVE_CONFIG_H @@ -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 @@ 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 @@ 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 @@ 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 @@ 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; http://cvs.php.net/viewvc.cgi/php-src/ext/mysqli/mysqli_mysqlnd.h?r1=1.3.2.3&r2=1.3.2.4&diff_format=u Index: php-src/ext/mysqli/mysqli_mysqlnd.h diff -u php-src/ext/mysqli/mysqli_mysqlnd.h:1.3.2.3 php-src/ext/mysqli/mysqli_mysqlnd.h:1.3.2.4 --- php-src/ext/mysqli/mysqli_mysqlnd.h:1.3.2.3 Mon Dec 31 07:17:10 2007 +++ php-src/ext/mysqli/mysqli_mysqlnd.h Mon Jan 28 18:25:50 2008 @@ -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 http://cvs.php.net/viewvc.cgi/php-src/ext/mysqli/mysqli_nonapi.c?r1=1.54.2.7.2.5.2.4&r2=1.54.2.7.2.5.2.5&diff_format=u Index: php-src/ext/mysqli/mysqli_nonapi.c diff -u php-src/ext/mysqli/mysqli_nonapi.c:1.54.2.7.2.5.2.4 php-src/ext/mysqli/mysqli_nonapi.c:1.54.2.7.2.5.2.5 --- php-src/ext/mysqli/mysqli_nonapi.c:1.54.2.7.2.5.2.4 Mon Dec 31 07:17:10 2007 +++ php-src/ext/mysqli/mysqli_nonapi.c Mon Jan 28 18:25:50 2008 @@ -17,7 +17,7 @@ | Ulf Wendel <uw@php.net> | +----------------------------------------------------------------------+ - $Id: mysqli_nonapi.c,v 1.54.2.7.2.5.2.4 2007/12/31 07:17:10 sebastian Exp $ + $Id: mysqli_nonapi.c,v 1.54.2.7.2.5.2.5 2008/01/28 18:25:50 andrey Exp $ */ #ifdef HAVE_CONFIG_H @@ -459,7 +459,11 @@ 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 @@ MYSQLI_DISABLE_MQ; + if (mysql_real_query(mysql->mysql, query, query_len)) { MYSQLI_REPORT_MYSQL_ERROR(mysql->mysql); RETURN_FALSE; @@ -481,7 +486,19 @@ 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, http://cvs.php.net/viewvc.cgi/php-src/ext/mysqli/mysqli_prop.c?r1=1.23.2.5.2.2.2.3&r2=1.23.2.5.2.2.2.4&diff_format=u Index: php-src/ext/mysqli/mysqli_prop.c diff -u php-src/ext/mysqli/mysqli_prop.c:1.23.2.5.2.2.2.3 php-src/ext/mysqli/mysqli_prop.c:1.23.2.5.2.2.2.4 --- php-src/ext/mysqli/mysqli_prop.c:1.23.2.5.2.2.2.3 Mon Dec 31 07:17:10 2007 +++ php-src/ext/mysqli/mysqli_prop.c Mon Jan 28 18:25:50 2008 @@ -15,7 +15,7 @@ | Author: Georg Richter <georg@php.net> | +----------------------------------------------------------------------+ - $Id: mysqli_prop.c,v 1.23.2.5.2.2.2.3 2007/12/31 07:17:10 sebastian Exp $ + $Id: mysqli_prop.c,v 1.23.2.5.2.2.2.4 2008/01/28 18:25:50 andrey Exp $ */ #ifdef HAVE_CONFIG_H @@ -211,7 +211,7 @@ 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; } http://cvs.php.net/viewvc.cgi/php-src/ext/mysqli/php_mysqli_structs.h?r1=1.4.2.6&r2=1.4.2.7&diff_format=u Index: php-src/ext/mysqli/php_mysqli_structs.h diff -u php-src/ext/mysqli/php_mysqli_structs.h:1.4.2.6 php-src/ext/mysqli/php_mysqli_structs.h:1.4.2.7 --- php-src/ext/mysqli/php_mysqli_structs.h:1.4.2.6 Mon Dec 31 07:17:10 2007 +++ php-src/ext/mysqli/php_mysqli_structs.h Mon Jan 28 18:25:50 2008 @@ -15,7 +15,7 @@ | Author: Georg Richter <georg@php.net> | +----------------------------------------------------------------------+ - $Id: php_mysqli_structs.h,v 1.4.2.6 2007/12/31 07:17:10 sebastian Exp $ + $Id: php_mysqli_structs.h,v 1.4.2.7 2008/01/28 18:25:50 andrey Exp $ */ #ifndef PHP_MYSQLI_STRUCTS_H @@ -298,6 +298,9 @@ #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 http://cvs.php.net/viewvc.cgi/php-src/ext/mysqli/tests/mysqli_get_client_stats.phpt?r1=1.2.2.2&r2=1.2.2.3&diff_format=u Index: php-src/ext/mysqli/tests/mysqli_get_client_stats.phpt diff -u php-src/ext/mysqli/tests/mysqli_get_client_stats.phpt:1.2.2.2 php-src/ext/mysqli/tests/mysqli_get_client_stats.phpt:1.2.2.3 --- php-src/ext/mysqli/tests/mysqli_get_client_stats.phpt:1.2.2.2 Wed Oct 10 10:16:00 2007 +++ php-src/ext/mysqli/tests/mysqli_get_client_stats.phpt Mon Jan 28 18:25:50 2008 @@ -969,9 +969,9 @@ ["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 @@ [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"]=> http://cvs.php.net/viewvc.cgi/php-src/ext/mysqlnd/mysqlnd.c?r1=1.5.2.8&r2=1.5.2.9&diff_format=u Index: php-src/ext/mysqlnd/mysqlnd.c diff -u php-src/ext/mysqlnd/mysqlnd.c:1.5.2.8 php-src/ext/mysqlnd/mysqlnd.c:1.5.2.9 --- php-src/ext/mysqlnd/mysqlnd.c:1.5.2.8 Wed Jan 23 19:11:28 2008 +++ php-src/ext/mysqlnd/mysqlnd.c Mon Jan 28 18:25:50 2008 @@ -18,7 +18,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: mysqlnd.c,v 1.5.2.8 2008/01/23 19:11:28 andrey Exp $ */ +/* $Id: mysqlnd.c,v 1.5.2.9 2008/01/28 18:25:50 andrey Exp $ */ #include "php.h" #include "mysqlnd.h" #include "mysqlnd_wireprotocol.h" @@ -66,12 +66,205 @@ 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 @@ #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 @@ /* {{{ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ } } } 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 @@ 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 @@ 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_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 @@ 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 @@ 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 @@ ret = conn->m->free_reference(conn TSRMLS_CC); - DBG_RETURN(ret); } /* }}} */ @@ -1273,6 +1509,46 @@ /* }}} */ +/* {{{ 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 @@ 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 @@ 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 @@ } /* 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 @@ } /* 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_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_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_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 @@ 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 @@ { REGISTER_INI_ENTRIES(); - mysqlnd_library_init(); + mysqlnd_library_init(TSRMLS_C); return SUCCESS; } /* }}} */ @@ -1995,7 +2321,7 @@ */ static PHP_MSHUTDOWN_FUNCTION(mysqlnd) { - mysqlnd_library_end(); + mysqlnd_library_end(TSRMLS_C); UNREGISTER_INI_ENTRIES(); return SUCCESS; http://cvs.php.net/viewvc.cgi/php-src/ext/mysqlnd/mysqlnd.h?r1=1.3.2.5&r2=1.3.2.6&diff_format=u Index: php-src/ext/mysqlnd/mysqlnd.h diff -u php-src/ext/mysqlnd/mysqlnd.h:1.3.2.5 php-src/ext/mysqlnd/mysqlnd.h:1.3.2.6 --- php-src/ext/mysqlnd/mysqlnd.h:1.3.2.5 Wed Jan 23 19:11:28 2008 +++ php-src/ext/mysqlnd/mysqlnd.h Mon Jan 28 18:25:50 2008 @@ -18,17 +18,20 @@ +----------------------------------------------------------------------+ */ -/* $Id: mysqlnd.h,v 1.3.2.5 2008/01/23 19:11:28 andrey Exp $ */ +/* $Id: mysqlnd.h,v 1.3.2.6 2008/01/28 18:25:50 andrey Exp $ */ #ifndef MYSQLND_H #define MYSQLND_H -#define MYSQLND_VERSION "mysqlnd 5.0.2-dev - 070928 - $Revision: 1.3.2.5 $" +#define MYSQLND_VERSION "mysqlnd 5.0.2-dev - 070928 - $Revision: 1.3.2.6 $" #define MYSQLND_VERSION_ID 50002 /* 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 @@ #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 @@ /* 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) http://cvs.php.net/viewvc.cgi/php-src/ext/mysqlnd/mysqlnd_enum_n_def.h?r1=1.2.2.5&r2=1.2.2.6&diff_format=u Index: php-src/ext/mysqlnd/mysqlnd_enum_n_def.h diff -u php-src/ext/mysqlnd/mysqlnd_enum_n_def.h:1.2.2.5 php-src/ext/mysqlnd/mysqlnd_enum_n_def.h:1.2.2.6 --- php-src/ext/mysqlnd/mysqlnd_enum_n_def.h:1.2.2.5 Wed Jan 23 19:11:28 2008 +++ php-src/ext/mysqlnd/mysqlnd_enum_n_def.h Mon Jan 28 18:25:50 2008 @@ -18,10 +18,11 @@ +----------------------------------------------------------------------+ */ -/* $Id: mysqlnd_enum_n_def.h,v 1.2.2.5 2008/01/23 19:11:28 andrey Exp $ */ +/* $Id: mysqlnd_enum_n_def.h,v 1.2.2.6 2008/01/28 18:25:50 andrey Exp $ */ #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 http://cvs.php.net/viewvc.cgi/php-src/ext/mysqlnd/mysqlnd_libmysql_compat.h?r1=1.1.2.5&r2=1.1.2.6&diff_format=u Index: php-src/ext/mysqlnd/mysqlnd_libmysql_compat.h diff -u php-src/ext/mysqlnd/mysqlnd_libmysql_compat.h:1.1.2.5 php-src/ext/mysqlnd/mysqlnd_libmysql_compat.h:1.1.2.6 --- php-src/ext/mysqlnd/mysqlnd_libmysql_compat.h:1.1.2.5 Wed Jan 23 19:11:28 2008 +++ php-src/ext/mysqlnd/mysqlnd_libmysql_compat.h Mon Jan 28 18:25:50 2008 @@ -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 */ http://cvs.php.net/viewvc.cgi/php-src/ext/mysqlnd/mysqlnd_priv.h?r1=1.4.2.7&r2=1.4.2.8&diff_format=u Index: php-src/ext/mysqlnd/mysqlnd_priv.h diff -u php-src/ext/mysqlnd/mysqlnd_priv.h:1.4.2.7 php-src/ext/mysqlnd/mysqlnd_priv.h:1.4.2.8 --- php-src/ext/mysqlnd/mysqlnd_priv.h:1.4.2.7 Wed Jan 23 19:11:28 2008 +++ php-src/ext/mysqlnd/mysqlnd_priv.h Mon Jan 28 18:25:50 2008 @@ -18,7 +18,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: mysqlnd_priv.h,v 1.4.2.7 2008/01/23 19:11:28 andrey Exp $ */ +/* $Id: mysqlnd_priv.h,v 1.4.2.8 2008/01/28 18:25:50 andrey Exp $ */ #ifndef MYSQLND_PRIV_H #define 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 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); http://cvs.php.net/viewvc.cgi/php-src/ext/mysqlnd/mysqlnd_ps.c?r1=1.3.2.6&r2=1.3.2.7&diff_format=u Index: php-src/ext/mysqlnd/mysqlnd_ps.c diff -u php-src/ext/mysqlnd/mysqlnd_ps.c:1.3.2.6 php-src/ext/mysqlnd/mysqlnd_ps.c:1.3.2.7 --- php-src/ext/mysqlnd/mysqlnd_ps.c:1.3.2.6 Wed Jan 23 19:11:28 2008 +++ php-src/ext/mysqlnd/mysqlnd_ps.c Mon Jan 28 18:25:50 2008 @@ -18,7 +18,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: mysqlnd_ps.c,v 1.3.2.6 2008/01/23 19:11:28 andrey Exp $ */ +/* $Id: mysqlnd_ps.c,v 1.3.2.7 2008/01/28 18:25:50 andrey Exp $ */ #include "php.h" #include "mysqlnd.h" #include "mysqlnd_wireprotocol.h" @@ -83,7 +83,74 @@ } /* 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 @@ } 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_stmt::get_result */ static MYSQLND_RES * MYSQLND_METHOD(mysqlnd_stmt, get_result)(MYSQLND_STMT * const stmt TSRMLS_DC) @@ -155,7 +220,7 @@ } /* 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 @@ 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 @@ 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 @@ 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 @@ { 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 @@ } } } - 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ /* 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 @@ /* 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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), http://cvs.php.net/viewvc.cgi/php-src/ext/mysqlnd/mysqlnd_result.c?r1=1.4.2.7&r2=1.4.2.8&diff_format=u Index: php-src/ext/mysqlnd/mysqlnd_result.c diff -u php-src/ext/mysqlnd/mysqlnd_result.c:1.4.2.7 php-src/ext/mysqlnd/mysqlnd_result.c:1.4.2.8 --- php-src/ext/mysqlnd/mysqlnd_result.c:1.4.2.7 Fri Jan 25 15:54:58 2008 +++ php-src/ext/mysqlnd/mysqlnd_result.c Mon Jan 28 18:25:50 2008 @@ -18,7 +18,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: mysqlnd_result.c,v 1.4.2.7 2008/01/25 15:54:58 rrichards Exp $ */ +/* $Id: mysqlnd_result.c,v 1.4.2.8 2008/01/28 18:25:50 andrey Exp $ */ #include "php.h" #include "mysqlnd.h" #include "mysqlnd_wireprotocol.h" @@ -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 @@ } 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_free_buffered_data(MYSQLND_RES *result TSRMLS_DC) { MYSQLND_THD_ZVAL_PCACHE *zval_cache = result->zval_cache; - MYSQLND_RES_BUFFERED *set = result->data; + MYSQLND_RES_BUFFERED *set = result->stored_data; unsigned int field_count = result->field_count; - unsigned int row; + int row; DBG_ENTER("mysqlnd_free_buffered_data"); - DBG_INF_FMT("Freeing "MYSQLND_LLU_SPEC" row(s)", result->data->row_count); + DBG_INF_FMT("Freeing "MYSQLND_LLU_SPEC" row(s)", set->row_count); DBG_INF_FMT("before: real_usage=%lu usage=%lu", zend_memory_usage(TRUE TSRMLS_CC), zend_memory_usage(FALSE TSRMLS_CC)); - for (row = 0; row < result->data->row_count; row++) { - unsigned int col; - zval **current_row = current_row = set->data[row]; - zend_uchar *current_buffer = set->row_buffers[row]; + for (row = set->row_count - 1; row >= 0; row--) { + zval **current_row = set->data + row * field_count; + MYSQLND_MEMORY_POOL_CHUNK *current_buffer = set->row_buffers[row]; + int col; - for (col = 0; col < field_count; col++) { + for (col = field_count - 1; col >=0 ; col--) { zend_bool copy_ctor_called; + if (current_row[0] == NULL) { + break;/* row that was never initialized */ + } mysqlnd_palloc_zval_ptr_dtor(&(current_row[col]), zval_cache, result->type, &copy_ctor_called TSRMLS_CC); #if MYSQLND_DEBUG_MEMORY @@ -108,8 +150,7 @@ #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 @@ if (set->qcache) { mysqlnd_qcache_free_cache_reference(&set->qcache); } + DBG_INF("Freeing set"); pefree(set, set->persistent); @@ -129,22 +171,105 @@ } /* }}} */ +#ifdef MYSQLND_THREADED +/* {{{ mysqlnd_free_background_buffered_data */ +void mysqlnd_free_background_buffered_data(MYSQLND_RES *result TSRMLS_DC) +{ + MYSQLND_THD_ZVAL_PCACHE *zval_cache = result->zval_cache; + MYSQLND_RES_BG_BUFFERED *set = result->bg_stored_data; + unsigned int field_count = result->field_count; + int row; + + DBG_ENTER("mysqlnd_free_buffered_data"); + DBG_INF_FMT("Freeing "MYSQLND_LLU_SPEC" row(s)", set->row_count); + + do { + tsrm_mutex_lock(set->LOCK); + if (set->bg_fetch_finished) { + tsrm_mutex_unlock(set->LOCK); + break; + } + tsrm_mutex_unlock(set->LOCK); +#if HAVE_USLEEP + usleep(2000); +#else + { + volatile int i; + for (i = 0; i < 1000; i++); + } +#endif + } while (1); + + DBG_INF_FMT("before: real_usage=%lu usage=%lu", zend_memory_usage(TRUE TSRMLS_CC), zend_memory_usage(FALSE TSRMLS_CC)); + for (row = set->row_count - 1; row >= 0; row--) { + MYSQLND_MEMORY_POOL_CHUNK *current_buffer = set->row_buffers[row]; + /* It could be the case that the user fetched no rows - then no set->data */ + if (row < set->data_size && set->data[row]) { + zval **current_row = set->data[row]; + unsigned int col; + + for (col = 0; col < field_count; col++) { + zend_bool copy_ctor_called; + mysqlnd_palloc_zval_ptr_dtor(&(current_row[col]), zval_cache, + result->type, &copy_ctor_called TSRMLS_CC); +#if MYSQLND_DEBUG_MEMORY + DBG_INF_FMT("Copy_ctor_called=%d", copy_ctor_called); +#endif + MYSQLND_INC_GLOBAL_STATISTIC(copy_ctor_called? STAT_COPY_ON_WRITE_PERFORMED: + STAT_COPY_ON_WRITE_SAVED); + } +#if MYSQLND_DEBUG_MEMORY + DBG_INF("Freeing current_row & current_buffer"); +#endif + pefree(current_row, set->persistent); + } + current_buffer->free_chunk(current_buffer, TRUE TSRMLS_CC); + } + DBG_INF("Freeing data & row_buffer"); + pefree(set->data, set->persistent); + pefree(set->row_buffers, set->persistent); + set->data = NULL; + set->row_buffers = NULL; + set->data_cursor = NULL; + set->row_count = 0; + if (set->qcache) { + mysqlnd_qcache_free_cache_reference(&set->qcache); + } + + if (set->LOCK) { + tsrm_mutex_free(set->LOCK); + } + + DBG_INF("Freeing set"); + pefree(set, set->persistent); + + DBG_INF_FMT("after: real_usage=%lu usage=%lu", zend_memory_usage(TRUE TSRMLS_CC), zend_memory_usage(FALSE TSRMLS_CC)); + DBG_VOID_RETURN; +} +/* }}} */ +#endif /* MYSQL_THREADING */ /* {{{ mysqlnd_res::free_result_buffers */ void MYSQLND_METHOD(mysqlnd_res, free_result_buffers)(MYSQLND_RES *result TSRMLS_DC) { DBG_ENTER("mysqlnd_res::free_result_buffers"); - DBG_INF_FMT("%s", result->unbuf? "unbuffered":(result->data? "buffered":"unknown")); + DBG_INF_FMT("%s", result->unbuf? "unbuffered":(result->stored_data? "buffered":"unknown")); if (result->unbuf) { mysqlnd_unbuffered_free_last_data(result TSRMLS_CC); efree(result->unbuf); result->unbuf = NULL; - } else if (result->data) { + } else if (result->stored_data) { mysqlnd_free_buffered_data(result TSRMLS_CC); - result->data = NULL; + result->stored_data = NULL; + } +#ifdef MYSQLND_THREADED + else if (result->bg_stored_data) { + mysqlnd_free_background_buffered_data(result TSRMLS_CC); + result->bg_stored_data = NULL; } +#endif if (result->lengths) { efree(result->lengths); @@ -193,11 +318,6 @@ 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 @@ 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 @@ 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_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 @@ 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,44 @@ { unsigned int i; zval **previous_row; + MYSQLND_RES_BUFFERED *set = result->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 * result->meta->field_count) )) + { + return NULL;/* No rows or no more rows */ + } + + 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: @@ -447,15 +605,15 @@ - 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)) { 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 - 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 @@ 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_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_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 @@ 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 @@ 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 @@ *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 @@ 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_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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ { 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 @@ } } } - 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 @@ /* }}} */ -#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 @@ 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 @@ 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 @@ } /* }}} */ +#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 @@ 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 @@ 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_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 @@ 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 @@ { 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_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 @@ 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 @@ 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); } http://cvs.php.net/viewvc.cgi/php-src/ext/mysqlnd/mysqlnd_result.h?r1=1.2.2.5&r2=1.2.2.6&diff_format=u Index: php-src/ext/mysqlnd/mysqlnd_result.h diff -u php-src/ext/mysqlnd/mysqlnd_result.h:1.2.2.5 php-src/ext/mysqlnd/mysqlnd_result.h:1.2.2.6 --- php-src/ext/mysqlnd/mysqlnd_result.h:1.2.2.5 Wed Jan 23 19:11:28 2008 +++ php-src/ext/mysqlnd/mysqlnd_result.h Mon Jan 28 18:25:50 2008 @@ -18,7 +18,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: mysqlnd_result.h,v 1.2.2.5 2008/01/23 19:11:28 andrey Exp $ */ +/* $Id: mysqlnd_result.h,v 1.2.2.6 2008/01/28 18:25:50 andrey Exp $ */ #ifndef MYSQLND_RESULT_H #define MYSQLND_RESULT_H @@ -31,11 +31,14 @@ 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 */ /* http://cvs.php.net/viewvc.cgi/php-src/ext/mysqlnd/mysqlnd_result_meta.c?r1=1.3.2.4&r2=1.3.2.5&diff_format=u Index: php-src/ext/mysqlnd/mysqlnd_result_meta.c diff -u php-src/ext/mysqlnd/mysqlnd_result_meta.c:1.3.2.4 php-src/ext/mysqlnd/mysqlnd_result_meta.c:1.3.2.5 --- php-src/ext/mysqlnd/mysqlnd_result_meta.c:1.3.2.4 Wed Jan 23 19:11:28 2008 +++ php-src/ext/mysqlnd/mysqlnd_result_meta.c Mon Jan 28 18:25:50 2008 @@ -18,7 +18,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: mysqlnd_result_meta.c,v 1.3.2.4 2008/01/23 19:11:28 andrey Exp $ */ +/* $Id: mysqlnd_result_meta.c,v 1.3.2.5 2008/01/28 18:25:50 andrey Exp $ */ #include "php.h" #include "mysqlnd.h" #include "mysqlnd_priv.h" @@ -421,8 +421,8 @@ /* +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); http://cvs.php.net/viewvc.cgi/php-src/ext/mysqlnd/mysqlnd_structs.h?r1=1.2.2.6&r2=1.2.2.7&diff_format=u Index: php-src/ext/mysqlnd/mysqlnd_structs.h diff -u php-src/ext/mysqlnd/mysqlnd_structs.h:1.2.2.6 php-src/ext/mysqlnd/mysqlnd_structs.h:1.2.2.7 --- php-src/ext/mysqlnd/mysqlnd_structs.h:1.2.2.6 Wed Jan 23 19:11:28 2008 +++ php-src/ext/mysqlnd/mysqlnd_structs.h Mon Jan 28 18:25:50 2008 @@ -18,11 +18,44 @@ +----------------------------------------------------------------------+ */ -/* $Id: mysqlnd_structs.h,v 1.2.2.6 2008/01/23 19:11:28 andrey Exp $ */ +/* $Id: mysqlnd_structs.h,v 1.2.2.7 2008/01/28 18:25:50 andrey Exp $ */ #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_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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ /* 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_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 @@ { /* 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 @@ 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. http://cvs.php.net/viewvc.cgi/php-src/ext/mysqlnd/mysqlnd_wireprotocol.c?r1=1.4.2.6&r2=1.4.2.7&diff_format=u Index: php-src/ext/mysqlnd/mysqlnd_wireprotocol.c diff -u php-src/ext/mysqlnd/mysqlnd_wireprotocol.c:1.4.2.6 php-src/ext/mysqlnd/mysqlnd_wireprotocol.c:1.4.2.7 --- php-src/ext/mysqlnd/mysqlnd_wireprotocol.c:1.4.2.6 Wed Jan 23 19:11:28 2008 +++ php-src/ext/mysqlnd/mysqlnd_wireprotocol.c Mon Jan 28 18:25:50 2008 @@ -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 @@ 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 @@ 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 @@ *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 @@ 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 @@ 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_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 @@ 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 @@ 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 @@ 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 @@ /* {{{ 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 @@ 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 @@ } 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 @@ *(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 @@ 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 @@ 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 @@ } 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 @@ 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 @@ 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 @@ 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 @@ { 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; } /* http://cvs.php.net/viewvc.cgi/php-src/ext/mysqlnd/mysqlnd_wireprotocol.h?r1=1.4.2.6&r2=1.4.2.7&diff_format=u Index: php-src/ext/mysqlnd/mysqlnd_wireprotocol.h diff -u php-src/ext/mysqlnd/mysqlnd_wireprotocol.h:1.4.2.6 php-src/ext/mysqlnd/mysqlnd_wireprotocol.h:1.4.2.7 --- php-src/ext/mysqlnd/mysqlnd_wireprotocol.h:1.4.2.6 Wed Jan 23 19:11:28 2008 +++ php-src/ext/mysqlnd/mysqlnd_wireprotocol.h Mon Jan 28 18:25:50 2008 @@ -18,7 +18,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: mysqlnd_wireprotocol.h,v 1.4.2.6 2008/01/23 19:11:28 andrey Exp $ */ +/* $Id: mysqlnd_wireprotocol.h,v 1.4.2.7 2008/01/28 18:25:50 andrey Exp $ */ #ifndef MYSQLND_WIREPROTOCOL_H #define MYSQLND_WIREPROTOCOL_H @@ -258,7 +258,7 @@ 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 @@ 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 */ /*
« previous php.cvs (#48059) next »