diff options
author | Dmitry Stogov <dmitry@zend.com> | 2014-04-26 00:32:51 +0400 |
---|---|---|
committer | Dmitry Stogov <dmitry@zend.com> | 2014-04-26 00:32:51 +0400 |
commit | f9927a6c97208c60d922f9a4e98feb8079c57d1f (patch) | |
tree | 35815b69d1bf7d47fb41e857ff8d2b024ddac153 /ext/mysqlnd | |
parent | 4e7cbf3f5842abe6688c11ce3cc11d2eabf0695f (diff) | |
parent | b82d077f988606580e5c06a9da18fe4f60ddb7cb (diff) | |
download | php-git-f9927a6c97208c60d922f9a4e98feb8079c57d1f.tar.gz |
Merge mainstream 'master' branch into refactoring
During merge I had to revert:
Nikita's patch for php_splice() (it probably needs to be applyed again)
Bob Weinand's patches related to constant expression handling (we need to review them carefully)
I also reverted all our attempts to support sapi/phpdbg (we didn't test it anyway)
Conflicts:
Zend/zend.h
Zend/zend_API.c
Zend/zend_ast.c
Zend/zend_compile.c
Zend/zend_compile.h
Zend/zend_constants.c
Zend/zend_exceptions.c
Zend/zend_execute.c
Zend/zend_execute.h
Zend/zend_execute_API.c
Zend/zend_hash.c
Zend/zend_highlight.c
Zend/zend_language_parser.y
Zend/zend_language_scanner.c
Zend/zend_language_scanner_defs.h
Zend/zend_variables.c
Zend/zend_vm_def.h
Zend/zend_vm_execute.h
ext/date/php_date.c
ext/dom/documenttype.c
ext/hash/hash.c
ext/iconv/iconv.c
ext/mbstring/tests/zend_multibyte-10.phpt
ext/mbstring/tests/zend_multibyte-11.phpt
ext/mbstring/tests/zend_multibyte-12.phpt
ext/mysql/php_mysql.c
ext/mysqli/mysqli.c
ext/mysqlnd/mysqlnd_reverse_api.c
ext/mysqlnd/php_mysqlnd.c
ext/opcache/ZendAccelerator.c
ext/opcache/zend_accelerator_util_funcs.c
ext/opcache/zend_persist.c
ext/opcache/zend_persist_calc.c
ext/pcre/php_pcre.c
ext/pdo/pdo_dbh.c
ext/pdo/pdo_stmt.c
ext/pdo_pgsql/pgsql_driver.c
ext/pgsql/pgsql.c
ext/reflection/php_reflection.c
ext/session/session.c
ext/spl/spl_array.c
ext/spl/spl_observer.c
ext/standard/array.c
ext/standard/basic_functions.c
ext/standard/html.c
ext/standard/mail.c
ext/standard/php_array.h
ext/standard/proc_open.c
ext/standard/streamsfuncs.c
ext/standard/user_filters.c
ext/standard/var_unserializer.c
ext/standard/var_unserializer.re
main/php_variables.c
sapi/phpdbg/phpdbg.c
sapi/phpdbg/phpdbg_bp.c
sapi/phpdbg/phpdbg_frame.c
sapi/phpdbg/phpdbg_help.c
sapi/phpdbg/phpdbg_list.c
sapi/phpdbg/phpdbg_print.c
sapi/phpdbg/phpdbg_prompt.c
Diffstat (limited to 'ext/mysqlnd')
-rw-r--r-- | ext/mysqlnd/mysqlnd.c | 181 | ||||
-rw-r--r-- | ext/mysqlnd/mysqlnd.h | 16 | ||||
-rw-r--r-- | ext/mysqlnd/mysqlnd_enum_n_def.h | 21 | ||||
-rw-r--r-- | ext/mysqlnd/mysqlnd_ext_plugin.c | 74 | ||||
-rw-r--r-- | ext/mysqlnd/mysqlnd_ext_plugin.h | 15 | ||||
-rw-r--r-- | ext/mysqlnd/mysqlnd_priv.h | 6 | ||||
-rw-r--r-- | ext/mysqlnd/mysqlnd_ps.c | 236 | ||||
-rw-r--r-- | ext/mysqlnd/mysqlnd_ps_codec.c | 105 | ||||
-rw-r--r-- | ext/mysqlnd/mysqlnd_result.c | 1231 | ||||
-rw-r--r-- | ext/mysqlnd/mysqlnd_result.h | 3 | ||||
-rw-r--r-- | ext/mysqlnd/mysqlnd_result_meta.c | 14 | ||||
-rw-r--r-- | ext/mysqlnd/mysqlnd_reverse_api.c | 9 | ||||
-rw-r--r-- | ext/mysqlnd/mysqlnd_reverse_api.h | 3 | ||||
-rw-r--r-- | ext/mysqlnd/mysqlnd_structs.h | 185 | ||||
-rw-r--r-- | ext/mysqlnd/mysqlnd_wireprotocol.c | 54 | ||||
-rw-r--r-- | ext/mysqlnd/mysqlnd_wireprotocol.h | 7 | ||||
-rw-r--r-- | ext/mysqlnd/php_mysqlnd.c | 21 |
17 files changed, 1493 insertions, 688 deletions
diff --git a/ext/mysqlnd/mysqlnd.c b/ext/mysqlnd/mysqlnd.c index 39813145ba..893a144c4b 100644 --- a/ext/mysqlnd/mysqlnd.c +++ b/ext/mysqlnd/mysqlnd.c @@ -689,7 +689,7 @@ MYSQLND_METHOD(mysqlnd_conn_data, execute_init_commands)(MYSQLND_CONN_DATA * con break; } if (conn->last_query_type == QUERY_SELECT) { - MYSQLND_RES * result = conn->m->use_result(conn TSRMLS_CC); + MYSQLND_RES * result = conn->m->use_result(conn, 0 TSRMLS_CC); if (result) { result->m.free_result(result, TRUE TSRMLS_CC); } @@ -1111,7 +1111,8 @@ PHPAPI MYSQLND * mysqlnd_connect(MYSQLND * conn_handle, const char * db, unsigned int db_len, unsigned int port, const char * socket_or_pipe, - unsigned int mysql_flags + unsigned int mysql_flags, + unsigned int client_api_flags TSRMLS_DC) { enum_func_status ret = FAIL; @@ -1122,7 +1123,7 @@ PHPAPI MYSQLND * mysqlnd_connect(MYSQLND * conn_handle, if (!conn_handle) { self_alloced = TRUE; - if (!(conn_handle = mysqlnd_init(FALSE))) { + if (!(conn_handle = mysqlnd_init(client_api_flags, FALSE))) { /* OOM */ DBG_RETURN(NULL); } @@ -1476,8 +1477,7 @@ MYSQLND_METHOD(mysqlnd_conn_data, list_fields)(MYSQLND_CONN_DATA * conn, const c } result->type = MYSQLND_RES_NORMAL; - result->m.fetch_row = result->m.fetch_row_normal_unbuffered; - result->unbuf = mnd_ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED)); + result->unbuf = mysqlnd_result_unbuffered_init(result->field_count, FALSE, result->persistent TSRMLS_CC); if (!result->unbuf) { /* OOM */ SET_OOM_ERROR(*conn->error_info); @@ -1523,7 +1523,7 @@ MYSQLND_METHOD(mysqlnd_conn_data, list_method)(MYSQLND_CONN_DATA * conn, const c } if (PASS == conn->m->query(conn, show_query, show_query_len TSRMLS_CC)) { - result = conn->m->store_result(conn TSRMLS_CC); + result = conn->m->store_result(conn, MYSQLND_STORE_NO_COPY TSRMLS_CC); } if (show_query != query) { mnd_sprintf_free(show_query); @@ -2519,7 +2519,7 @@ end: /* {{{ mysqlnd_conn_data::use_result */ static MYSQLND_RES * -MYSQLND_METHOD(mysqlnd_conn_data, use_result)(MYSQLND_CONN_DATA * const conn TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_conn_data, use_result)(MYSQLND_CONN_DATA * const conn, const unsigned int flags TSRMLS_DC) { size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, use_result); MYSQLND_RES * result = NULL; @@ -2561,7 +2561,7 @@ MYSQLND_METHOD(mysqlnd_conn_data, use_result)(MYSQLND_CONN_DATA * const conn TSR /* {{{ mysqlnd_conn_data::store_result */ static MYSQLND_RES * -MYSQLND_METHOD(mysqlnd_conn_data, store_result)(MYSQLND_CONN_DATA * const conn TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_conn_data, store_result)(MYSQLND_CONN_DATA * const conn, const unsigned int flags TSRMLS_DC) { size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, store_result); MYSQLND_RES * result = NULL; @@ -2571,6 +2571,7 @@ MYSQLND_METHOD(mysqlnd_conn_data, store_result)(MYSQLND_CONN_DATA * const conn T if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { do { + unsigned int f = flags; if (!conn->current_result) { break; } @@ -2584,7 +2585,24 @@ MYSQLND_METHOD(mysqlnd_conn_data, store_result)(MYSQLND_CONN_DATA * const conn T MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_BUFFERED_SETS); - result = conn->current_result->m.store_result(conn->current_result, conn, FALSE TSRMLS_CC); + /* overwrite */ + if ((conn->m->get_client_api_capabilities(conn TSRMLS_CC) & MYSQLND_CLIENT_KNOWS_RSET_COPY_DATA)) { + if (MYSQLND_G(fetch_data_copy)) { + f &= ~MYSQLND_STORE_NO_COPY; + f |= MYSQLND_STORE_COPY; + } + } else { + /* if for some reason PDO borks something */ + if (!(f & (MYSQLND_STORE_NO_COPY | MYSQLND_STORE_COPY))) { + f |= MYSQLND_STORE_COPY; + } + } + if (!(f & (MYSQLND_STORE_NO_COPY | MYSQLND_STORE_COPY))) { + SET_CLIENT_ERROR(*conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "Unknown fetch mode"); + DBG_ERR("Unknown fetch mode"); + break; + } + result = conn->current_result->m.store_result(conn->current_result, conn, f TSRMLS_CC); if (!result) { conn->current_result->m.free_result(conn->current_result, TRUE TSRMLS_CC); } @@ -2652,24 +2670,24 @@ MYSQLND_METHOD(mysqlnd_conn_data, tx_cor_options_to_string)(const MYSQLND_CONN_D { if (mode & TRANS_COR_AND_CHAIN && !(mode & TRANS_COR_AND_NO_CHAIN)) { if (str->len) { - smart_str_appendl(str, ", ", sizeof(", ") - 1); + smart_str_appendl(str, " ", sizeof(" ") - 1); } smart_str_appendl(str, "AND CHAIN", sizeof("AND CHAIN") - 1); } else if (mode & TRANS_COR_AND_NO_CHAIN && !(mode & TRANS_COR_AND_CHAIN)) { if (str->len) { - smart_str_appendl(str, ", ", sizeof(", ") - 1); + smart_str_appendl(str, " ", sizeof(" ") - 1); } smart_str_appendl(str, "AND NO CHAIN", sizeof("AND NO CHAIN") - 1); } if (mode & TRANS_COR_RELEASE && !(mode & TRANS_COR_NO_RELEASE)) { if (str->len) { - smart_str_appendl(str, ", ", sizeof(", ") - 1); + smart_str_appendl(str, " ", sizeof(" ") - 1); } smart_str_appendl(str, "RELEASE", sizeof("RELEASE") - 1); } else if (mode & TRANS_COR_NO_RELEASE && !(mode & TRANS_COR_RELEASE)) { if (str->len) { - smart_str_appendl(str, ", ", sizeof(", ") - 1); + smart_str_appendl(str, " ", sizeof(" ") - 1); } smart_str_appendl(str, "NO RELEASE", sizeof("NO RELEASE") - 1); } @@ -2678,6 +2696,49 @@ MYSQLND_METHOD(mysqlnd_conn_data, tx_cor_options_to_string)(const MYSQLND_CONN_D /* }}} */ +/* {{{ mysqlnd_escape_string_for_tx_name_in_comment */ +static char * +mysqlnd_escape_string_for_tx_name_in_comment(const char * const name TSRMLS_DC) +{ + char * ret = NULL; + DBG_ENTER("mysqlnd_escape_string_for_tx_name_in_comment"); + if (name) { + zend_bool warned = FALSE; + const char * p_orig = name; + char * p_copy; + p_copy = ret = mnd_emalloc(strlen(name) + 1 + 2 + 2 + 1); /* space, open, close, NullS */ + *p_copy++ = ' '; + *p_copy++ = '/'; + *p_copy++ = '*'; + while (1) { + register char v = *p_orig; + if (v == 0) { + break; + } + if ((v >= '0' && v <= '9') || + (v >= 'a' && v <= 'z') || + (v >= 'A' && v <= 'Z') || + v == '-' || + v == '_' || + v == ' ' || + v == '=') + { + *p_copy++ = v; + } else if (warned == FALSE) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Transaction name truncated. Must be only [0-9A-Za-z\\-_=]+"); + warned = TRUE; + } + ++p_orig; + } + *p_copy++ = '*'; + *p_copy++ = '/'; + *p_copy++ = 0; + } + DBG_RETURN(ret); +} +/* }}} */ + + /* {{{ mysqlnd_conn_data::tx_commit_ex */ static enum_func_status MYSQLND_METHOD(mysqlnd_conn_data, tx_commit_or_rollback)(MYSQLND_CONN_DATA * conn, const zend_bool commit, const unsigned int flags, const char * const name TSRMLS_DC) @@ -2692,23 +2753,26 @@ MYSQLND_METHOD(mysqlnd_conn_data, tx_commit_or_rollback)(MYSQLND_CONN_DATA * con conn->m->tx_cor_options_to_string(conn, &tmp_str, flags TSRMLS_CC); smart_str_0(&tmp_str); + { - char * commented_name = NULL; - unsigned int commented_name_len = name? mnd_sprintf(&commented_name, 0, " /*%s*/", name):0; char * query; - unsigned int query_len = mnd_sprintf(&query, 0, (commit? "COMMIT%s %s":"ROLLBACK%s %s"), - commented_name? commented_name:"", tmp_str.c? tmp_str.c:""); + size_t query_len; + char * name_esc = mysqlnd_escape_string_for_tx_name_in_comment(name TSRMLS_CC); + + query_len = mnd_sprintf(&query, 0, (commit? "COMMIT%s %s":"ROLLBACK%s %s"), + name_esc? name_esc:"", tmp_str.c? tmp_str.c:""); smart_str_free(&tmp_str); - + if (name_esc) { + mnd_efree(name_esc); + name_esc = NULL; + } if (!query) { SET_OOM_ERROR(*conn->error_info); break; } + ret = conn->m->query(conn, query, query_len TSRMLS_CC); mnd_sprintf_free(query); - if (commented_name) { - mnd_sprintf_free(commented_name); - } } } while (0); conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); @@ -2736,36 +2800,41 @@ MYSQLND_METHOD(mysqlnd_conn_data, tx_begin)(MYSQLND_CONN_DATA * conn, const unsi } smart_str_appendl(&tmp_str, "WITH CONSISTENT SNAPSHOT", sizeof("WITH CONSISTENT SNAPSHOT") - 1); } - if (mode & TRANS_START_READ_WRITE) { - if (tmp_str.len) { - smart_str_appendl(&tmp_str, ", ", sizeof(", ") - 1); - } - smart_str_appendl(&tmp_str, "READ WRITE", sizeof("READ WRITE") - 1); - } - if (mode & TRANS_START_READ_ONLY) { - if (tmp_str.len) { - smart_str_appendl(&tmp_str, ", ", sizeof(", ") - 1); + if (mode & (TRANS_START_READ_WRITE | TRANS_START_READ_ONLY)) { + unsigned long server_version = conn->m->get_server_version(conn TSRMLS_CC); + if (server_version < 50605L) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "This server version doesn't support 'READ WRITE' and 'READ ONLY'. Minimum 5.6.5 is required"); + smart_str_free(&tmp_str); + break; + } else if (mode & TRANS_START_READ_WRITE) { + if (tmp_str.len) { + smart_str_appendl(&tmp_str, ", ", sizeof(", ") - 1); + } + smart_str_appendl(&tmp_str, "READ WRITE", sizeof("READ WRITE") - 1); + } else if (mode & TRANS_START_READ_ONLY) { + if (tmp_str.len) { + smart_str_appendl(&tmp_str, ", ", sizeof(", ") - 1); + } + smart_str_appendl(&tmp_str, "READ ONLY", sizeof("READ ONLY") - 1); } - smart_str_appendl(&tmp_str, "READ ONLY", sizeof("READ ONLY") - 1); } smart_str_0(&tmp_str); { - char * commented_name = NULL; - unsigned int commented_name_len = name? mnd_sprintf(&commented_name, 0, " /*%s*/", name):0; + char * name_esc = mysqlnd_escape_string_for_tx_name_in_comment(name TSRMLS_CC); char * query; - unsigned int query_len = mnd_sprintf(&query, 0, "START TRANSACTION%s %s", commented_name? commented_name:"", tmp_str.c? tmp_str.c:""); + unsigned int query_len = mnd_sprintf(&query, 0, "START TRANSACTION%s %s", name_esc? name_esc:"", tmp_str.c? tmp_str.c:""); smart_str_free(&tmp_str); - + if (name_esc) { + mnd_efree(name_esc); + name_esc = NULL; + } if (!query) { SET_OOM_ERROR(*conn->error_info); break; } ret = conn->m->query(conn, query, query_len TSRMLS_CC); mnd_sprintf_free(query); - if (commented_name) { - mnd_sprintf_free(commented_name); - } } } while (0); conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); @@ -2840,6 +2909,32 @@ MYSQLND_METHOD(mysqlnd_conn_data, tx_savepoint_release)(MYSQLND_CONN_DATA * conn /* }}} */ +/* {{{ mysqlnd_conn_data::negotiate_client_api_capabilities */ +static unsigned int +MYSQLND_METHOD(mysqlnd_conn_data, negotiate_client_api_capabilities)(MYSQLND_CONN_DATA * const conn, const unsigned int flags TSRMLS_DC) +{ + unsigned int ret = 0; + DBG_ENTER("mysqlnd_conn_data::negotiate_client_api_capabilities"); + if (conn) { + ret = conn->client_api_capabilities; + conn->client_api_capabilities = flags; + } + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::get_client_api_capabilities */ +static unsigned int +MYSQLND_METHOD(mysqlnd_conn_data, get_client_api_capabilities)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_conn_data::get_client_api_capabilities"); + DBG_RETURN(conn? conn->client_api_capabilities : 0); +} +/* }}} */ + + /* {{{ mysqlnd_conn_data::local_tx_start */ static enum_func_status MYSQLND_METHOD(mysqlnd_conn_data, local_tx_start)(MYSQLND_CONN_DATA * conn, size_t this_func TSRMLS_DC) @@ -2968,7 +3063,10 @@ MYSQLND_CLASS_METHODS_START(mysqlnd_conn_data) MYSQLND_METHOD(mysqlnd_conn_data, simple_command_send_request), MYSQLND_METHOD(mysqlnd_conn_data, fetch_auth_plugin_by_name), - MYSQLND_METHOD(mysqlnd_conn_data, set_client_option_2d) + MYSQLND_METHOD(mysqlnd_conn_data, set_client_option_2d), + + MYSQLND_METHOD(mysqlnd_conn_data, negotiate_client_api_capabilities), + MYSQLND_METHOD(mysqlnd_conn_data, get_client_api_capabilities) MYSQLND_CLASS_METHODS_END; @@ -3047,11 +3145,14 @@ MYSQLND_CLASS_METHODS_END; /* {{{ _mysqlnd_init */ PHPAPI MYSQLND * -_mysqlnd_init(zend_bool persistent TSRMLS_DC) +_mysqlnd_init(unsigned int flags, zend_bool persistent TSRMLS_DC) { MYSQLND * ret; DBG_ENTER("mysqlnd_init"); ret = MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory).get_connection(persistent TSRMLS_CC); + if (ret && ret->data) { + ret->data->m->negotiate_client_api_capabilities(ret->data, flags TSRMLS_CC); + } DBG_RETURN(ret); } /* }}} */ diff --git a/ext/mysqlnd/mysqlnd.h b/ext/mysqlnd/mysqlnd.h index bf9ad53b11..f373ea5dd5 100644 --- a/ext/mysqlnd/mysqlnd.h +++ b/ext/mysqlnd/mysqlnd.h @@ -85,15 +85,16 @@ PHPAPI const MYSQLND_CHARSET * mysqlnd_find_charset_name(const char * const char /* Connect */ -#define mysqlnd_init(persistent) _mysqlnd_init((persistent) TSRMLS_CC) -PHPAPI MYSQLND * _mysqlnd_init(zend_bool persistent TSRMLS_DC); +#define mysqlnd_init(client_flags, persistent) _mysqlnd_init((client_flags), (persistent) TSRMLS_CC) +PHPAPI MYSQLND * _mysqlnd_init(unsigned int client_flags, zend_bool persistent TSRMLS_DC); PHPAPI MYSQLND * mysqlnd_connect(MYSQLND * conn, const char * host, const char * user, const char * passwd, unsigned int passwd_len, const char * db, unsigned int db_len, unsigned int port, const char * socket_or_pipe, - unsigned int mysql_flags + unsigned int mysql_flags, + unsigned int client_api_flags TSRMLS_DC); #define mysqlnd_change_user(conn, user, passwd, db, silent) ((conn)->data)->m->change_user((conn)->data, (user), (passwd), (db), (silent), strlen((passwd)) TSRMLS_CC) @@ -119,8 +120,9 @@ PHPAPI void _mysqlnd_debug(const char *mode TSRMLS_DC); PHPAPI enum_func_status _mysqlnd_poll(MYSQLND **r_array, MYSQLND **e_array, MYSQLND ***dont_poll, long sec, long usec, int * desc_num TSRMLS_DC); -#define mysqlnd_use_result(conn) ((conn)->data)->m->use_result((conn)->data TSRMLS_CC) -#define mysqlnd_store_result(conn) ((conn)->data)->m->store_result((conn)->data TSRMLS_CC) +#define mysqlnd_use_result(conn) ((conn)->data)->m->use_result((conn)->data, 0 TSRMLS_CC) +#define mysqlnd_store_result(conn) ((conn)->data)->m->store_result((conn)->data, MYSQLND_STORE_NO_COPY TSRMLS_CC) +#define mysqlnd_store_result_ofs(conn) ((conn)->data)->m->store_result((conn)->data, MYSQLND_STORE_COPY TSRMLS_CC) #define mysqlnd_next_result(conn) ((conn)->data)->m->next_result((conn)->data TSRMLS_CC) #define mysqlnd_more_results(conn) ((conn)->data)->m->more_results((conn)->data TSRMLS_CC) #define mysqlnd_free_result(r,e_or_i) ((MYSQLND_RES*)r)->m.free_result(((MYSQLND_RES*)(r)), (e_or_i) TSRMLS_CC) @@ -150,8 +152,7 @@ PHPAPI enum_func_status _mysqlnd_poll(MYSQLND **r_array, MYSQLND **e_array, MYSQ #define mysqlnd_num_rows(result) (result)->m.num_rows((result) TSRMLS_CC) #define mysqlnd_num_fields(result) (result)->m.num_fields((result) TSRMLS_CC) -#define mysqlnd_fetch_lengths(result) _mysqlnd_fetch_lengths((result) TSRMLS_CC) -PHPAPI unsigned long * _mysqlnd_fetch_lengths(MYSQLND_RES * const result TSRMLS_DC); +#define mysqlnd_fetch_lengths(result) (result)->m.fetch_lengths((result) TSRMLS_CC) #define mysqlnd_field_seek(result, ofs) (result)->m.seek_field((result), (ofs) TSRMLS_CC) #define mysqlnd_field_tell(result) (result)->m.field_tell((result) TSRMLS_CC) @@ -282,6 +283,7 @@ ZEND_BEGIN_MODULE_GLOBALS(mysqlnd) long debug_calloc_fail_threshold; long debug_realloc_fail_threshold; char * sha256_server_public_key; + zend_bool fetch_data_copy; ZEND_END_MODULE_GLOBALS(mysqlnd) PHPAPI ZEND_EXTERN_MODULE_GLOBALS(mysqlnd) diff --git a/ext/mysqlnd/mysqlnd_enum_n_def.h b/ext/mysqlnd/mysqlnd_enum_n_def.h index 3d65001382..e1fc5f8f01 100644 --- a/ext/mysqlnd/mysqlnd_enum_n_def.h +++ b/ext/mysqlnd/mysqlnd_enum_n_def.h @@ -561,6 +561,10 @@ enum mysqlnd_packet_type }; +/* + After adding new elements please update + `mysqlnd_command_to_text` in mysqlnd_wireprotocol.c +*/ enum php_mysqlnd_server_command { COM_SLEEP = 0, @@ -593,6 +597,8 @@ enum php_mysqlnd_server_command COM_SET_OPTION = 27, COM_STMT_FETCH = 28, COM_DAEMON, + COM_BINLOG_DUMP_GTID, + COM_RESET_CONNECTION, COM_END }; @@ -609,6 +615,21 @@ enum php_mysqlnd_server_command #define MYSQLND_REFRESH_MASTER 128 /* Remove all bin logs in the index */ #define MYSQLND_REFRESH_BACKUP_LOG 0x200000L + +#define MYSQLND_STORE_PS 1 +#define MYSQLND_STORE_NO_COPY 2 +#define MYSQLND_STORE_COPY 4 + +enum mysqlnd_buffered_type +{ + MYSQLND_BUFFERED_TYPE_ZVAL = 1, + MYSQLND_BUFFERED_TYPE_C +}; + + +#define MYSQLND_CLIENT_NO_FLAG 0 +#define MYSQLND_CLIENT_KNOWS_RSET_COPY_DATA 1 + #endif /* MYSQLND_ENUM_N_DEF_H */ diff --git a/ext/mysqlnd/mysqlnd_ext_plugin.c b/ext/mysqlnd/mysqlnd_ext_plugin.c index f40688b272..13f1e294dc 100644 --- a/ext/mysqlnd/mysqlnd_ext_plugin.c +++ b/ext/mysqlnd/mysqlnd_ext_plugin.c @@ -70,6 +70,44 @@ PHPAPI void ** _mysqlnd_plugin_get_plugin_result_data(const MYSQLND_RES * result /* }}} */ +/* {{{ _mysqlnd_plugin_get_plugin_result_unbuffered_data */ +PHPAPI void ** _mysqlnd_plugin_get_plugin_result_unbuffered_data(const MYSQLND_RES_UNBUFFERED * result, unsigned int plugin_id TSRMLS_DC) +{ + DBG_ENTER("_mysqlnd_plugin_get_plugin_result_data"); + DBG_INF_FMT("plugin_id=%u", plugin_id); + if (!result || plugin_id >= mysqlnd_plugin_count()) { + return NULL; + } + DBG_RETURN((void *)((char *)result + sizeof(MYSQLND_RES_UNBUFFERED) + plugin_id * sizeof(void *))); +} +/* }}} */ + + +/* {{{ _mysqlnd_plugin_get_plugin_result_buffered_data */ +PHPAPI void ** _mysqlnd_plugin_get_plugin_result_buffered_data_zval(const MYSQLND_RES_BUFFERED_ZVAL * result, unsigned int plugin_id TSRMLS_DC) +{ + DBG_ENTER("_mysqlnd_plugin_get_plugin_result_data"); + DBG_INF_FMT("plugin_id=%u", plugin_id); + if (!result || plugin_id >= mysqlnd_plugin_count()) { + return NULL; + } + DBG_RETURN((void *)((char *)result + sizeof(MYSQLND_RES_BUFFERED_ZVAL) + plugin_id * sizeof(void *))); +} +/* }}} */ + +/* {{{ _mysqlnd_plugin_get_plugin_result_buffered_data */ +PHPAPI void ** _mysqlnd_plugin_get_plugin_result_buffered_data_c(const MYSQLND_RES_BUFFERED_C * result, unsigned int plugin_id TSRMLS_DC) +{ + DBG_ENTER("_mysqlnd_plugin_get_plugin_result_data"); + DBG_INF_FMT("plugin_id=%u", plugin_id); + if (!result || plugin_id >= mysqlnd_plugin_count()) { + return NULL; + } + DBG_RETURN((void *)((char *)result + sizeof(MYSQLND_RES_BUFFERED_C) + plugin_id * sizeof(void *))); +} +/* }}} */ + + /* {{{ _mysqlnd_plugin_get_plugin_protocol_data */ PHPAPI void ** _mysqlnd_plugin_get_plugin_protocol_data(const MYSQLND_PROTOCOL * protocol, unsigned int plugin_id TSRMLS_DC) @@ -161,6 +199,42 @@ mysqlnd_result_set_methods(struct st_mysqlnd_res_methods * methods) /* }}} */ +/* {{{ mysqlnd_result_unbuffered_get_methods */ +PHPAPI struct st_mysqlnd_result_unbuffered_methods * +mysqlnd_result_unbuffered_get_methods() +{ + return &MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_result_unbuffered); +} +/* }}} */ + + +/* {{{ mysqlnd_result_unbuffered_set_methods */ +PHPAPI void +mysqlnd_result_unbuffered_set_methods(struct st_mysqlnd_result_unbuffered_methods * methods) +{ + MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_result_unbuffered) = *methods; +} +/* }}} */ + + +/* {{{ mysqlnd_result_buffered_get_methods */ +PHPAPI struct st_mysqlnd_result_buffered_methods * +mysqlnd_result_buffered_get_methods() +{ + return &MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_result_buffered); +} +/* }}} */ + + +/* {{{ mysqlnd_result_buffered_set_methods */ +PHPAPI void +mysqlnd_result_buffered_set_methods(struct st_mysqlnd_result_buffered_methods * methods) +{ + MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_result_buffered) = *methods; +} +/* }}} */ + + /* {{{ mysqlnd_stmt_get_methods */ PHPAPI struct st_mysqlnd_stmt_methods * mysqlnd_stmt_get_methods() diff --git a/ext/mysqlnd/mysqlnd_ext_plugin.h b/ext/mysqlnd/mysqlnd_ext_plugin.h index 2a2416a042..d4a9d6cfc0 100644 --- a/ext/mysqlnd/mysqlnd_ext_plugin.h +++ b/ext/mysqlnd/mysqlnd_ext_plugin.h @@ -31,6 +31,15 @@ PHPAPI void ** _mysqlnd_plugin_get_plugin_connection_data_data(const MYSQLND_CON PHPAPI void ** _mysqlnd_plugin_get_plugin_result_data(const MYSQLND_RES * result, unsigned int plugin_id TSRMLS_DC); #define mysqlnd_plugin_get_plugin_result_data(r, p_id) _mysqlnd_plugin_get_plugin_result_data((r), (p_id) TSRMLS_CC) +PHPAPI void ** _mysqlnd_plugin_get_plugin_result_unbuffered_data(const MYSQLND_RES_UNBUFFERED * result, unsigned int plugin_id TSRMLS_DC); +#define mysqlnd_plugin_get_plugin_result_unbuffered_data(r, p_id) _mysqlnd_plugin_get_plugin_result_unbuffered_data((r), (p_id) TSRMLS_CC) + +PHPAPI void ** _mysqlnd_plugin_get_plugin_result_buffered_data_zval(const MYSQLND_RES_BUFFERED_ZVAL * result, unsigned int plugin_id TSRMLS_DC); +#define mysqlnd_plugin_get_plugin_result_buffered_data_zval(r, p_id) _mysqlnd_plugin_get_plugin_result_buffered_data_zval((r), (p_id) TSRMLS_CC) + +PHPAPI void ** _mysqlnd_plugin_get_plugin_result_buffered_data_c(const MYSQLND_RES_BUFFERED_C * result, unsigned int plugin_id TSRMLS_DC); +#define mysqlnd_plugin_get_plugin_result_buffered_data_c(r, p_id) _mysqlnd_plugin_get_plugin_result_buffered_data_c((r), (p_id) TSRMLS_CC) + PHPAPI void ** _mysqlnd_plugin_get_plugin_stmt_data(const MYSQLND_STMT * stmt, unsigned int plugin_id TSRMLS_DC); #define mysqlnd_plugin_get_plugin_stmt_data(s, p_id) _mysqlnd_plugin_get_plugin_stmt_data((s), (p_id) TSRMLS_CC) @@ -50,6 +59,12 @@ PHPAPI void mysqlnd_conn_data_set_methods(struct st_mysqlnd_conn_data_methods * PHPAPI struct st_mysqlnd_res_methods * mysqlnd_result_get_methods(); PHPAPI void mysqlnd_result_set_methods(struct st_mysqlnd_res_methods * methods); +PHPAPI struct st_mysqlnd_result_unbuffered_methods * mysqlnd_result_unbuffered_get_methods(); +PHPAPI void mysqlnd_result_unbuffered_set_methods(struct st_mysqlnd_result_unbuffered_methods * methods); + +PHPAPI struct st_mysqlnd_result_buffered_methods * mysqlnd_result_buffered_get_methods(); +PHPAPI void mysqlnd_result_buffered_set_methods(struct st_mysqlnd_result_buffered_methods * methods); + PHPAPI struct st_mysqlnd_stmt_methods * mysqlnd_stmt_get_methods(); PHPAPI void mysqlnd_stmt_set_methods(struct st_mysqlnd_stmt_methods * methods); diff --git a/ext/mysqlnd/mysqlnd_priv.h b/ext/mysqlnd/mysqlnd_priv.h index deb0bdc8c8..a44e29d9f9 100644 --- a/ext/mysqlnd/mysqlnd_priv.h +++ b/ext/mysqlnd/mysqlnd_priv.h @@ -171,6 +171,10 @@ struct st_mysqlnd_perm_bind { extern struct st_mysqlnd_perm_bind mysqlnd_ps_fetch_functions[MYSQL_TYPE_LAST + 1]; +enum_func_status mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything TSRMLS_DC); +enum_func_status mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything TSRMLS_DC); + + PHPAPI extern const char * const mysqlnd_old_passwd; PHPAPI extern const char * const mysqlnd_out_of_sync; PHPAPI extern const char * const mysqlnd_server_gone; @@ -180,6 +184,8 @@ PHPAPI extern MYSQLND_CLASS_METHOD_TABLE_NAME_FORWARD(mysqlnd_object_factory); PHPAPI extern MYSQLND_CLASS_METHOD_TABLE_NAME_FORWARD(mysqlnd_conn); PHPAPI extern MYSQLND_CLASS_METHOD_TABLE_NAME_FORWARD(mysqlnd_conn_data); PHPAPI extern MYSQLND_CLASS_METHOD_TABLE_NAME_FORWARD(mysqlnd_res); +PHPAPI extern MYSQLND_CLASS_METHOD_TABLE_NAME_FORWARD(mysqlnd_result_unbuffered); +PHPAPI extern MYSQLND_CLASS_METHOD_TABLE_NAME_FORWARD(mysqlnd_result_buffered); PHPAPI extern MYSQLND_CLASS_METHOD_TABLE_NAME_FORWARD(mysqlnd_protocol); PHPAPI extern MYSQLND_CLASS_METHOD_TABLE_NAME_FORWARD(mysqlnd_net); diff --git a/ext/mysqlnd/mysqlnd_ps.c b/ext/mysqlnd/mysqlnd_ps.c index 8096cbbbd9..539d1c8f83 100644 --- a/ext/mysqlnd/mysqlnd_ps.c +++ b/ext/mysqlnd/mysqlnd_ps.c @@ -38,14 +38,7 @@ const char * const mysqlnd_stmt_not_prepared = "Statement not prepared"; /* Exported by mysqlnd_ps_codec.c */ enum_func_status mysqlnd_stmt_execute_generate_request(MYSQLND_STMT * const s, zend_uchar ** request, size_t *request_len, zend_bool * free_buffer TSRMLS_DC); - -enum_func_status mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES *result, void *param, - unsigned int flags, - zend_bool *fetched_anything TSRMLS_DC); - -enum_func_status mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, - unsigned int flags, - zend_bool *fetched_anything TSRMLS_DC); +enum_func_status mysqlnd_stmt_execute_batch_generate_request(MYSQLND_STMT * const s, zend_uchar ** request, size_t *request_len, zend_bool * free_buffer TSRMLS_DC); static void mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const stmt TSRMLS_DC); static void mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const stmt, unsigned int param_no TSRMLS_DC); @@ -95,15 +88,42 @@ MYSQLND_METHOD(mysqlnd_stmt, store_result)(MYSQLND_STMT * const s TSRMLS_DC) result = stmt->result; result->type = MYSQLND_RES_PS_BUF; - result->m.fetch_row = mysqlnd_stmt_fetch_row_buffered; - result->m.fetch_lengths = NULL;/* makes no sense */ - result->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol; +/* result->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol; */ - result->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC); + result->stored_data = (MYSQLND_RES_BUFFERED *) mysqlnd_result_buffered_zval_init(result->field_count, TRUE, result->persistent TSRMLS_CC); + if (!result->stored_data) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(NULL); + } - ret = result->m.store_result_fetch_data(conn, result, result->meta, TRUE TSRMLS_CC); + ret = result->m.store_result_fetch_data(conn, result, result->meta, &result->stored_data->row_buffers, TRUE TSRMLS_CC); + + result->stored_data->m.fetch_row = mysqlnd_stmt_fetch_row_buffered; if (PASS == ret) { + /* Overflow ? */ + if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_ZVAL) { + MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data; + if (result->stored_data->row_count) { + /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */ + if (result->stored_data->row_count * result->meta->field_count * sizeof(zval *) > SIZE_MAX) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(NULL); + } + /* if pecalloc is used valgrind barks gcc version 4.3.1 20080507 (prerelease) [gcc-4_3-branch revision 135036] (SUSE Linux) */ + set->data = mnd_emalloc((size_t)(result->stored_data->row_count * result->meta->field_count * sizeof(zval *))); + if (!set->data) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(NULL); + } + memset(set->data, 0, (size_t)(result->stored_data->row_count * result->meta->field_count * sizeof(zval *))); + } + /* Position at the first row */ + set->data_cursor = set->data; + } else if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_ZVAL) { + /*TODO*/ + } + /* libmysql API docs say it should be so for SELECT statements */ stmt->upsert_status->affected_rows = stmt->result->stored_data->row_count; @@ -171,7 +191,7 @@ MYSQLND_METHOD(mysqlnd_stmt, get_result)(MYSQLND_STMT * const s TSRMLS_DC) break; } - if ((result = result->m.store_result(result, conn, TRUE TSRMLS_CC))) { + if ((result = result->m.store_result(result, conn, MYSQLND_STORE_PS | MYSQLND_STORE_NO_COPY TSRMLS_CC))) { stmt->upsert_status->affected_rows = result->stored_data->row_count; stmt->state = MYSQLND_STMT_PREPARED; result->type = MYSQLND_RES_PS_BUF; @@ -521,7 +541,11 @@ mysqlnd_stmt_execute_parse_response(MYSQLND_STMT * const s TSRMLS_DC) /* Update stmt->field_count as SHOW sets it to 0 at prepare */ stmt->field_count = stmt->result->field_count = conn->field_count; - stmt->result->lengths = NULL; + if (stmt->result->stored_data) { + stmt->result->stored_data->lengths = NULL; + } else if (stmt->result->unbuf) { + stmt->result->unbuf->lengths = NULL; + } if (stmt->field_count) { stmt->state = MYSQLND_STMT_WAITING_USE_OR_STORE; /* @@ -709,94 +733,98 @@ MYSQLND_METHOD(mysqlnd_stmt, execute)(MYSQLND_STMT * const s TSRMLS_DC) /* {{{ mysqlnd_stmt_fetch_row_buffered */ enum_func_status -mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES *result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC) +mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything TSRMLS_DC) { MYSQLND_STMT * s = (MYSQLND_STMT *) param; MYSQLND_STMT_DATA * stmt = s? s->data:NULL; - MYSQLND_RES_BUFFERED *set = result->stored_data; - unsigned int field_count = result->meta->field_count; + const MYSQLND_RES_METADATA * const meta = result->meta; + unsigned int field_count = meta->field_count; DBG_ENTER("mysqlnd_stmt_fetch_row_buffered"); *fetched_anything = FALSE; DBG_INF_FMT("stmt=%lu", stmt != NULL ? stmt->stmt_id : 0L); /* If we haven't read everything */ - 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) { - unsigned int i; - MYSQLND_RES_METADATA * meta = result->meta; - zval **current_row = set->data_cursor; - - if (NULL == current_row[0]) { - uint64_t row_num = (set->data_cursor - set->data) / field_count; - enum_func_status rc = result->m.row_decoder(set->row_buffers[row_num], - current_row, - meta->field_count, - meta->fields, - result->conn->options->int_and_float_native, - result->conn->stats TSRMLS_CC); - if (PASS != rc) { - DBG_RETURN(FAIL); - } - set->initialized_rows++; - 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 (meta->fields[i].max_length < len) { - meta->fields[i].max_length = len; + if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_ZVAL) { + MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data; + if (set->data_cursor && + (set->data_cursor - set->data) < (result->stored_data->row_count * field_count)) + { + /* The user could have skipped binding - don't crash*/ + if (stmt->result_bind) { + unsigned int i; + zval **current_row = set->data_cursor; + + if (NULL == current_row[0]) { + uint64_t row_num = (set->data_cursor - set->data) / field_count; + enum_func_status rc = result->stored_data->m.row_decoder(result->stored_data->row_buffers[row_num], + current_row, + meta->field_count, + meta->fields, + result->conn->options->int_and_float_native, + result->conn->stats TSRMLS_CC); + if (PASS != rc) { + DBG_RETURN(FAIL); + } + result->stored_data->initialized_rows++; + 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 (meta->fields[i].max_length < len) { + meta->fields[i].max_length = len; + } } } } } - } - for (i = 0; i < result->field_count; i++) { - /* Clean what we copied last time */ + 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 - if (stmt->result_bind[i].zv) { - zval_dtor(stmt->result_bind[i].zv); - } + if (stmt->result_bind[i].zv) { + zval_dtor(stmt->result_bind[i].zv); + } #endif - /* copy the type */ - if (stmt->result_bind[i].bound == TRUE) { - DBG_INF_FMT("i=%u type=%u", i, Z_TYPE_P(current_row[i])); - if (Z_TYPE_P(current_row[i]) != IS_NULL) { - /* - Copy the value. - Pre-condition is that the zvals in the result_bind buffer - have been ZVAL_NULL()-ed or to another simple type - (int, double, bool but not string). Because of the reference - counting the user can't delete the strings the variables point to. - */ - - Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(current_row[i]); - stmt->result_bind[i].zv->value = current_row[i]->value; + /* copy the type */ + if (stmt->result_bind[i].bound == TRUE) { + DBG_INF_FMT("i=%u type=%u", i, Z_TYPE_P(current_row[i])); + if (Z_TYPE_P(current_row[i]) != IS_NULL) { + /* + Copy the value. + Pre-condition is that the zvals in the result_bind buffer + have been ZVAL_NULL()-ed or to another simple type + (int, double, bool but not string). Because of the reference + counting the user can't delete the strings the variables point to. + */ + + Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(current_row[i]); + stmt->result_bind[i].zv->value = current_row[i]->value; #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF - zval_copy_ctor(stmt->result_bind[i].zv); + zval_copy_ctor(stmt->result_bind[i].zv); #endif - } else { - ZVAL_NULL(stmt->result_bind[i].zv); + } else { + ZVAL_NULL(stmt->result_bind[i].zv); + } } } } + 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 { + set->data_cursor = NULL; + DBG_INF("no more data"); } - 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 { - set->data_cursor = NULL; - DBG_INF("no more data"); + } else if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_C) { + /*TODO*/ } DBG_INF("PASS"); DBG_RETURN(PASS); @@ -805,13 +833,14 @@ mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES *result, void *param, unsigned int f /* {{{ mysqlnd_stmt_fetch_row_unbuffered */ -static enum_func_status -mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC) +enum_func_status +mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything TSRMLS_DC) { enum_func_status ret; MYSQLND_STMT * s = (MYSQLND_STMT *) param; MYSQLND_STMT_DATA * stmt = s? s->data:NULL; MYSQLND_PACKET_ROW * row_packet; + const MYSQLND_RES_METADATA * const meta = result->meta; DBG_ENTER("mysqlnd_stmt_fetch_row_unbuffered"); @@ -828,7 +857,7 @@ mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int DBG_ERR("command out of sync"); DBG_RETURN(FAIL); } - if (!(row_packet = result->row_packet)) { + if (!(row_packet = result->unbuf->row_packet)) { DBG_RETURN(FAIL); } @@ -837,20 +866,20 @@ mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int /* If we skip rows (stmt == NULL || stmt->result_bind == NULL) we have to - result->m.unbuffered_free_last_data() before it. The function returns always true. + result->unbuf->m.free_last_data() before it. The function returns always true. */ if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) { unsigned int i, field_count = result->field_count; if (!row_packet->skip_extraction) { - result->m.unbuffered_free_last_data(result TSRMLS_CC); + result->unbuf->m.free_last_data(result->unbuf, result->conn? result->conn->stats : NULL TSRMLS_CC); 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 (PASS != result->m.row_decoder(result->unbuf->last_row_buffer, + if (PASS != result->unbuf->m.row_decoder(result->unbuf->last_row_buffer, result->unbuf->last_row_data, row_packet->field_count, row_packet->fields_metadata, @@ -865,14 +894,14 @@ mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int zval *data = result->unbuf->last_row_data[i]; /* stmt->result_bind[i].zv has been already destructed - in result->m.unbuffered_free_last_data() + in result->unbuf->m.free_last_data() */ #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF zval_dtor(stmt->result_bind[i].zv); #endif if (IS_NULL != (Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(data)) ) { - if ((Z_TYPE_P(data) == IS_STRING) && (result->meta->fields[i].max_length < (unsigned long) Z_STRLEN_P(data))) { - result->meta->fields[i].max_length = Z_STRLEN_P(data); + if ((Z_TYPE_P(data) == IS_STRING) && (meta->fields[i].max_length < (unsigned long) Z_STRLEN_P(data))) { + meta->fields[i].max_length = Z_STRLEN_P(data); } stmt->result_bind[i].zv->value = data->value; /* copied data, thus also the ownership. Thus null data */ @@ -884,7 +913,7 @@ mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int } else { DBG_INF("skipping extraction"); /* - Data has been allocated and usually result->m.unbuffered_free_last_data() + Data has been allocated and usually result->unbuf->m.free_last_data() frees it but we can't call this function as it will cause problems with the bound variables. Thus we need to do part of what it does or Zend will report leaks. @@ -959,7 +988,7 @@ MYSQLND_METHOD(mysqlnd_stmt, use_result)(MYSQLND_STMT * s TSRMLS_DC) result = stmt->result; result->m.use_result(stmt->result, TRUE TSRMLS_CC); - result->m.fetch_row = stmt->cursor_exists? mysqlnd_fetch_stmt_row_cursor: + result->unbuf->m.fetch_row = stmt->cursor_exists? mysqlnd_fetch_stmt_row_cursor: mysqlnd_stmt_fetch_row_unbuffered; stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED; @@ -973,7 +1002,7 @@ MYSQLND_METHOD(mysqlnd_stmt, use_result)(MYSQLND_STMT * s TSRMLS_DC) /* {{{ mysqlnd_fetch_row_cursor */ enum_func_status -mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC) +mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything TSRMLS_DC) { enum_func_status ret; MYSQLND_STMT * s = (MYSQLND_STMT *) param; @@ -997,7 +1026,7 @@ mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, unsigned int fla DBG_ERR("command out of sync"); DBG_RETURN(FAIL); } - if (!(row_packet = result->row_packet)) { + if (!(row_packet = result->unbuf->row_packet)) { DBG_RETURN(FAIL); } @@ -1018,17 +1047,18 @@ mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, unsigned int fla memset(stmt->upsert_status, 0, sizeof(*stmt->upsert_status)); if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) { + const MYSQLND_RES_METADATA * const meta = result->meta; unsigned int i, field_count = result->field_count; if (!row_packet->skip_extraction) { - result->m.unbuffered_free_last_data(result TSRMLS_CC); + result->unbuf->m.free_last_data(result->unbuf, result->conn? result->conn->stats : NULL TSRMLS_CC); 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 (PASS != result->m.row_decoder(result->unbuf->last_row_buffer, + if (PASS != result->unbuf->m.row_decoder(result->unbuf->last_row_buffer, result->unbuf->last_row_data, row_packet->field_count, row_packet->fields_metadata, @@ -1044,7 +1074,7 @@ mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, unsigned int fla zval *data = result->unbuf->last_row_data[i]; /* stmt->result_bind[i].zv has been already destructed - in result->m.unbuffered_free_last_data() + in result->unbuf->m.free_last_data() */ #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF zval_dtor(stmt->result_bind[i].zv); @@ -1052,8 +1082,8 @@ mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, unsigned int fla DBG_INF_FMT("i=%u bound_var=%p type=%u refc=%u", i, stmt->result_bind[i].zv, Z_TYPE_P(data), Z_REFCOUNT_P(stmt->result_bind[i].zv)); if (IS_NULL != (Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(data))) { - if ((Z_TYPE_P(data) == IS_STRING) && (result->meta->fields[i].max_length < (unsigned long) Z_STRLEN_P(data))) { - result->meta->fields[i].max_length = Z_STRLEN_P(data); + if ((Z_TYPE_P(data) == IS_STRING) && (meta->fields[i].max_length < (unsigned long) Z_STRLEN_P(data))) { + meta->fields[i].max_length = Z_STRLEN_P(data); } stmt->result_bind[i].zv->value = data->value; /* copied data, thus also the ownership. Thus null data */ @@ -1064,7 +1094,7 @@ mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, unsigned int fla } else { DBG_INF("skipping extraction"); /* - Data has been allocated and usually result->m.unbuffered_free_last_data() + Data has been allocated and usually result->unbuf->m.free_last_data() frees it but we can't call this function as it will cause problems with the bound variables. Thus we need to do part of what it does or Zend will report leaks. @@ -1772,7 +1802,8 @@ MYSQLND_METHOD(mysqlnd_stmt, result_metadata)(MYSQLND_STMT * const s TSRMLS_DC) if (stmt->update_max_length && stmt->result->stored_data) { /* stored result, we have to update the max_length before we clone the meta data :( */ - stmt->result->m.initialize_result_set_rest(stmt->result TSRMLS_CC); + stmt->result->stored_data->m.initialize_result_set_rest(stmt->result->stored_data, stmt->result->meta, stmt->conn->stats, + stmt->conn->options->int_and_float_native TSRMLS_CC); } /* TODO: This implementation is kind of a hack, @@ -1789,8 +1820,7 @@ MYSQLND_METHOD(mysqlnd_stmt, result_metadata)(MYSQLND_STMT * const s TSRMLS_DC) break; } result->type = MYSQLND_RES_NORMAL; - result->m.fetch_row = result->m.fetch_row_normal_unbuffered; - result->unbuf = mnd_ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED)); + result->unbuf = mysqlnd_result_unbuffered_init(stmt->field_count, TRUE, result->persistent TSRMLS_CC); if (!result->unbuf) { break; } diff --git a/ext/mysqlnd/mysqlnd_ps_codec.c b/ext/mysqlnd/mysqlnd_ps_codec.c index 1af4912d91..d96a57721c 100644 --- a/ext/mysqlnd/mysqlnd_ps_codec.c +++ b/ext/mysqlnd/mysqlnd_ps_codec.c @@ -569,6 +569,7 @@ mysqlnd_stmt_execute_prepare_param_types(MYSQLND_STMT_DATA * stmt, zval *** copi DBG_ENTER("mysqlnd_stmt_execute_prepare_param_types"); for (i = 0; i < stmt->param_count; i++) { short current_type = stmt->param_bind[i].type; + if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_NULL && (current_type == MYSQL_TYPE_LONG || current_type == MYSQL_TYPE_LONGLONG)) { zval ** copies; /* always copy the var, because we do many conversions */ @@ -668,9 +669,8 @@ mysqlnd_stmt_execute_calculate_param_values_size(MYSQLND_STMT_DATA * stmt, zval { unsigned int i; DBG_ENTER("mysqlnd_stmt_execute_calculate_param_values_size"); - for (i = 0; i < stmt->param_count; i++) { - zval ** copies = NULL; + unsigned short is_longlong = 0; unsigned int j; zval *the_var = stmt->param_bind[i].zv; @@ -680,23 +680,21 @@ mysqlnd_stmt_execute_calculate_param_values_size(MYSQLND_STMT_DATA * stmt, zval for (j = i + 1; j < stmt->param_count; j++) { if (stmt->param_bind[j].zv == the_var) { /* Double binding of the same zval, make a copy */ - if (!copies || !copies[i]) { + if (!*copies_param || !(*copies_param)[i]) { if (PASS != mysqlnd_stmt_copy_it(copies_param, the_var, stmt->param_count, i TSRMLS_CC)) { SET_OOM_ERROR(*stmt->error_info); goto end; } } - break; + break; } } - copies = *copies_param; - switch (stmt->param_bind[i].type) { case MYSQL_TYPE_DOUBLE: *data_size += 8; if (Z_TYPE_P(the_var) != IS_DOUBLE) { - if (!copies || !copies[i]) { + if (!*copies_param || !(*copies_param)[i]) { if (PASS != mysqlnd_stmt_copy_it(copies_param, the_var, stmt->param_count, i TSRMLS_CC)) { SET_OOM_ERROR(*stmt->error_info); goto end; @@ -705,24 +703,17 @@ mysqlnd_stmt_execute_calculate_param_values_size(MYSQLND_STMT_DATA * stmt, zval } break; case MYSQL_TYPE_LONGLONG: - { - zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; - if (Z_TYPE_P(tmp_data) == IS_STRING) { - goto use_string; - } - convert_to_long_ex(&tmp_data); - } - *data_size += 8; - break; + is_longlong = 4; + /* fall-through */ case MYSQL_TYPE_LONG: { - zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; + zval *tmp_data = (*copies_param && (*copies_param)[i])? (*copies_param)[i]: stmt->param_bind[i].zv; if (Z_TYPE_P(tmp_data) == IS_STRING) { goto use_string; } convert_to_long_ex(&tmp_data); } - *data_size += 4; + *data_size += 4 + is_longlong; break; case MYSQL_TYPE_LONG_BLOB: if (!(stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED)) { @@ -738,14 +729,13 @@ mysqlnd_stmt_execute_calculate_param_values_size(MYSQLND_STMT_DATA * stmt, zval use_string: *data_size += 8; /* max 8 bytes for size */ if (Z_TYPE_P(the_var) != IS_STRING) { - if (!copies || !copies[i]) { + if (!*copies_param || !(*copies_param)[i]) { if (PASS != mysqlnd_stmt_copy_it(copies_param, the_var, stmt->param_count, i TSRMLS_CC)) { SET_OOM_ERROR(*stmt->error_info); goto end; } } - copies = *copies_param; - the_var = copies[i]; + the_var = (*copies_param)[i]; } convert_to_string_ex(&the_var); *data_size += Z_STRLEN_P(the_var); @@ -761,14 +751,14 @@ end: /* {{{ mysqlnd_stmt_execute_store_param_values */ static void -mysqlnd_stmt_execute_store_param_values(MYSQLND_STMT_DATA * stmt, zval ** copies, zend_uchar ** buf, zend_uchar ** p, size_t null_byte_offset) +mysqlnd_stmt_execute_store_param_values(MYSQLND_STMT_DATA * stmt, zval ** copies, zend_uchar * buf, zend_uchar ** p, size_t null_byte_offset) { unsigned int i; for (i = 0; i < stmt->param_count; i++) { zval * data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; /* Handle long data */ if (stmt->param_bind[i].zv && Z_TYPE_P(data) == IS_NULL) { - (*buf + null_byte_offset)[i/8] |= (zend_uchar) (1 << (i & 7)); + (buf + null_byte_offset)[i/8] |= (zend_uchar) (1 << (i & 7)); } else { switch (stmt->param_bind[i].type) { case MYSQL_TYPE_DOUBLE: @@ -803,7 +793,7 @@ mysqlnd_stmt_execute_store_param_values(MYSQLND_STMT_DATA * stmt, zval ** copies case MYSQL_TYPE_VAR_STRING: send_string: { - unsigned int len = Z_STRLEN_P(data); + size_t len = Z_STRLEN_P(data); /* to is after p. The latter hasn't been moved */ *p = php_mysqlnd_net_store_length(*p, len); memcpy(*p, Z_STRVAL_P(data), len); @@ -812,7 +802,7 @@ send_string: break; default: /* Won't happen, but set to NULL */ - (*buf + null_byte_offset)[i/8] |= (zend_uchar) (1 << (i & 7)); + (buf + null_byte_offset)[i/8] |= (zend_uchar) (1 << (i & 7)); break; } } @@ -823,12 +813,13 @@ send_string: /* {{{ mysqlnd_stmt_execute_store_params */ static enum_func_status -mysqlnd_stmt_execute_store_params(MYSQLND_STMT * s, zend_uchar ** buf, zend_uchar ** p, size_t * buf_len TSRMLS_DC) +mysqlnd_stmt_execute_store_params(MYSQLND_STMT * s, zend_uchar **buf, zend_uchar **p, size_t *buf_len TSRMLS_DC) { MYSQLND_STMT_DATA * stmt = s->data; + unsigned int i = 0; zend_uchar * provided_buffer = *buf; size_t data_size = 0; - zval ** copies = NULL;/* if there are different types */ + zval **copies = NULL;/* if there are different types */ enum_func_status ret = FAIL; int resend_types_next_time = 0; size_t null_byte_offset; @@ -839,7 +830,7 @@ mysqlnd_stmt_execute_store_params(MYSQLND_STMT * s, zend_uchar ** buf, zend_ucha unsigned int null_count = (stmt->param_count + 7) / 8; if (FAIL == mysqlnd_stmt_execute_check_n_enlarge_buffer(buf, p, buf_len, provided_buffer, null_count TSRMLS_CC)) { SET_OOM_ERROR(*stmt->error_info); - goto end; + goto end; } /* put `null` bytes */ null_byte_offset = *p - *buf; @@ -859,19 +850,20 @@ mysqlnd_stmt_execute_store_params(MYSQLND_STMT * s, zend_uchar ** buf, zend_ucha goto end; } - int1store(*p, stmt->send_types_to_server); + int1store(*p, stmt->send_types_to_server); (*p)++; if (stmt->send_types_to_server) { if (FAIL == mysqlnd_stmt_execute_check_n_enlarge_buffer(buf, p, buf_len, provided_buffer, stmt->param_count * 2 TSRMLS_CC)) { SET_OOM_ERROR(*stmt->error_info); - goto end; + goto end; } mysqlnd_stmt_execute_store_types(stmt, copies, p); } + stmt->send_types_to_server = resend_types_next_time; -/* 2. Store actual data */ +/* 2. Store data */ /* 2.1 Calculate how much space we need */ if (FAIL == mysqlnd_stmt_execute_calculate_param_values_size(stmt, &copies, &data_size TSRMLS_CC)) { goto end; @@ -880,11 +872,11 @@ mysqlnd_stmt_execute_store_params(MYSQLND_STMT * s, zend_uchar ** buf, zend_ucha /* 2.2 Enlarge the buffer, if needed */ if (FAIL == mysqlnd_stmt_execute_check_n_enlarge_buffer(buf, p, buf_len, provided_buffer, data_size TSRMLS_CC)) { SET_OOM_ERROR(*stmt->error_info); - goto end; + goto end; } /* 2.3 Store the actual data */ - mysqlnd_stmt_execute_store_param_values(stmt, copies, buf, p, null_byte_offset); + mysqlnd_stmt_execute_store_param_values(stmt, copies, *buf, p, null_byte_offset); ret = PASS; end: @@ -898,7 +890,7 @@ end: /* {{{ mysqlnd_stmt_execute_generate_request */ enum_func_status -mysqlnd_stmt_execute_generate_request(MYSQLND_STMT * const s, zend_uchar ** request, size_t * request_len, zend_bool * free_buffer TSRMLS_DC) +mysqlnd_stmt_execute_generate_request(MYSQLND_STMT * const s, zend_uchar ** request, size_t *request_len, zend_bool * free_buffer TSRMLS_DC) { MYSQLND_STMT_DATA * stmt = s->data; zend_uchar *p = stmt->execute_cmd_buffer.buffer, @@ -931,51 +923,6 @@ mysqlnd_stmt_execute_generate_request(MYSQLND_STMT * const s, zend_uchar ** requ } /* }}} */ - -/* {{{ mysqlnd_stmt_execute_batch_generate_request */ -enum_func_status -mysqlnd_stmt_execute_batch_generate_request(MYSQLND_STMT * const s, zend_uchar ** request, size_t *request_len, zend_bool * free_buffer TSRMLS_DC) -{ - MYSQLND_STMT_DATA * stmt = s->data; - zend_uchar *p = stmt->execute_cmd_buffer.buffer; - enum_func_status ret; - - DBG_ENTER("mysqlnd_stmt_execute_batch_generate_request"); - - int4store(p, stmt->stmt_id); - p += 4; - - /* flags is 4 bytes, we store just 1 */ - int1store(p, (zend_uchar) stmt->flags); - p++; - - int2store(p, stmt->param_count); - p+= 2; - - int2store(p, 0); /* reserved */ - p+= 2; - - { - zend_uchar * provided_buffer = stmt->execute_cmd_buffer.buffer; - size_t provided_buffer_length = stmt->execute_cmd_buffer.length; - uint i= 0; - for (;i < 5; i++) { - ret = mysqlnd_stmt_execute_store_params(s, &provided_buffer, &p, &provided_buffer_length TSRMLS_CC); - if (ret == FAIL) { - break; - } - - *free_buffer = (provided_buffer != stmt->execute_cmd_buffer.buffer); - *request_len = (p - provided_buffer); - *request = provided_buffer; - } - } - DBG_INF_FMT("ret=%s", ret == PASS? "PASS":"FAIL"); - DBG_RETURN(ret); -} -/* }}} */ - - /* * Local variables: * tab-width: 4 diff --git a/ext/mysqlnd/mysqlnd_result.c b/ext/mysqlnd/mysqlnd_result.c index 74019b55bf..0112a1394d 100644 --- a/ext/mysqlnd/mysqlnd_result.c +++ b/ext/mysqlnd/mysqlnd_result.c @@ -32,37 +32,39 @@ #define MYSQLND_SILENT - -/* {{{ mysqlnd_res::initialize_result_set_rest */ +/* {{{ mysqlnd_result_buffered_zval::initialize_result_set_rest */ static enum_func_status -MYSQLND_METHOD(mysqlnd_res, initialize_result_set_rest)(MYSQLND_RES * const result TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_result_buffered_zval, initialize_result_set_rest)(MYSQLND_RES_BUFFERED * const result, MYSQLND_RES_METADATA * const meta, + MYSQLND_STATS * stats, zend_bool int_and_float_native TSRMLS_DC) { unsigned int i; - zval **data_cursor = result->stored_data? result->stored_data->data:NULL; - zval **data_begin = result->stored_data? result->stored_data->data:NULL; - unsigned int field_count = result->meta? result->meta->field_count : 0; - uint64_t row_count = result->stored_data? result->stored_data->row_count:0; enum_func_status ret = PASS; - DBG_ENTER("mysqlnd_res::initialize_result_set_rest"); + const unsigned int field_count = meta->field_count; + const uint64_t row_count = result->row_count; + enum_func_status rc; + + zval **data_begin = ((MYSQLND_RES_BUFFERED_ZVAL *) result)->data; + zval **data_cursor = data_begin; - if (!data_cursor || row_count == result->stored_data->initialized_rows) { + DBG_ENTER("mysqlnd_result_buffered_zval::initialize_result_set_rest"); + + if (!data_cursor || row_count == result->initialized_rows) { DBG_RETURN(ret); } while ((data_cursor - data_begin) < (int)(row_count * field_count)) { if (NULL == data_cursor[0]) { - enum_func_status rc = result->m.row_decoder( - result->stored_data->row_buffers[(data_cursor - data_begin) / field_count], + rc = result->m.row_decoder(result->row_buffers[(data_cursor - data_begin) / field_count], data_cursor, - result->meta->field_count, - result->meta->fields, - result->conn->options->int_and_float_native, - result->conn->stats TSRMLS_CC); + field_count, + meta->fields, + int_and_float_native, + stats TSRMLS_CC); if (rc != PASS) { ret = FAIL; break; } - result->stored_data->initialized_rows++; - for (i = 0; i < result->field_count; i++) { + result->initialized_rows++; + for (i = 0; i < 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. @@ -70,8 +72,8 @@ MYSQLND_METHOD(mysqlnd_res, initialize_result_set_rest)(MYSQLND_RES * const resu */ 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; + if (meta->fields[i].max_length < len) { + meta->fields[i].max_length = len; } } } @@ -83,11 +85,68 @@ MYSQLND_METHOD(mysqlnd_res, initialize_result_set_rest)(MYSQLND_RES * const resu /* }}} */ +/* {{{ mysqlnd_result_buffered_c::initialize_result_set_rest */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_result_buffered_c, initialize_result_set_rest)(MYSQLND_RES_BUFFERED * const result, MYSQLND_RES_METADATA * const meta, + MYSQLND_STATS * stats, zend_bool int_and_float_native TSRMLS_DC) +{ + unsigned int i; + enum_func_status ret = PASS; + const unsigned int field_count = meta->field_count; + const uint64_t row_count = result->row_count; + enum_func_status rc; + DBG_ENTER("mysqlnd_result_buffered_c::initialize_result_set_rest"); + + if (result->initialized_rows < row_count) { + zend_uchar * initialized = ((MYSQLND_RES_BUFFERED_C *) result)->initialized; + zval ** current_row = mnd_emalloc(field_count * sizeof(zval *)); + + if (!current_row) { + DBG_RETURN(FAIL); + } + + for (i = 0; i < result->row_count; i++) { + /* (i / 8) & the_bit_for_i*/ + if (initialized[i >> 3] & (1 << (i & 7))) { + continue; + } + + rc = result->m.row_decoder(result->row_buffers[i], current_row, field_count, meta->fields, int_and_float_native, stats TSRMLS_CC); + + if (rc != PASS) { + ret = FAIL; + break; + } + result->initialized_rows++; + initialized[i >> 3] |= (1 << (i & 7)); + for (i = 0; i < 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; + } + } + zval_ptr_dtor(¤t_row[i]); + } + } + mnd_efree(current_row); + } + DBG_RETURN(ret); +} +/* }}} */ + + /* {{{ mysqlnd_rset_zval_ptr_dtor */ static void mysqlnd_rset_zval_ptr_dtor(zval **zv, enum_mysqlnd_res_type type, zend_bool * copy_ctor_called TSRMLS_DC) { DBG_ENTER("mysqlnd_rset_zval_ptr_dtor"); + DBG_INF_FMT("type=%u", type); if (!zv || !*zv) { *copy_ctor_called = FALSE; DBG_ERR_FMT("zv was NULL"); @@ -121,42 +180,42 @@ mysqlnd_rset_zval_ptr_dtor(zval **zv, enum_mysqlnd_res_type type, zend_bool * co ZVAL_NULL(*zv); } } + DBG_INF_FMT("call the dtor on zval with refc %u", Z_REFCOUNT_PP(zv)); zval_ptr_dtor(zv); DBG_VOID_RETURN; } /* }}} */ -/* {{{ mysqlnd_res::unbuffered_free_last_data */ +/* {{{ mysqlnd_result_unbuffered::free_last_data */ static void -MYSQLND_METHOD(mysqlnd_res, unbuffered_free_last_data)(MYSQLND_RES * result TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_result_unbuffered, free_last_data)(MYSQLND_RES_UNBUFFERED * unbuf, MYSQLND_STATS * const global_stats TSRMLS_DC) { - MYSQLND_RES_UNBUFFERED *unbuf = result->unbuf; - DBG_ENTER("mysqlnd_res::unbuffered_free_last_data"); if (!unbuf) { DBG_VOID_RETURN; } + DBG_INF_FMT("field_count=%u", unbuf->field_count); if (unbuf->last_row_data) { unsigned int i, ctor_called_count = 0; zend_bool copy_ctor_called; - MYSQLND_STATS *global_stats = result->conn? result->conn->stats:NULL; - for (i = 0; i < result->field_count; i++) { - mysqlnd_rset_zval_ptr_dtor(&(unbuf->last_row_data[i]), result->type, ©_ctor_called TSRMLS_CC); + for (i = 0; i < unbuf->field_count; i++) { + mysqlnd_rset_zval_ptr_dtor(&(unbuf->last_row_data[i]), unbuf->ps ? MYSQLND_RES_PS_UNBUF : MYSQLND_RES_NORMAL, ©_ctor_called TSRMLS_CC); if (copy_ctor_called) { ++ctor_called_count; } } + DBG_INF_FMT("copy_ctor_called_count=%u", ctor_called_count); /* By using value3 macros we hold a mutex only once, there is no value2 */ MYSQLND_INC_CONN_STATISTIC_W_VALUE2(global_stats, STAT_COPY_ON_WRITE_PERFORMED, ctor_called_count, STAT_COPY_ON_WRITE_SAVED, - result->field_count - ctor_called_count); + unbuf->field_count - ctor_called_count); /* Free last row's zvals */ mnd_efree(unbuf->last_row_data); unbuf->last_row_data = NULL; @@ -173,31 +232,60 @@ MYSQLND_METHOD(mysqlnd_res, unbuffered_free_last_data)(MYSQLND_RES * result TSRM /* }}} */ -/* {{{ mysqlnd_res::free_buffered_data */ +/* {{{ mysqlnd_result_unbuffered::free_result */ static void -MYSQLND_METHOD(mysqlnd_res, free_buffered_data)(MYSQLND_RES * result TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_result_unbuffered, free_result)(MYSQLND_RES_UNBUFFERED * const result, MYSQLND_STATS * const global_stats TSRMLS_DC) { - MYSQLND_RES_BUFFERED *set = result->stored_data; - unsigned int field_count = result->field_count; - int64_t row; + DBG_ENTER("mysqlnd_result_unbuffered, free_result"); + result->m.free_last_data(result, global_stats TSRMLS_CC); - DBG_ENTER("mysqlnd_res::free_buffered_data"); - DBG_INF_FMT("Freeing "MYSQLND_LLU_SPEC" row(s)", set->row_count); + if (result->lengths) { + mnd_pefree(result->lengths, result->persistent); + result->lengths = NULL; + } + + /* must be free before because references the memory pool */ + if (result->row_packet) { + PACKET_FREE(result->row_packet); + result->row_packet = NULL; + } + + if (result->result_set_memory_pool) { + mysqlnd_mempool_destroy(result->result_set_memory_pool TSRMLS_CC); + result->result_set_memory_pool = NULL; + } + + + mnd_pefree(result, result->persistent); + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_result_buffered_zval::free_result */ +static void +MYSQLND_METHOD(mysqlnd_result_buffered_zval, free_result)(MYSQLND_RES_BUFFERED_ZVAL * const set TSRMLS_DC) +{ + zval ** data = set->data; - if (set->data) { + DBG_ENTER("mysqlnd_result_buffered_zval::free_result"); + + set->data = NULL; /* prevent double free if following loop is interrupted */ + if (data) { unsigned int copy_on_write_performed = 0; unsigned int copy_on_write_saved = 0; - + unsigned int field_count = set->field_count; + int64_t 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]; + zval **current_row = data + row * field_count; int64_t col; if (current_row != NULL) { for (col = field_count - 1; col >= 0; --col) { if (current_row[col]) { zend_bool copy_ctor_called; - mysqlnd_rset_zval_ptr_dtor(&(current_row[col]), result->type, ©_ctor_called TSRMLS_CC); + mysqlnd_rset_zval_ptr_dtor(&(current_row[col]), set->ps? MYSQLND_RES_PS_BUF : MYSQLND_RES_NORMAL, ©_ctor_called TSRMLS_CC); if (copy_ctor_called) { ++copy_on_write_performed; } else { @@ -206,23 +294,68 @@ MYSQLND_METHOD(mysqlnd_res, free_buffered_data)(MYSQLND_RES * result TSRMLS_DC) } } } - current_buffer->free_chunk(current_buffer TSRMLS_CC); } - MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_COPY_ON_WRITE_PERFORMED, copy_on_write_performed, STAT_COPY_ON_WRITE_SAVED, copy_on_write_saved); - mnd_efree(set->data); - set->data = NULL; + mnd_efree(data); + } + set->data_cursor = NULL; + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_result_buffered_c::free_result */ +static void +MYSQLND_METHOD(mysqlnd_result_buffered_c, free_result)(MYSQLND_RES_BUFFERED_C * const set TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_result_buffered_c::free_result"); + mnd_pefree(set->initialized, set->persistent); + set->initialized = NULL; + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_result_buffered::free_result */ +static void +MYSQLND_METHOD(mysqlnd_result_buffered, free_result)(MYSQLND_RES_BUFFERED * const set TSRMLS_DC) +{ + int64_t row; + + DBG_ENTER("mysqlnd_result_buffered::free_result"); + DBG_INF_FMT("Freeing "MYSQLND_LLU_SPEC" row(s)", set->row_count); + + if (set->type == MYSQLND_BUFFERED_TYPE_ZVAL) { + MYSQLND_METHOD(mysqlnd_result_buffered_zval, free_result)((MYSQLND_RES_BUFFERED_ZVAL *) set TSRMLS_CC); + } if (set->type == MYSQLND_BUFFERED_TYPE_C) { + MYSQLND_METHOD(mysqlnd_result_buffered_c, free_result)((MYSQLND_RES_BUFFERED_C *) set TSRMLS_CC); + } + + for (row = set->row_count - 1; row >= 0; row--) { + MYSQLND_MEMORY_POOL_CHUNK *current_buffer = set->row_buffers[row]; + current_buffer->free_chunk(current_buffer TSRMLS_CC); + } + + if (set->lengths) { + mnd_pefree(set->lengths, set->persistent); + set->lengths = NULL; } if (set->row_buffers) { - mnd_efree(set->row_buffers); - set->row_buffers = NULL; + mnd_pefree(set->row_buffers, 0); + set->row_buffers = NULL; } - set->data_cursor = NULL; + + if (set->result_set_memory_pool) { + mysqlnd_mempool_destroy(set->result_set_memory_pool TSRMLS_CC); + set->result_set_memory_pool = NULL; + } + + set->row_count = 0; - mnd_efree(set); + mnd_pefree(set, set->persistent); DBG_VOID_RETURN; } @@ -237,39 +370,24 @@ MYSQLND_METHOD(mysqlnd_res, free_result_buffers)(MYSQLND_RES * result TSRMLS_DC) DBG_INF_FMT("%s", result->unbuf? "unbuffered":(result->stored_data? "buffered":"unknown")); if (result->unbuf) { - result->m.unbuffered_free_last_data(result TSRMLS_CC); - mnd_efree(result->unbuf); + result->unbuf->m.free_result(result->unbuf, result->conn? result->conn->stats : NULL TSRMLS_CC); result->unbuf = NULL; } else if (result->stored_data) { - result->m.free_buffered_data(result TSRMLS_CC); + result->stored_data->m.free_result(result->stored_data TSRMLS_CC); result->stored_data = NULL; } - if (result->lengths) { - mnd_efree(result->lengths); - result->lengths = NULL; - } - - if (result->row_packet) { - PACKET_FREE(result->row_packet); - result->row_packet = NULL; - } - - if (result->result_set_memory_pool) { - mysqlnd_mempool_destroy(result->result_set_memory_pool TSRMLS_CC); - result->result_set_memory_pool = NULL; - } DBG_VOID_RETURN; } /* }}} */ -/* {{{ mysqlnd_internal_free_result_contents */ +/* {{{ mysqlnd_res::free_result_contents_internal */ static -void mysqlnd_internal_free_result_contents(MYSQLND_RES * result TSRMLS_DC) +void MYSQLND_METHOD(mysqlnd_res, free_result_contents_internal)(MYSQLND_RES * result TSRMLS_DC) { - DBG_ENTER("mysqlnd_internal_free_result_contents"); + DBG_ENTER("mysqlnd_res::free_result_contents_internal"); result->m.free_result_buffers(result TSRMLS_CC); @@ -283,11 +401,13 @@ void mysqlnd_internal_free_result_contents(MYSQLND_RES * result TSRMLS_DC) /* }}} */ -/* {{{ mysqlnd_internal_free_result */ +/* {{{ mysqlnd_res::free_result_internal */ static -void mysqlnd_internal_free_result(MYSQLND_RES * result TSRMLS_DC) +void MYSQLND_METHOD(mysqlnd_res, free_result_internal)(MYSQLND_RES * result TSRMLS_DC) { - DBG_ENTER("mysqlnd_internal_free_result"); + DBG_ENTER("mysqlnd_res::free_result_internal"); + result->m.skip_result(result TSRMLS_CC); + result->m.free_result_contents(result TSRMLS_CC); if (result->conn) { @@ -551,7 +671,7 @@ mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * s /* }}} */ -/* {{{ mysqlnd_fetch_lengths_buffered */ +/* {{{ mysqlnd_result_buffered::fetch_lengths */ /* Do lazy initialization for buffered results. As PHP strings have length inside, this function makes not much sense in the context @@ -559,78 +679,103 @@ mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * s completeness. */ static unsigned long * -mysqlnd_fetch_lengths_buffered(MYSQLND_RES * const result TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_lengths)(MYSQLND_RES_BUFFERED * const result TSRMLS_DC) { - unsigned int i; - zval **previous_row; - MYSQLND_RES_BUFFERED *set = result->stored_data; - + const MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result; /* If: - unbuffered result - first row has not been read - last_row has been read */ + DBG_ENTER("mysqlnd_result_buffered_zval::fetch_lengths"); + if (set->data_cursor == NULL || set->data_cursor == set->data || - ((set->data_cursor - set->data) > (set->row_count * result->meta->field_count) )) + ((set->data_cursor - set->data) > (result->row_count * result->field_count) )) { - return NULL;/* No rows or no more rows */ + DBG_INF("EOF"); + DBG_RETURN(NULL);/* No rows or no more rows */ } + DBG_INF("non NULL"); + DBG_RETURN(result->lengths); +} +/* }}} */ - 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_result_buffered_c::fetch_lengths */ +/* + 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_METHOD(mysqlnd_result_buffered_c, fetch_lengths)(MYSQLND_RES_BUFFERED * const result TSRMLS_DC) +{ + const MYSQLND_RES_BUFFERED_C * set = (MYSQLND_RES_BUFFERED_C *) result; + DBG_ENTER("mysqlnd_result_buffered_c::fetch_lengths"); + + if (set->current_row > set->row_count || set->current_row == 0) { + DBG_INF("EOF"); + DBG_RETURN(NULL); /* No more rows, or no fetched row */ + } + DBG_INF("non NULL"); + DBG_RETURN(result->lengths); } /* }}} */ -/* {{{ mysqlnd_fetch_lengths_unbuffered */ +/* {{{ mysqlnd_result_unbuffered::fetch_lengths */ static unsigned long * -mysqlnd_fetch_lengths_unbuffered(MYSQLND_RES * const result TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_lengths)(MYSQLND_RES_UNBUFFERED * const result TSRMLS_DC) { /* simulate output of libmysql */ - return (!result->unbuf || result->unbuf->last_row_data || result->unbuf->eof_reached)? result->lengths:NULL; + return (result->last_row_data || result->eof_reached)? result->lengths : NULL; } /* }}} */ /* {{{ mysqlnd_res::fetch_lengths */ -PHPAPI unsigned long * _mysqlnd_fetch_lengths(MYSQLND_RES * const result TSRMLS_DC) +static unsigned long * +MYSQLND_METHOD(mysqlnd_res, fetch_lengths)(MYSQLND_RES * const result TSRMLS_DC) { - return result->m.fetch_lengths? result->m.fetch_lengths(result TSRMLS_CC) : NULL; + unsigned long * ret; + DBG_ENTER("mysqlnd_res::fetch_lengths"); + ret = result->stored_data && result->stored_data->m.fetch_lengths ? + result->stored_data->m.fetch_lengths(result->stored_data TSRMLS_CC) : + (result->unbuf && result->unbuf->m.fetch_lengths ? + result->unbuf->m.fetch_lengths(result->unbuf TSRMLS_CC) : + NULL + ); + DBG_RETURN(ret); } /* }}} */ -/* {{{ mysqlnd_fetch_row_unbuffered_c */ -static MYSQLND_ROW_C -mysqlnd_fetch_row_unbuffered_c(MYSQLND_RES * result TSRMLS_DC) +/* {{{ mysqlnd_result_unbuffered::fetch_row_c */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row_c)(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything TSRMLS_DC) { enum_func_status ret; - MYSQLND_ROW_C retrow = NULL; - unsigned int i, - field_count = result->field_count; - MYSQLND_PACKET_ROW *row_packet = result->row_packet; - unsigned long *lengths = result->lengths; + MYSQLND_ROW_C *row = (MYSQLND_ROW_C *) param; + MYSQLND_PACKET_ROW *row_packet = result->unbuf->row_packet; + const MYSQLND_RES_METADATA * const meta = result->meta; - DBG_ENTER("mysqlnd_fetch_row_unbuffered_c"); + DBG_ENTER("mysqlnd_result_unbuffered::fetch_row_c"); + *fetched_anything = FALSE; if (result->unbuf->eof_reached) { /* No more rows obviously */ - DBG_RETURN(retrow); + DBG_RETURN(PASS); } 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); + SET_CLIENT_ERROR(*result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync); + DBG_RETURN(FAIL); } if (!row_packet) { /* Not fully initialized object that is being cleaned up */ - DBG_RETURN(retrow); + DBG_RETURN(FAIL); } /* Let the row packet fill our buffer and skip additional mnd_malloc + memcpy */ row_packet->skip_extraction = FALSE; @@ -640,9 +785,7 @@ mysqlnd_fetch_row_unbuffered_c(MYSQLND_RES * result TSRMLS_DC) result->m.unbuffered_free_last_data() before it. The function returns always true. */ if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) { - result->unbuf->row_count++; - - result->m.unbuffered_free_last_data(result TSRMLS_CC); + result->unbuf->m.free_last_data(result->unbuf, result->conn? result->conn->stats : NULL TSRMLS_CC); result->unbuf->last_row_data = row_packet->fields; result->unbuf->last_row_buffer = row_packet->row_buffer; @@ -652,46 +795,51 @@ mysqlnd_fetch_row_unbuffered_c(MYSQLND_RES * result TSRMLS_DC) MYSQLND_INC_CONN_STATISTIC(result->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF); if (!row_packet->skip_extraction) { - MYSQLND_FIELD *field = result->meta->fields; - struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys; - - enum_func_status rc = result->m.row_decoder(result->unbuf->last_row_buffer, - result->unbuf->last_row_data, - row_packet->field_count, - row_packet->fields_metadata, - result->conn->options->int_and_float_native, - result->conn->stats TSRMLS_CC); + unsigned int i, field_count = meta->field_count; + + enum_func_status rc = result->unbuf->m.row_decoder(result->unbuf->last_row_buffer, + result->unbuf->last_row_data, + field_count, + row_packet->fields_metadata, + result->conn->options->int_and_float_native, + result->conn->stats TSRMLS_CC); if (PASS != rc) { - DBG_RETURN(retrow); + DBG_RETURN(FAIL); } + { + *row = mnd_malloc(field_count * sizeof(char *)); + if (*row) { + MYSQLND_FIELD * field = meta->fields; + unsigned long * lengths = result->unbuf->lengths; + + for (i = 0; i < field_count; i++, field++) { + zval * data = result->unbuf->last_row_data[i]; + unsigned int len = (Z_TYPE_P(data) == IS_NULL)? 0:Z_STRLEN_P(data); + +/* BEGIN difference between normal normal fetch and _c */ + if (Z_TYPE_P(data) != IS_NULL) { + convert_to_string(data); + (*row)[i] = Z_STRVAL_P(data); + } else { + (*row)[i] = NULL; + } +/* END difference between normal normal fetch and _c */ - retrow = mnd_malloc(result->field_count * sizeof(char *)); - if (retrow) { - for (i = 0; i < field_count; i++, field++, hash_key++) { - zval *data = result->unbuf->last_row_data[i]; - unsigned 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 (lengths) { + lengths[i] = len; + } - if (field->max_length < len) { - field->max_length = len; + if (field->max_length < len) { + field->max_length = len; + } } + } else { + SET_OOM_ERROR(*result->conn->error_info); } - } else { - SET_OOM_ERROR(*result->conn->error_info); } } + result->unbuf->row_count++; + *fetched_anything = TRUE; } else if (ret == FAIL) { if (row_packet->error_info.error_no) { COPY_CLIENT_ERROR(*result->conn->error_info, row_packet->error_info); @@ -715,23 +863,25 @@ mysqlnd_fetch_row_unbuffered_c(MYSQLND_RES * result TSRMLS_DC) } else { CONN_SET_STATE(result->conn, CONN_READY); } - result->m.unbuffered_free_last_data(result TSRMLS_CC); + result->unbuf->m.free_last_data(result->unbuf, result->conn? result->conn->stats : NULL TSRMLS_CC); } - DBG_RETURN(retrow); + DBG_INF_FMT("ret=%s fetched=%u", ret == PASS? "PASS":"FAIL", *fetched_anything); + DBG_RETURN(PASS); } /* }}} */ -/* {{{ mysqlnd_fetch_row_unbuffered */ +/* {{{ mysqlnd_result_unbuffered::fetch_row */ static enum_func_status -mysqlnd_fetch_row_unbuffered(MYSQLND_RES * result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row)(MYSQLND_RES * result, void * param, const unsigned int flags, zend_bool * fetched_anything TSRMLS_DC) { enum_func_status ret; zval *row = (zval *) param; - MYSQLND_PACKET_ROW *row_packet = result->row_packet; + MYSQLND_PACKET_ROW *row_packet = result->unbuf->row_packet; + const MYSQLND_RES_METADATA * const meta = result->meta; - DBG_ENTER("mysqlnd_fetch_row_unbuffered"); + DBG_ENTER("mysqlnd_result_unbuffered::fetch_row"); *fetched_anything = FALSE; if (result->unbuf->eof_reached) { @@ -754,7 +904,7 @@ mysqlnd_fetch_row_unbuffered(MYSQLND_RES * result, void *param, unsigned int fla result->m.unbuffered_free_last_data() before it. The function returns always true. */ if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) { - result->m.unbuffered_free_last_data(result TSRMLS_CC); + result->unbuf->m.free_last_data(result->unbuf, result->conn? result->conn->stats : NULL TSRMLS_CC); result->unbuf->last_row_data = row_packet->fields; result->unbuf->last_row_buffer = row_packet->row_buffer; @@ -764,13 +914,9 @@ mysqlnd_fetch_row_unbuffered(MYSQLND_RES * result, void *param, unsigned int fla MYSQLND_INC_CONN_STATISTIC(result->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF); if (!row_packet->skip_extraction) { - HashTable *row_ht = Z_ARRVAL_P(row); - MYSQLND_FIELD *field = result->meta->fields; - struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys; - unsigned int i, field_count = result->field_count; - unsigned long *lengths = result->lengths; + unsigned int i, field_count = meta->field_count; - enum_func_status rc = result->m.row_decoder(result->unbuf->last_row_buffer, + enum_func_status rc = result->unbuf->m.row_decoder(result->unbuf->last_row_buffer, result->unbuf->last_row_data, field_count, row_packet->fields_metadata, @@ -779,46 +925,51 @@ mysqlnd_fetch_row_unbuffered(MYSQLND_RES * result, void *param, unsigned int fla if (PASS != rc) { DBG_RETURN(FAIL); } - for (i = 0; i < field_count; i++, field++, hash_key++) { - zval *data = result->unbuf->last_row_data[i]; - unsigned int len = (Z_TYPE_P(data) == IS_NULL)? 0:Z_STRLEN_P(data); + { + HashTable * row_ht = Z_ARRVAL_P(row); + MYSQLND_FIELD * field = meta->fields; + unsigned long * lengths = result->unbuf->lengths; + + for (i = 0; i < field_count; i++, field++) { + zval * data = result->unbuf->last_row_data[i]; + unsigned int len = (Z_TYPE_P(data) == IS_NULL)? 0:Z_STRLEN_P(data); + + if (flags & MYSQLND_FETCH_NUM) { + Z_ADDREF_P(data); + zend_hash_next_index_insert(row_ht, &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. + */ + Z_ADDREF_P(data); + if (meta->zend_hash_keys[i].is_numeric == FALSE) { + zend_hash_quick_update(Z_ARRVAL_P(row), + field->name, + field->name_length + 1, + meta->zend_hash_keys[i].key, + (void *) &data, sizeof(zval *), NULL); + } else { + zend_hash_index_update(Z_ARRVAL_P(row), meta->zend_hash_keys[i].key, (void *) &data, sizeof(zval *), NULL); + } + } - if (lengths) { - lengths[i] = len; - } + if (lengths) { + lengths[i] = len; + } - if (flags & MYSQLND_FETCH_NUM) { - Z_ADDREF_P(data); - zend_hash_next_index_insert(row_ht, &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. - */ - Z_ADDREF_P(data); - if (hash_key->is_numeric == FALSE) { - zend_hash_quick_update(Z_ARRVAL_P(row), - field->name, - field->name_length + 1, - hash_key->key, - (void *) &data, sizeof(zval *), NULL); - } else { - zend_hash_index_update(Z_ARRVAL_P(row), - hash_key->key, - (void *) &data, sizeof(zval *), NULL); + if (field->max_length < len) { + field->max_length = len; } } - if (field->max_length < len) { - field->max_length = len; - } } } - *fetched_anything = TRUE; result->unbuf->row_count++; + *fetched_anything = TRUE; } else if (ret == FAIL) { if (row_packet->error_info.error_no) { COPY_CLIENT_ERROR(*result->conn->error_info, row_packet->error_info); @@ -842,7 +993,7 @@ mysqlnd_fetch_row_unbuffered(MYSQLND_RES * result, void *param, unsigned int fla } else { CONN_SET_STATE(result->conn, CONN_READY); } - result->m.unbuffered_free_last_data(result TSRMLS_CC); + result->unbuf->m.free_last_data(result->unbuf, result->conn? result->conn->stats : NULL TSRMLS_CC); } DBG_INF_FMT("ret=%s fetched=%u", ret == PASS? "PASS":"FAIL", *fetched_anything); @@ -861,44 +1012,31 @@ MYSQLND_METHOD(mysqlnd_res, use_result)(MYSQLND_RES * const result, zend_bool ps if (ps == FALSE) { result->type = MYSQLND_RES_NORMAL; - result->m.fetch_row = result->m.fetch_row_normal_unbuffered; - result->m.fetch_lengths = mysqlnd_fetch_lengths_unbuffered; - result->m.row_decoder = php_mysqlnd_rowp_read_text_protocol; - result->lengths = mnd_ecalloc(result->field_count, sizeof(unsigned long)); - if (!result->lengths) { - goto oom; - } } else { result->type = MYSQLND_RES_PS_UNBUF; - result->m.fetch_row = NULL; - /* result->m.fetch_row() will be set in mysqlnd_ps.c */ - result->m.fetch_lengths = NULL; /* makes no sense */ - result->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol; - result->lengths = NULL; } - result->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC); - result->unbuf = mnd_ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED)); - if (!result->result_set_memory_pool || !result->unbuf) { + result->unbuf = mysqlnd_result_unbuffered_init(result->field_count, ps, result->persistent TSRMLS_CC); + if (!result->unbuf) { goto oom; } /* Will be freed in the mysqlnd_internal_free_result_contents() called - by the resource destructor. mysqlnd_fetch_row_unbuffered() expects + by the resource destructor. mysqlnd_result_unbuffered::fetch_row() expects this to be not NULL. */ /* FALSE = non-persistent */ - result->row_packet = result->conn->protocol->m.get_row_packet(result->conn->protocol, FALSE TSRMLS_CC); - if (!result->row_packet) { + result->unbuf->row_packet = result->conn->protocol->m.get_row_packet(result->conn->protocol, FALSE TSRMLS_CC); + if (!result->unbuf->row_packet) { goto oom; } - result->row_packet->result_set_memory_pool = result->result_set_memory_pool; - result->row_packet->field_count = result->field_count; - result->row_packet->binary_protocol = ps; - result->row_packet->fields_metadata = result->meta->fields; - result->row_packet->bit_fields_count = result->meta->bit_fields_count; - result->row_packet->bit_fields_total_len = result->meta->bit_fields_total_len; + result->unbuf->row_packet->result_set_memory_pool = result->unbuf->result_set_memory_pool; + result->unbuf->row_packet->field_count = result->field_count; + result->unbuf->row_packet->binary_protocol = ps; + result->unbuf->row_packet->fields_metadata = result->meta->fields; + result->unbuf->row_packet->bit_fields_count = result->meta->bit_fields_count; + result->unbuf->row_packet->bit_fields_total_len = result->meta->bit_fields_total_len; DBG_RETURN(result); oom: @@ -908,37 +1046,130 @@ oom: /* }}} */ -/* {{{ mysqlnd_fetch_row_buffered_c */ -static MYSQLND_ROW_C -mysqlnd_fetch_row_buffered_c(MYSQLND_RES * result TSRMLS_DC) +/* {{{ mysqlnd_result_buffered::fetch_row_c */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row_c)(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything TSRMLS_DC) { - MYSQLND_ROW_C ret = NULL; - MYSQLND_RES_BUFFERED *set = result->stored_data; + MYSQLND_ROW_C * row = (MYSQLND_ROW_C *) param; + const MYSQLND_RES_METADATA * const meta = result->meta; + unsigned int field_count = meta->field_count; + enum_func_status ret = FAIL; + DBG_ENTER("mysqlnd_result_buffered::fetch_row_c"); + + if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_ZVAL) { + MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data; + + /* If we haven't read everything */ + if (set->data_cursor && + (set->data_cursor - set->data) < (result->stored_data->row_count * field_count)) + { + zval **current_row = set->data_cursor; + unsigned int i; + + if (NULL == current_row[0]) { + uint64_t row_num = (set->data_cursor - set->data) / field_count; + enum_func_status rc = set->m.row_decoder(set->row_buffers[row_num], + current_row, + field_count, + meta->fields, + result->conn->options->int_and_float_native, + result->conn->stats TSRMLS_CC); + if (rc != PASS) { + DBG_RETURN(FAIL); + } + set->initialized_rows++; + for (i = 0; i < 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; + } + } + } + } + +/* BEGIN difference between normal normal fetch and _c */ + /* there is no conn handle in this function thus we can't set OOM in error_info */ + *row = mnd_malloc(field_count * sizeof(char *)); + if (*row) { + for (i = 0; i < field_count; i++) { + zval * data = current_row[i]; + + set->lengths[i] = (Z_TYPE_P(data) == IS_NULL)? 0:Z_STRLEN_P(data); + + if (Z_TYPE_P(data) != IS_NULL) { + convert_to_string(data); + (*row)[i] = Z_STRVAL_P(data); + } else { + (*row)[i] = NULL; + } + } + set->data_cursor += field_count; + MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF); + } else { + SET_OOM_ERROR(*result->conn->error_info); + } +/* END difference between normal normal fetch and _c */ + + *fetched_anything = *row? TRUE:FALSE; + ret = *row? PASS:FAIL; + } else { + set->data_cursor = NULL; + DBG_INF("EOF reached"); + *fetched_anything = FALSE; + ret = PASS; + } + } else if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_C) { + /* + We don't support _C with pdo because it uses the data in a different way - just references it. + We will either leak or give nirvana pointers + */ + *fetched_anything = FALSE; + DBG_RETURN(FAIL); + } + DBG_INF_FMT("ret=PASS fetched=%u", *fetched_anything); + DBG_RETURN(ret); +} +/* }}} */ - DBG_ENTER("mysqlnd_fetch_row_buffered_c"); + +/* {{{ mysqlnd_result_buffered_zval::fetch_row */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_row)(MYSQLND_RES * result, void * param, const unsigned int flags, zend_bool * fetched_anything TSRMLS_DC) +{ + zval * row = (zval *) param; + const MYSQLND_RES_METADATA * const meta = result->meta; + unsigned int field_count = meta->field_count; + enum_func_status ret = FAIL; + MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data; + + DBG_ENTER("mysqlnd_result_buffered_zval::fetch_row"); /* If we haven't read everything */ if (set->data_cursor && - (set->data_cursor - set->data) < (set->row_count * result->meta->field_count)) + (set->data_cursor - set->data) < (set->row_count * field_count)) { - zval **current_row = set->data_cursor; - MYSQLND_FIELD *field = result->meta->fields; - struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys; unsigned int i; + zval **current_row = set->data_cursor; if (NULL == current_row[0]) { - uint64_t row_num = (set->data_cursor - set->data) / result->meta->field_count; - enum_func_status rc = result->m.row_decoder(set->row_buffers[row_num], + uint64_t row_num = (set->data_cursor - set->data) / field_count; + enum_func_status rc = set->m.row_decoder(set->row_buffers[row_num], current_row, - result->meta->field_count, - result->meta->fields, + field_count, + meta->fields, result->conn->options->int_and_float_native, result->conn->stats TSRMLS_CC); if (rc != PASS) { - DBG_RETURN(ret); + DBG_RETURN(FAIL); } set->initialized_rows++; - for (i = 0; i < result->field_count; i++) { + for (i = 0; i < 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. @@ -946,71 +1177,100 @@ mysqlnd_fetch_row_buffered_c(MYSQLND_RES * result TSRMLS_DC) */ 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; + if (meta->fields[i].max_length < len) { + meta->fields[i].max_length = len; } } } } - set->data_cursor += result->meta->field_count; - MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF); + for (i = 0; i < field_count; i++) { + zval * data = current_row[i]; - ret = mnd_malloc(result->field_count * sizeof(char *)); - if (ret) { - for (i = 0; i < result->field_count; i++, field++, hash_key++) { - zval *data = current_row[i]; + set->lengths[i] = (Z_TYPE_P(data) == IS_NULL)? 0:Z_STRLEN_P(data); - if (Z_TYPE_P(data) != IS_NULL) { - convert_to_string(data); - ret[i] = Z_STRVAL_P(data); + if (flags & MYSQLND_FETCH_NUM) { + Z_ADDREF_P(data); + 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. + */ + Z_ADDREF_P(data); + if (meta->zend_hash_keys[i].is_numeric == FALSE) { + zend_hash_quick_update(Z_ARRVAL_P(row), + meta->fields[i].name, + meta->fields[i].name_length + 1, + meta->zend_hash_keys[i].key, + (void *) &data, sizeof(zval *), NULL); } else { - ret[i] = NULL; + zend_hash_index_update(Z_ARRVAL_P(row), + meta->zend_hash_keys[i].key, + (void *) &data, sizeof(zval *), NULL); } } } - /* there is no conn handle in this function thus we can't set OOM in error_info */ + set->data_cursor += field_count; + MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF); + *fetched_anything = TRUE; + ret = PASS; } else { set->data_cursor = NULL; DBG_INF("EOF reached"); + *fetched_anything = FALSE; + ret = PASS; } + DBG_INF_FMT("ret=PASS fetched=%u", *fetched_anything); DBG_RETURN(ret); } /* }}} */ -/* {{{ mysqlnd_fetch_row_buffered */ +/* {{{ mysqlnd_result_buffered_c::fetch_row */ static enum_func_status -mysqlnd_fetch_row_buffered(MYSQLND_RES * result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_result_buffered_c, fetch_row)(MYSQLND_RES * result, void * param, const unsigned int flags, zend_bool * fetched_anything TSRMLS_DC) { - unsigned int i; - zval *row = (zval *) param; - MYSQLND_RES_BUFFERED *set = result->stored_data; + zval * row = (zval *) param; + const MYSQLND_RES_METADATA * const meta = result->meta; + unsigned int field_count = meta->field_count; enum_func_status ret = FAIL; - DBG_ENTER("mysqlnd_fetch_row_buffered"); + MYSQLND_RES_BUFFERED_C * set = (MYSQLND_RES_BUFFERED_C *) result->stored_data; + + DBG_ENTER("mysqlnd_result_buffered_c::fetch_row"); /* If we haven't read everything */ - if (set->data_cursor && - (set->data_cursor - set->data) < (set->row_count * result->meta->field_count)) - { - zval **current_row = set->data_cursor; - MYSQLND_FIELD *field = result->meta->fields; - struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys; + if (set->current_row < set->row_count) { + zval **current_row; + enum_func_status rc; + unsigned int i; + + current_row = mnd_emalloc(field_count * sizeof(zval *)); + if (!current_row) { + SET_OOM_ERROR(*result->conn->error_info); + DBG_RETURN(FAIL); + } + + rc = result->stored_data->m.row_decoder(result->stored_data->row_buffers[set->current_row], + current_row, + field_count, + meta->fields, + result->conn->options->int_and_float_native, + result->conn->stats TSRMLS_CC); + if (rc != PASS) { + DBG_RETURN(FAIL); + } + if (!(set->initialized[set->current_row >> 3] & (1 << (set->current_row & 7)))) { + set->initialized[set->current_row >> 3] |= (1 << (set->current_row & 7)); /* mark initialized */ - if (NULL == current_row[0]) { - uint64_t row_num = (set->data_cursor - set->data) / result->meta->field_count; - enum_func_status rc = result->m.row_decoder(set->row_buffers[row_num], - current_row, - result->meta->field_count, - result->meta->fields, - result->conn->options->int_and_float_native, - result->conn->stats TSRMLS_CC); - if (rc != PASS) { - DBG_RETURN(FAIL); - } set->initialized_rows++; - for (i = 0; i < result->field_count; i++) { + + for (i = 0; i < 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. @@ -1018,16 +1278,18 @@ mysqlnd_fetch_row_buffered(MYSQLND_RES * result, void *param, unsigned int flags */ 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; + if (meta->fields[i].max_length < len) { + meta->fields[i].max_length = len; } } } } - for (i = 0; i < result->field_count; i++, field++, hash_key++) { - zval *data = current_row[i]; + for (i = 0; i < field_count; i++) { + zval * data = current_row[i]; + set->lengths[i] = (Z_TYPE_P(data) == IS_NULL)? 0:Z_STRLEN_P(data); + if (flags & MYSQLND_FETCH_NUM) { Z_ADDREF_P(data); zend_hash_next_index_insert(Z_ARRVAL_P(row), &data, sizeof(zval *), NULL); @@ -1041,59 +1303,85 @@ mysqlnd_fetch_row_buffered(MYSQLND_RES * result, void *param, unsigned int flags hashing of the column name, which is not needed as it can be precomputed. */ Z_ADDREF_P(data); - if (hash_key->is_numeric == FALSE) { + if (meta->zend_hash_keys[i].is_numeric == FALSE) { zend_hash_quick_update(Z_ARRVAL_P(row), - field->name, - field->name_length + 1, - hash_key->key, + meta->fields[i].name, + meta->fields[i].name_length + 1, + meta->zend_hash_keys[i].key, (void *) &data, sizeof(zval *), NULL); } else { zend_hash_index_update(Z_ARRVAL_P(row), - hash_key->key, + meta->zend_hash_keys[i].key, (void *) &data, sizeof(zval *), NULL); } } + /* + This will usually not destroy anything but decref. + However, if neither NUM nor ASSOC is set we will free memory cleanly and won't leak. + It also simplifies the handling of Z_ADDREF_P because we don't need to check if only + either NUM or ASSOC is set but not both. + */ + zval_ptr_dtor(&data); } - set->data_cursor += result->meta->field_count; - *fetched_anything = TRUE; + mnd_efree(current_row); + set->current_row++; MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF); + *fetched_anything = TRUE; ret = PASS; } else { - set->data_cursor = NULL; + if (set->current_row == set->row_count) { + set->current_row = set->row_count + 1; + } + DBG_INF_FMT("EOF reached. current_row=%llu", (unsigned long long) set->current_row); *fetched_anything = FALSE; ret = PASS; - DBG_INF("EOF reached"); } + DBG_INF_FMT("ret=PASS fetched=%u", *fetched_anything); DBG_RETURN(ret); } /* }}} */ +/* {{{ mysqlnd_res::fetch_row */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_res, fetch_row)(MYSQLND_RES * result, void * param, const unsigned int flags, zend_bool *fetched_anything TSRMLS_DC) +{ + const mysqlnd_fetch_row_func f = result->stored_data? result->stored_data->m.fetch_row:(result->unbuf? result->unbuf->m.fetch_row:NULL); + if (f) { + return f(result, param, flags, fetched_anything TSRMLS_CC); + } + *fetched_anything = FALSE; + return PASS; +} +/* }}} */ + + #define STORE_RESULT_PREALLOCATED_SET_IF_NOT_EMPTY 2 /* {{{ mysqlnd_res::store_result_fetch_data */ enum_func_status MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND_CONN_DATA * const conn, MYSQLND_RES * result, - MYSQLND_RES_METADATA *meta, + MYSQLND_RES_METADATA * meta, + MYSQLND_MEMORY_POOL_CHUNK ***row_buffers, zend_bool binary_protocol TSRMLS_DC) { enum_func_status ret; - MYSQLND_PACKET_ROW *row_packet = NULL; + MYSQLND_PACKET_ROW * row_packet = NULL; unsigned int next_extend = STORE_RESULT_PREALLOCATED_SET_IF_NOT_EMPTY, free_rows = 1; MYSQLND_RES_BUFFERED *set; DBG_ENTER("mysqlnd_res::store_result_fetch_data"); - result->stored_data = set = mnd_ecalloc(1, sizeof(MYSQLND_RES_BUFFERED)); - if (!set) { - SET_OOM_ERROR(*conn->error_info); + set = result->stored_data; + + if (!set || !row_buffers) { ret = FAIL; goto end; } if (free_rows) { - set->row_buffers = mnd_emalloc((size_t)(free_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *))); - if (!set->row_buffers) { + *row_buffers = mnd_pemalloc((size_t)(free_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *)), 0); + if (!*row_buffers) { SET_OOM_ERROR(*conn->error_info); ret = FAIL; goto end; @@ -1108,7 +1396,7 @@ MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND_CONN_DATA * const c ret = FAIL; goto end; } - row_packet->result_set_memory_pool = result->result_set_memory_pool; + row_packet->result_set_memory_pool = result->stored_data->result_set_memory_pool; row_packet->field_count = meta->field_count; row_packet->binary_protocol = binary_protocol; row_packet->fields_metadata = meta->fields; @@ -1129,16 +1417,16 @@ MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND_CONN_DATA * const c ret = FAIL; goto end; } - new_row_buffers = mnd_erealloc(set->row_buffers, (size_t)(total_allocated_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *))); + new_row_buffers = mnd_perealloc(*row_buffers, (size_t)(total_allocated_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *)), 0); if (!new_row_buffers) { SET_OOM_ERROR(*conn->error_info); ret = FAIL; goto end; } - set->row_buffers = new_row_buffers; + *row_buffers = new_row_buffers; } free_rows--; - set->row_buffers[set->row_count] = row_packet->row_buffer; + (*row_buffers)[set->row_count] = row_packet->row_buffer; set->row_count++; @@ -1154,23 +1442,6 @@ MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND_CONN_DATA * const c */ } /* Overflow ? */ - if (set->row_count) { - /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */ - if (set->row_count * meta->field_count * sizeof(zval *) > SIZE_MAX) { - SET_OOM_ERROR(*conn->error_info); - ret = FAIL; - goto end; - } - /* if pecalloc is used valgrind barks gcc version 4.3.1 20080507 (prerelease) [gcc-4_3-branch revision 135036] (SUSE Linux) */ - set->data = mnd_emalloc((size_t)(set->row_count * meta->field_count * sizeof(zval *))); - if (!set->data) { - SET_OOM_ERROR(*conn->error_info); - ret = FAIL; - goto end; - } - memset(set->data, 0, (size_t)(set->row_count * meta->field_count * sizeof(zval *))); - } - MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats, binary_protocol? STAT_ROWS_BUFFERED_FROM_CLIENT_PS: STAT_ROWS_BUFFERED_FROM_CLIENT_NORMAL, @@ -1190,7 +1461,7 @@ MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND_CONN_DATA * const c ret = FAIL; goto end; } - set->row_buffers = mnd_erealloc(set->row_buffers, (size_t) (set->row_count * sizeof(MYSQLND_MEMORY_POOL_CHUNK *))); + *row_buffers = mnd_perealloc(*row_buffers, (size_t) (set->row_count * sizeof(MYSQLND_MEMORY_POOL_CHUNK *)), 0); } if (conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS) { @@ -1202,9 +1473,6 @@ MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND_CONN_DATA * const c if (ret == FAIL) { COPY_CLIENT_ERROR(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; } @@ -1212,7 +1480,7 @@ MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND_CONN_DATA * const c ret == PASS? "PASS":"FAIL", (uint) set->row_count, conn->upsert_status->warning_count, conn->upsert_status->server_status); end: PACKET_FREE(row_packet); - + DBG_INF_FMT("rows=%llu", (unsigned long long)result->stored_data->row_count); DBG_RETURN(ret); } /* }}} */ @@ -1222,31 +1490,37 @@ end: static MYSQLND_RES * MYSQLND_METHOD(mysqlnd_res, store_result)(MYSQLND_RES * result, MYSQLND_CONN_DATA * const conn, - zend_bool ps_protocol TSRMLS_DC) + const unsigned int flags TSRMLS_DC) { enum_func_status ret; + MYSQLND_MEMORY_POOL_CHUNK ***row_buffers = NULL; DBG_ENTER("mysqlnd_res::store_result"); /* We need the conn because we are doing lazy zval initialization in buffered_fetch_row */ - result->conn = conn->m->get_reference(conn TSRMLS_CC); - result->type = MYSQLND_RES_NORMAL; - result->m.fetch_row = result->m.fetch_row_normal_buffered; - result->m.fetch_lengths = mysqlnd_fetch_lengths_buffered; - result->m.row_decoder = ps_protocol? php_mysqlnd_rowp_read_binary_protocol: - php_mysqlnd_rowp_read_text_protocol; + /* In case of error the reference will be released in free_result_internal() called indirectly by our caller */ + result->conn = conn->m->get_reference(conn TSRMLS_CC); + result->type = MYSQLND_RES_NORMAL; - result->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC); - result->lengths = mnd_ecalloc(result->field_count, sizeof(unsigned long)); + CONN_SET_STATE(conn, CONN_FETCHING_DATA); - if (!result->result_set_memory_pool || !result->lengths) { - SET_OOM_ERROR(*conn->error_info); - DBG_RETURN(NULL); + if (flags & MYSQLND_STORE_NO_COPY) { + result->stored_data = (MYSQLND_RES_BUFFERED *) mysqlnd_result_buffered_zval_init(result->field_count, flags & MYSQLND_STORE_PS, result->persistent TSRMLS_CC); + if (!result->stored_data) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(NULL); + } + row_buffers = &result->stored_data->row_buffers; + } else if (flags & MYSQLND_STORE_COPY) { + result->stored_data = (MYSQLND_RES_BUFFERED *) mysqlnd_result_buffered_c_init(result->field_count, flags & MYSQLND_STORE_PS, result->persistent TSRMLS_CC); + if (!result->stored_data) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(NULL); + } + row_buffers = &result->stored_data->row_buffers; } + ret = result->m.store_result_fetch_data(conn, result, result->meta, row_buffers, flags & MYSQLND_STORE_PS TSRMLS_CC); - CONN_SET_STATE(conn, CONN_FETCHING_DATA); - - ret = result->m.store_result_fetch_data(conn, result, result->meta, ps_protocol TSRMLS_CC); if (FAIL == ret) { if (result->stored_data) { COPY_CLIENT_ERROR(*conn->error_info, result->stored_data->error_info); @@ -1254,7 +1528,34 @@ MYSQLND_METHOD(mysqlnd_res, store_result)(MYSQLND_RES * result, SET_OOM_ERROR(*conn->error_info); } DBG_RETURN(NULL); + } else { + /* Overflow ? */ + if (flags & MYSQLND_STORE_NO_COPY) { + MYSQLND_RES_METADATA * meta = result->meta; + MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data; + if (set->row_count) { + /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */ + if (set->row_count * meta->field_count * sizeof(zval *) > SIZE_MAX) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(NULL); + } + /* if pecalloc is used valgrind barks gcc version 4.3.1 20080507 (prerelease) [gcc-4_3-branch revision 135036] (SUSE Linux) */ + set->data = mnd_emalloc((size_t)(set->row_count * meta->field_count * sizeof(zval *))); + if (!set->data) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(NULL); + } + memset(set->data, 0, (size_t)(set->row_count * meta->field_count * sizeof(zval *))); + } + /* Position at the first row */ + set->data_cursor = set->data; + } else if (flags & MYSQLND_STORE_COPY) { + MYSQLND_RES_BUFFERED_C * set = (MYSQLND_RES_BUFFERED_C *) result->stored_data; + set->current_row = 0; + set->initialized = mnd_pecalloc((set->row_count / 8) + 1, sizeof(zend_uchar), set->persistent); /* +1 for safety */ + } } + /* libmysql's documentation says it should be so for SELECT statements */ conn->upsert_status->affected_rows = result->stored_data->row_count; @@ -1275,9 +1576,7 @@ MYSQLND_METHOD(mysqlnd_res, skip_result)(MYSQLND_RES * const result TSRMLS_DC) A PS could be prepared - there is metadata and thus a stmt->result but the fetch_row function isn't actually set (NULL), thus we have to skip these. */ - if (!result->stored_data && result->unbuf && - !result->unbuf->eof_reached && result->m.fetch_row) - { + if (result->unbuf && !result->unbuf->eof_reached) { DBG_INF("skipping result"); /* We have to fetch all data to clean the line */ MYSQLND_INC_CONN_STATISTIC(result->conn->stats, @@ -1299,7 +1598,6 @@ MYSQLND_METHOD(mysqlnd_res, free_result)(MYSQLND_RES * result, zend_bool implici { DBG_ENTER("mysqlnd_res::free_result"); - result->m.skip_result(result TSRMLS_CC); MYSQLND_INC_CONN_STATISTIC(result->conn? result->conn->stats : NULL, implicit == TRUE? STAT_FREE_RESULT_IMPLICIT: STAT_FREE_RESULT_EXPLICIT); @@ -1312,33 +1610,78 @@ MYSQLND_METHOD(mysqlnd_res, free_result)(MYSQLND_RES * result, zend_bool implici /* {{{ mysqlnd_res::data_seek */ static enum_func_status -MYSQLND_METHOD(mysqlnd_res, data_seek)(MYSQLND_RES * result, uint64_t row TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_res, data_seek)(MYSQLND_RES * const result, const uint64_t row TSRMLS_DC) { DBG_ENTER("mysqlnd_res::data_seek"); DBG_INF_FMT("row=%lu", row); - if (!result->stored_data) { - return FAIL; - } + DBG_RETURN(result->stored_data? result->stored_data->m.data_seek(result->stored_data, row TSRMLS_CC) : FAIL); +} +/* }}} */ + + +/* {{{ mysqlnd_result_buffered_zval::data_seek */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_result_buffered_zval, data_seek)(MYSQLND_RES_BUFFERED * const result, const uint64_t row TSRMLS_DC) +{ + MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result; + DBG_ENTER("mysqlnd_result_buffered_zval::data_seek"); /* libmysql just moves to the end, it does traversing of a linked list */ - if (row >= result->stored_data->row_count) { - result->stored_data->data_cursor = NULL; + if (row >= set->row_count) { + set->data_cursor = NULL; } else { - result->stored_data->data_cursor = result->stored_data->data + row * result->meta->field_count; + set->data_cursor = set->data + row * result->field_count; } + DBG_RETURN(PASS); +} +/* }}} */ + +/* {{{ mysqlnd_result_buffered_c::data_seek */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_result_buffered_c, data_seek)(MYSQLND_RES_BUFFERED * const result, const uint64_t row TSRMLS_DC) +{ + MYSQLND_RES_BUFFERED_C * set = (MYSQLND_RES_BUFFERED_C *) result; + DBG_ENTER("mysqlnd_result_buffered_c::data_seek"); + + /* libmysql just moves to the end, it does traversing of a linked list */ + if (row >= set->row_count) { + set->current_row = set->row_count; + } else { + set->current_row = row; + } DBG_RETURN(PASS); } /* }}} */ +/* {{{ mysqlnd_result_unbuffered::num_rows */ +static uint64_t +MYSQLND_METHOD(mysqlnd_result_unbuffered, num_rows)(const MYSQLND_RES_UNBUFFERED * const result TSRMLS_DC) +{ + /* Be compatible with libmysql. We count row_count, but will return 0 */ + return result->eof_reached? result->row_count:0; +} +/* }}} */ + + +/* {{{ mysqlnd_result_buffered::num_rows */ +static uint64_t +MYSQLND_METHOD(mysqlnd_result_buffered, num_rows)(const MYSQLND_RES_BUFFERED * const result TSRMLS_DC) +{ + return result->row_count; +} +/* }}} */ + + /* {{{ mysqlnd_res::num_rows */ static uint64_t MYSQLND_METHOD(mysqlnd_res, num_rows)(const MYSQLND_RES * const result TSRMLS_DC) { - /* Be compatible with libmysql. We count row_count, but will return 0 */ - return result->stored_data? result->stored_data->row_count:(result->unbuf && result->unbuf->eof_reached? result->unbuf->row_count:0); + return result->stored_data? + result->stored_data->m.num_rows(result->stored_data TSRMLS_CC) : + (result->unbuf? result->unbuf->m.num_rows(result->unbuf TSRMLS_CC) : 0); } /* }}} */ @@ -1372,7 +1715,9 @@ MYSQLND_METHOD(mysqlnd_res, fetch_field)(MYSQLND_RES * const result TSRMLS_DC) if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) { DBG_INF_FMT("We have decode the whole result set to be able to satisfy this meta request"); /* we have to initialize the rest to get the updated max length */ - if (PASS != result->m.initialize_result_set_rest(result TSRMLS_CC)) { + if (PASS != result->stored_data->m.initialize_result_set_rest(result->stored_data, result->meta, result->conn->stats, + result->conn->options->int_and_float_native TSRMLS_CC)) + { break; } } @@ -1386,7 +1731,7 @@ MYSQLND_METHOD(mysqlnd_res, fetch_field)(MYSQLND_RES * const result TSRMLS_DC) /* {{{ mysqlnd_res::fetch_field_direct */ static const MYSQLND_FIELD * -MYSQLND_METHOD(mysqlnd_res, fetch_field_direct)(MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_res, fetch_field_direct)(MYSQLND_RES * const result, const MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC) { DBG_ENTER("mysqlnd_res::fetch_field_direct"); do { @@ -1404,7 +1749,9 @@ MYSQLND_METHOD(mysqlnd_res, fetch_field_direct)(MYSQLND_RES * const result, MYSQ if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) { DBG_INF_FMT("We have decode the whole result set to be able to satisfy this meta request"); /* we have to initialized the rest to get the updated max length */ - if (PASS != result->m.initialize_result_set_rest(result TSRMLS_CC)) { + if (PASS != result->stored_data->m.initialize_result_set_rest(result->stored_data, result->meta, result->conn->stats, + result->conn->options->int_and_float_native TSRMLS_CC)) + { break; } } @@ -1426,7 +1773,9 @@ MYSQLND_METHOD(mysqlnd_res, fetch_fields)(MYSQLND_RES * const result TSRMLS_DC) 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 */ - if (PASS != result->m.initialize_result_set_rest(result TSRMLS_CC)) { + if (PASS != result->stored_data->m.initialize_result_set_rest(result->stored_data, result->meta, result->conn->stats, + result->conn->options->int_and_float_native TSRMLS_CC)) + { break; } } @@ -1438,17 +1787,11 @@ MYSQLND_METHOD(mysqlnd_res, fetch_fields)(MYSQLND_RES * const result TSRMLS_DC) /* }}} */ - /* {{{ mysqlnd_res::field_seek */ static MYSQLND_FIELD_OFFSET -MYSQLND_METHOD(mysqlnd_res, field_seek)(MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET field_offset TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_res, field_seek)(MYSQLND_RES * const result, const MYSQLND_FIELD_OFFSET field_offset TSRMLS_DC) { - MYSQLND_FIELD_OFFSET return_value = 0; - if (result->meta) { - return_value = result->meta->current_field; - result->meta->current_field = field_offset; - } - return return_value; + return result->meta? result->meta->m->field_seek(result->meta, field_offset TSRMLS_CC) : 0; } /* }}} */ @@ -1464,7 +1807,7 @@ MYSQLND_METHOD(mysqlnd_res, field_tell)(const MYSQLND_RES * const result TSRMLS_ /* {{{ mysqlnd_res::fetch_into */ static void -MYSQLND_METHOD(mysqlnd_res, fetch_into)(MYSQLND_RES * result, unsigned int flags, +MYSQLND_METHOD(mysqlnd_res, fetch_into)(MYSQLND_RES * result, const unsigned int flags, zval *return_value, enum_mysqlnd_extension extension TSRMLS_DC ZEND_FILE_LINE_DC) { @@ -1472,10 +1815,6 @@ MYSQLND_METHOD(mysqlnd_res, fetch_into)(MYSQLND_RES * result, unsigned int flags DBG_ENTER("mysqlnd_res::fetch_into"); - if (!result->m.fetch_row) { - RETVAL_NULL(); - DBG_VOID_RETURN; - } /* Hint Zend how many elements we will have in the hash. Thus it won't extend and rehash the hash constantly. @@ -1510,17 +1849,17 @@ MYSQLND_METHOD(mysqlnd_res, fetch_into)(MYSQLND_RES * result, unsigned int flags static MYSQLND_ROW_C MYSQLND_METHOD(mysqlnd_res, fetch_row_c)(MYSQLND_RES * result TSRMLS_DC) { + zend_bool fetched_anything; MYSQLND_ROW_C ret = NULL; DBG_ENTER("mysqlnd_res::fetch_row_c"); - if (result->m.fetch_row) { - if (result->m.fetch_row == result->m.fetch_row_normal_buffered) { - DBG_RETURN(mysqlnd_fetch_row_buffered_c(result TSRMLS_CC)); - } else if (result->m.fetch_row == result->m.fetch_row_normal_unbuffered) { - DBG_RETURN(mysqlnd_fetch_row_unbuffered_c(result TSRMLS_CC)); - } else { - php_error_docref(NULL TSRMLS_CC, E_ERROR, "result->m.fetch_row has invalid value. Report to the developers"); - } + if (result->stored_data && result->stored_data->m.fetch_row == MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_row)) { + MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row_c)(result, (void *) &ret, 0, &fetched_anything TSRMLS_CC); + } else if (result->unbuf && result->unbuf->m.fetch_row == MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row)) { + MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row_c)(result, (void *) &ret, 0, &fetched_anything TSRMLS_CC); + } else { + ret = NULL; + php_error_docref(NULL TSRMLS_CC, E_ERROR, "result->m.fetch_row has invalid value. Report to the developers"); } DBG_RETURN(ret); } @@ -1529,7 +1868,7 @@ MYSQLND_METHOD(mysqlnd_res, fetch_row_c)(MYSQLND_RES * result TSRMLS_DC) /* {{{ mysqlnd_res::fetch_all */ static void -MYSQLND_METHOD(mysqlnd_res, fetch_all)(MYSQLND_RES * result, unsigned int flags, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC) +MYSQLND_METHOD(mysqlnd_res, fetch_all)(MYSQLND_RES * result, const unsigned int flags, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC) { zval *row; ulong i = 0; @@ -1574,11 +1913,6 @@ MYSQLND_METHOD(mysqlnd_res, fetch_field_data)(MYSQLND_RES * result, unsigned int DBG_ENTER("mysqlnd_res::fetch_field_data"); DBG_INF_FMT("offset=%u", offset); - - if (!result->m.fetch_row) { - RETVAL_NULL(); - DBG_VOID_RETURN; - } /* Hint Zend how many elements we will have in the hash. Thus it won't extend and rehash the hash constantly. @@ -1609,9 +1943,7 @@ MYSQLND_METHOD(mysqlnd_res, fetch_field_data)(MYSQLND_RES * result, unsigned int MYSQLND_CLASS_METHODS_START(mysqlnd_res) - NULL, /* fetch_row */ - mysqlnd_fetch_row_buffered, - mysqlnd_fetch_row_unbuffered, + MYSQLND_METHOD(mysqlnd_res, fetch_row), MYSQLND_METHOD(mysqlnd_res, use_result), MYSQLND_METHOD(mysqlnd_res, store_result), MYSQLND_METHOD(mysqlnd_res, fetch_into), @@ -1628,19 +1960,34 @@ MYSQLND_CLASS_METHODS_START(mysqlnd_res) MYSQLND_METHOD(mysqlnd_res, fetch_field_direct), MYSQLND_METHOD(mysqlnd_res, fetch_fields), MYSQLND_METHOD(mysqlnd_res, read_result_metadata), - NULL, /* fetch_lengths */ + MYSQLND_METHOD(mysqlnd_res, fetch_lengths), MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data), - MYSQLND_METHOD(mysqlnd_res, initialize_result_set_rest), MYSQLND_METHOD(mysqlnd_res, free_result_buffers), MYSQLND_METHOD(mysqlnd_res, free_result), + MYSQLND_METHOD(mysqlnd_res, free_result_internal), + MYSQLND_METHOD(mysqlnd_res, free_result_contents_internal), + mysqlnd_result_meta_init +MYSQLND_CLASS_METHODS_END; - mysqlnd_internal_free_result, /* free_result_internal */ - mysqlnd_internal_free_result_contents, /* free_result_contents */ - MYSQLND_METHOD(mysqlnd_res, free_buffered_data), - MYSQLND_METHOD(mysqlnd_res, unbuffered_free_last_data), - NULL /* row_decoder */, - mysqlnd_result_meta_init +MYSQLND_CLASS_METHODS_START(mysqlnd_result_unbuffered) + MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row), + NULL, /* row_decoder */ + MYSQLND_METHOD(mysqlnd_result_unbuffered, num_rows), + MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_lengths), + MYSQLND_METHOD(mysqlnd_result_unbuffered, free_last_data), + MYSQLND_METHOD(mysqlnd_result_unbuffered, free_result) +MYSQLND_CLASS_METHODS_END; + + +MYSQLND_CLASS_METHODS_START(mysqlnd_result_buffered) + NULL, /* fetch_row */ + NULL, /* row_decoder */ + MYSQLND_METHOD(mysqlnd_result_buffered, num_rows), + NULL, /* fetch_lengths */ + NULL, /* data_seek */ + NULL, /* initialize_result_set_rest */ + MYSQLND_METHOD(mysqlnd_result_buffered, free_result) MYSQLND_CLASS_METHODS_END; @@ -1649,7 +1996,7 @@ PHPAPI MYSQLND_RES * mysqlnd_result_init(unsigned int field_count, zend_bool persistent TSRMLS_DC) { size_t alloc_size = sizeof(MYSQLND_RES) + mysqlnd_plugin_count() * sizeof(void *); - MYSQLND_RES *ret = mnd_pecalloc(1, alloc_size, persistent); + MYSQLND_RES * ret = mnd_pecalloc(1, alloc_size, persistent); DBG_ENTER("mysqlnd_result_init"); @@ -1666,6 +2013,134 @@ mysqlnd_result_init(unsigned int field_count, zend_bool persistent TSRMLS_DC) /* }}} */ +/* {{{ mysqlnd_result_unbuffered_init */ +PHPAPI MYSQLND_RES_UNBUFFERED * +mysqlnd_result_unbuffered_init(unsigned int field_count, zend_bool ps, zend_bool persistent TSRMLS_DC) +{ + size_t alloc_size = sizeof(MYSQLND_RES_UNBUFFERED) + mysqlnd_plugin_count() * sizeof(void *); + MYSQLND_RES_UNBUFFERED * ret = mnd_pecalloc(1, alloc_size, persistent); + + DBG_ENTER("mysqlnd_result_unbuffered_init"); + + if (!ret) { + DBG_RETURN(NULL); + } + + if (!(ret->lengths = mnd_pecalloc(field_count, sizeof(unsigned long), persistent))) { + mnd_pefree(ret, persistent); + DBG_RETURN(NULL); + } + if (!(ret->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC))) { + mnd_efree(ret->lengths); + mnd_pefree(ret, persistent); + DBG_RETURN(NULL); + } + + ret->persistent = persistent; + ret->field_count= field_count; + ret->ps = ps; + + ret->m = *mysqlnd_result_unbuffered_get_methods(); + + if (ps) { + ret->m.fetch_lengths = NULL; /* makes no sense */ + ret->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol; + } else { + ret->m.row_decoder = php_mysqlnd_rowp_read_text_protocol_zval; + } + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_result_buffered_zval_init */ +PHPAPI MYSQLND_RES_BUFFERED_ZVAL * +mysqlnd_result_buffered_zval_init(unsigned int field_count, zend_bool ps, zend_bool persistent TSRMLS_DC) +{ + size_t alloc_size = sizeof(MYSQLND_RES_BUFFERED_ZVAL) + mysqlnd_plugin_count() * sizeof(void *); + MYSQLND_RES_BUFFERED_ZVAL * ret = mnd_pecalloc(1, alloc_size, persistent); + + DBG_ENTER("mysqlnd_result_buffered_zval_init"); + + if (!ret) { + DBG_RETURN(NULL); + } + if (!(ret->lengths = mnd_pecalloc(field_count, sizeof(unsigned long), persistent))) { + mnd_pefree(ret, persistent); + DBG_RETURN(NULL); + } + if (!(ret->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC))) { + mnd_efree(ret->lengths); + mnd_pefree(ret, persistent); + DBG_RETURN(NULL); + } + + ret->persistent = persistent; + ret->field_count= field_count; + ret->ps = ps; + ret->m = *mysqlnd_result_buffered_get_methods(); + ret->type = MYSQLND_BUFFERED_TYPE_ZVAL; + + if (ps) { + ret->m.fetch_lengths = NULL; /* makes no sense */ + ret->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol; + } else { + ret->m.row_decoder = php_mysqlnd_rowp_read_text_protocol_zval; + } + ret->m.fetch_row = MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_row); + ret->m.fetch_lengths = MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_lengths); + ret->m.data_seek = MYSQLND_METHOD(mysqlnd_result_buffered_zval, data_seek); + ret->m.initialize_result_set_rest = MYSQLND_METHOD(mysqlnd_result_buffered_zval, initialize_result_set_rest); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_result_buffered_c_init */ +PHPAPI MYSQLND_RES_BUFFERED_C * +mysqlnd_result_buffered_c_init(unsigned int field_count, zend_bool ps, zend_bool persistent TSRMLS_DC) +{ + size_t alloc_size = sizeof(MYSQLND_RES_BUFFERED_C) + mysqlnd_plugin_count() * sizeof(void *); + MYSQLND_RES_BUFFERED_C * ret = mnd_pecalloc(1, alloc_size, persistent); + + DBG_ENTER("mysqlnd_result_buffered_c_init"); + + if (!ret) { + DBG_RETURN(NULL); + } + if (!(ret->lengths = mnd_pecalloc(field_count, sizeof(unsigned long), persistent))) { + mnd_pefree(ret, persistent); + DBG_RETURN(NULL); + } + if (!(ret->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC))) { + mnd_efree(ret->lengths); + mnd_pefree(ret, persistent); + DBG_RETURN(NULL); + } + + ret->persistent = persistent; + ret->field_count= field_count; + ret->ps = ps; + ret->m = *mysqlnd_result_buffered_get_methods(); + ret->type = MYSQLND_BUFFERED_TYPE_C; + + if (ps) { + ret->m.fetch_lengths = NULL; /* makes no sense */ + ret->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol; + } else { + ret->m.row_decoder = php_mysqlnd_rowp_read_text_protocol_c; + } + ret->m.fetch_row = MYSQLND_METHOD(mysqlnd_result_buffered_c, fetch_row); + ret->m.fetch_lengths = MYSQLND_METHOD(mysqlnd_result_buffered_c, fetch_lengths); + ret->m.data_seek = MYSQLND_METHOD(mysqlnd_result_buffered_c, data_seek); + ret->m.initialize_result_set_rest = MYSQLND_METHOD(mysqlnd_result_buffered_c, initialize_result_set_rest); + + DBG_RETURN(ret); +} +/* }}} */ + + /* * Local variables: * tab-width: 4 diff --git a/ext/mysqlnd/mysqlnd_result.h b/ext/mysqlnd/mysqlnd_result.h index 54f3b03d7d..f7e22a1d87 100644 --- a/ext/mysqlnd/mysqlnd_result.h +++ b/ext/mysqlnd/mysqlnd_result.h @@ -24,6 +24,9 @@ #define MYSQLND_RESULT_H PHPAPI MYSQLND_RES * mysqlnd_result_init(unsigned int field_count, zend_bool persistent TSRMLS_DC); +PHPAPI MYSQLND_RES_UNBUFFERED * mysqlnd_result_unbuffered_init(unsigned int field_count, zend_bool ps, zend_bool persistent TSRMLS_DC); +PHPAPI MYSQLND_RES_BUFFERED_ZVAL * mysqlnd_result_buffered_zval_init(unsigned int field_count, zend_bool ps, zend_bool persistent TSRMLS_DC); +PHPAPI MYSQLND_RES_BUFFERED_C * mysqlnd_result_buffered_c_init(unsigned int field_count, zend_bool ps, zend_bool persistent TSRMLS_DC); enum_func_status mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * stmt TSRMLS_DC); diff --git a/ext/mysqlnd/mysqlnd_result_meta.c b/ext/mysqlnd/mysqlnd_result_meta.c index 6523a431ca..85dc3d00c4 100644 --- a/ext/mysqlnd/mysqlnd_result_meta.c +++ b/ext/mysqlnd/mysqlnd_result_meta.c @@ -341,7 +341,7 @@ MYSQLND_METHOD(mysqlnd_res_meta, fetch_field)(MYSQLND_RES_METADATA * const meta /* {{{ mysqlnd_res_meta::fetch_field_direct */ static const MYSQLND_FIELD * -MYSQLND_METHOD(mysqlnd_res_meta, fetch_field_direct)(const MYSQLND_RES_METADATA * const meta, MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_res_meta, fetch_field_direct)(const MYSQLND_RES_METADATA * const meta, const MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC) { DBG_ENTER("mysqlnd_res_meta::fetch_field_direct"); DBG_INF_FMT("fieldnr=%u", fieldnr); @@ -371,6 +371,17 @@ MYSQLND_METHOD(mysqlnd_res_meta, field_tell)(const MYSQLND_RES_METADATA * const } /* }}} */ +/* {{{ mysqlnd_res_meta::field_seek */ +static MYSQLND_FIELD_OFFSET +MYSQLND_METHOD(mysqlnd_res_meta, field_seek)(MYSQLND_RES_METADATA * const meta, const MYSQLND_FIELD_OFFSET field_offset TSRMLS_DC) +{ + MYSQLND_FIELD_OFFSET return_value = 0; + DBG_ENTER("mysqlnd_res_meta::fetch_fields"); + return_value = meta->current_field; + meta->current_field = field_offset; + DBG_RETURN(return_value); +} +/* }}} */ static MYSQLND_CLASS_METHODS_START(mysqlnd_res_meta) @@ -378,6 +389,7 @@ MYSQLND_CLASS_METHODS_START(mysqlnd_res_meta) MYSQLND_METHOD(mysqlnd_res_meta, fetch_field_direct), MYSQLND_METHOD(mysqlnd_res_meta, fetch_fields), MYSQLND_METHOD(mysqlnd_res_meta, field_tell), + MYSQLND_METHOD(mysqlnd_res_meta, field_seek), MYSQLND_METHOD(mysqlnd_res_meta, read_metadata), MYSQLND_METHOD(mysqlnd_res_meta, clone_metadata), MYSQLND_METHOD(mysqlnd_res_meta, free), diff --git a/ext/mysqlnd/mysqlnd_reverse_api.c b/ext/mysqlnd/mysqlnd_reverse_api.c index 18a2634f25..d8b1a41511 100644 --- a/ext/mysqlnd/mysqlnd_reverse_api.c +++ b/ext/mysqlnd/mysqlnd_reverse_api.c @@ -60,15 +60,15 @@ mysqlnd_reverse_api_get_api_list(TSRMLS_D) PHPAPI void mysqlnd_reverse_api_register_api(MYSQLND_REVERSE_API * apiext TSRMLS_DC) { - zend_hash_add(&mysqlnd_api_ext_ht, apiext->module->name, strlen(apiext->module->name) + 1, apiext, - sizeof(MYSQLND_REVERSE_API), NULL); + zend_hash_add(&mysqlnd_api_ext_ht, apiext->module->name, strlen(apiext->module->name) + 1, &apiext, + sizeof(MYSQLND_REVERSE_API *), NULL); } /* }}} */ /* {{{ zval_to_mysqlnd */ PHPAPI MYSQLND * -zval_to_mysqlnd(zval * zv TSRMLS_DC) +zval_to_mysqlnd(zval * zv, const unsigned int client_api_capabilities, unsigned int * save_client_api_capabilities TSRMLS_DC) { MYSQLND * retval; MYSQLND_REVERSE_API ** elem; @@ -80,6 +80,9 @@ zval_to_mysqlnd(zval * zv TSRMLS_DC) if ((*elem)->conversion_cb) { retval = (*elem)->conversion_cb(zv TSRMLS_CC); if (retval) { + if (retval->data) { + *save_client_api_capabilities = retval->data->m->negotiate_client_api_capabilities(retval->data, client_api_capabilities TSRMLS_CC); + } return retval; } } diff --git a/ext/mysqlnd/mysqlnd_reverse_api.h b/ext/mysqlnd/mysqlnd_reverse_api.h index 028a1f6b13..79c0feb60a 100644 --- a/ext/mysqlnd/mysqlnd_reverse_api.h +++ b/ext/mysqlnd/mysqlnd_reverse_api.h @@ -34,8 +34,7 @@ PHPAPI void mysqlnd_reverse_api_end(TSRMLS_D); PHPAPI HashTable * mysqlnd_reverse_api_get_api_list(TSRMLS_D); PHPAPI void mysqlnd_reverse_api_register_api(MYSQLND_REVERSE_API * apiext TSRMLS_DC); - -PHPAPI MYSQLND * zval_to_mysqlnd(zval * zv TSRMLS_DC); +PHPAPI MYSQLND * zval_to_mysqlnd(zval * zv, const unsigned int client_api_capabilities, unsigned int * save_client_api_capabilities TSRMLS_DC); #endif /* MYSQLND_REVERSE_API_H */ diff --git a/ext/mysqlnd/mysqlnd_structs.h b/ext/mysqlnd/mysqlnd_structs.h index a9048b2b13..66920e47be 100644 --- a/ext/mysqlnd/mysqlnd_structs.h +++ b/ext/mysqlnd/mysqlnd_structs.h @@ -235,7 +235,9 @@ typedef struct st_mysqlnd_param_bind MYSQLND_PARAM_BIND; typedef struct st_mysqlnd_result_bind MYSQLND_RESULT_BIND; typedef struct st_mysqlnd_result_metadata MYSQLND_RES_METADATA; -typedef struct st_mysqlnd_buffered_result MYSQLND_RES_BUFFERED; +typedef struct st_mysqlnd_buffered_result_parent MYSQLND_RES_BUFFERED; +typedef struct st_mysqlnd_buffered_result_zval MYSQLND_RES_BUFFERED_ZVAL; +typedef struct st_mysqlnd_buffered_result_c MYSQLND_RES_BUFFERED_C; typedef struct st_mysqlnd_unbuffered_result MYSQLND_RES_UNBUFFERED; typedef struct st_mysqlnd_debug MYSQLND_DEBUG; @@ -243,11 +245,12 @@ typedef struct st_mysqlnd_debug MYSQLND_DEBUG; typedef MYSQLND_RES* (*mysqlnd_stmt_use_or_store_func)(MYSQLND_STMT * const TSRMLS_DC); typedef enum_func_status (*mysqlnd_fetch_row_func)(MYSQLND_RES *result, - void *param, - unsigned int flags, - zend_bool *fetched_anything + void * param, + const unsigned int flags, + zend_bool * fetched_anything TSRMLS_DC); + typedef struct st_mysqlnd_stats MYSQLND_STATS; typedef void (*mysqlnd_stat_trigger)(MYSQLND_STATS * stats, enum_mysqlnd_collected_stats stat, int64_t change TSRMLS_DC); @@ -417,8 +420,8 @@ typedef enum_func_status (*func_mysqlnd_conn_data__set_charset)(MYSQLND_CONN_DAT typedef enum_func_status (*func_mysqlnd_conn_data__query)(MYSQLND_CONN_DATA * conn, const char * query, unsigned int query_len TSRMLS_DC); typedef enum_func_status (*func_mysqlnd_conn_data__send_query)(MYSQLND_CONN_DATA * conn, const char *query, unsigned int query_len TSRMLS_DC); typedef enum_func_status (*func_mysqlnd_conn_data__reap_query)(MYSQLND_CONN_DATA * conn TSRMLS_DC); -typedef MYSQLND_RES * (*func_mysqlnd_conn_data__use_result)(MYSQLND_CONN_DATA * const conn TSRMLS_DC); -typedef MYSQLND_RES * (*func_mysqlnd_conn_data__store_result)(MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef MYSQLND_RES * (*func_mysqlnd_conn_data__use_result)(MYSQLND_CONN_DATA * const conn, const unsigned int flags TSRMLS_DC); +typedef MYSQLND_RES * (*func_mysqlnd_conn_data__store_result)(MYSQLND_CONN_DATA * const conn, const unsigned int flags TSRMLS_DC); typedef enum_func_status (*func_mysqlnd_conn_data__next_result)(MYSQLND_CONN_DATA * const conn TSRMLS_DC); typedef zend_bool (*func_mysqlnd_conn_data__more_results)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC); @@ -499,6 +502,11 @@ typedef struct st_mysqlnd_authentication_plugin * (*func_mysqlnd_conn_data__fetc typedef enum_func_status (*func_mysqlnd_conn_data__set_client_option_2d)(MYSQLND_CONN_DATA * const conn, enum mysqlnd_option option, const char * const key, const char * const value TSRMLS_DC); + +typedef unsigned int (*func_mysqlnd_conn_data__negotiate_client_api_capabilities)(MYSQLND_CONN_DATA * const conn, const unsigned int flags TSRMLS_DC); +typedef unsigned int (*func_mysqlnd_conn_data__get_client_api_capabilities)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC); + + struct st_mysqlnd_conn_data_methods { func_mysqlnd_conn_data__init init; @@ -590,6 +598,9 @@ struct st_mysqlnd_conn_data_methods func_mysqlnd_conn_data__fetch_auth_plugin_by_name fetch_auth_plugin_by_name; func_mysqlnd_conn_data__set_client_option_2d set_client_option_2d; + + func_mysqlnd_conn_data__negotiate_client_api_capabilities negotiate_client_api_capabilities; + func_mysqlnd_conn_data__get_client_api_capabilities get_client_api_capabilities; }; @@ -606,31 +617,31 @@ struct st_mysqlnd_conn_methods func_mysqlnd_conn__close close; }; + /* for decoding - binary or text protocol */ +typedef enum_func_status (*func_mysqlnd_res__row_decoder)(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, + unsigned int field_count, const MYSQLND_FIELD * fields_metadata, + zend_bool as_int_or_float, MYSQLND_STATS * stats TSRMLS_DC); -typedef mysqlnd_fetch_row_func fetch_row; -typedef mysqlnd_fetch_row_func fetch_row_normal_buffered; /* private */ -typedef mysqlnd_fetch_row_func fetch_row_normal_unbuffered; /* private */ typedef MYSQLND_RES * (*func_mysqlnd_res__use_result)(MYSQLND_RES * const result, zend_bool ps_protocol TSRMLS_DC); -typedef MYSQLND_RES * (*func_mysqlnd_res__store_result)(MYSQLND_RES * result, MYSQLND_CONN_DATA * const conn, zend_bool ps TSRMLS_DC); -typedef void (*func_mysqlnd_res__fetch_into)(MYSQLND_RES *result, unsigned int flags, zval *return_value, enum_mysqlnd_extension ext TSRMLS_DC ZEND_FILE_LINE_DC); +typedef MYSQLND_RES * (*func_mysqlnd_res__store_result)(MYSQLND_RES * result, MYSQLND_CONN_DATA * const conn, const unsigned int flags TSRMLS_DC); +typedef void (*func_mysqlnd_res__fetch_into)(MYSQLND_RES *result, const unsigned int flags, zval *return_value, enum_mysqlnd_extension ext TSRMLS_DC ZEND_FILE_LINE_DC); typedef MYSQLND_ROW_C (*func_mysqlnd_res__fetch_row_c)(MYSQLND_RES *result TSRMLS_DC); -typedef void (*func_mysqlnd_res__fetch_all)(MYSQLND_RES *result, unsigned int flags, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC); +typedef void (*func_mysqlnd_res__fetch_all)(MYSQLND_RES *result, const unsigned int flags, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC); typedef void (*func_mysqlnd_res__fetch_field_data)(MYSQLND_RES *result, unsigned int offset, zval *return_value TSRMLS_DC); typedef uint64_t (*func_mysqlnd_res__num_rows)(const MYSQLND_RES * const result TSRMLS_DC); typedef unsigned int (*func_mysqlnd_res__num_fields)(const MYSQLND_RES * const result TSRMLS_DC); typedef enum_func_status (*func_mysqlnd_res__skip_result)(MYSQLND_RES * const result TSRMLS_DC); -typedef enum_func_status (*func_mysqlnd_res__seek_data)(MYSQLND_RES * result, uint64_t row TSRMLS_DC); -typedef MYSQLND_FIELD_OFFSET (*func_mysqlnd_res__seek_field)(MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET field_offset TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_res__seek_data)(MYSQLND_RES * const result, const uint64_t row TSRMLS_DC); +typedef MYSQLND_FIELD_OFFSET (*func_mysqlnd_res__seek_field)(MYSQLND_RES * const result, const MYSQLND_FIELD_OFFSET field_offset TSRMLS_DC); typedef MYSQLND_FIELD_OFFSET (*func_mysqlnd_res__field_tell)(const MYSQLND_RES * const result TSRMLS_DC); typedef const MYSQLND_FIELD *(*func_mysqlnd_res__fetch_field)(MYSQLND_RES * const result TSRMLS_DC); -typedef const MYSQLND_FIELD *(*func_mysqlnd_res__fetch_field_direct)(MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC); +typedef const MYSQLND_FIELD *(*func_mysqlnd_res__fetch_field_direct)(MYSQLND_RES * const result, const MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC); typedef const MYSQLND_FIELD *(*func_mysqlnd_res__fetch_fields)(MYSQLND_RES * const result TSRMLS_DC); typedef enum_func_status (*func_mysqlnd_res__read_result_metadata)(MYSQLND_RES * result, MYSQLND_CONN_DATA * conn TSRMLS_DC); typedef unsigned long * (*func_mysqlnd_res__fetch_lengths)(MYSQLND_RES * const result TSRMLS_DC); -typedef enum_func_status (*func_mysqlnd_res__store_result_fetch_data)(MYSQLND_CONN_DATA * const conn, MYSQLND_RES * result, MYSQLND_RES_METADATA *meta, zend_bool binary_protocol TSRMLS_DC); -typedef enum_func_status (*func_mysqlnd_res__initialize_result_set_rest)(MYSQLND_RES * const result TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_res__store_result_fetch_data)(MYSQLND_CONN_DATA * const conn, MYSQLND_RES * result, MYSQLND_RES_METADATA * meta, MYSQLND_MEMORY_POOL_CHUNK *** row_buffers, zend_bool binary_protocol TSRMLS_DC); typedef void (*func_mysqlnd_res__free_result_buffers)(MYSQLND_RES * result TSRMLS_DC); /* private */ typedef enum_func_status (*func_mysqlnd_res__free_result)(MYSQLND_RES * result, zend_bool implicit TSRMLS_DC); @@ -639,18 +650,12 @@ typedef void (*func_mysqlnd_res__free_result_contents)(MYSQLND_RES *result TS typedef void (*func_mysqlnd_res__free_buffered_data)(MYSQLND_RES *result TSRMLS_DC); typedef void (*func_mysqlnd_res__unbuffered_free_last_data)(MYSQLND_RES *result TSRMLS_DC); - /* for decoding - binary or text protocol */ -typedef enum_func_status (*func_mysqlnd_res__row_decoder)(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, - unsigned int field_count, const MYSQLND_FIELD * fields_metadata, - zend_bool as_int_or_float, MYSQLND_STATS * stats TSRMLS_DC); typedef MYSQLND_RES_METADATA * (*func_mysqlnd_res__result_meta_init)(unsigned int field_count, zend_bool persistent TSRMLS_DC); struct st_mysqlnd_res_methods { mysqlnd_fetch_row_func fetch_row; - mysqlnd_fetch_row_func fetch_row_normal_buffered; /* private */ - mysqlnd_fetch_row_func fetch_row_normal_unbuffered; /* private */ func_mysqlnd_res__use_result use_result; func_mysqlnd_res__store_result store_result; @@ -670,16 +675,10 @@ struct st_mysqlnd_res_methods func_mysqlnd_res__read_result_metadata read_result_metadata; func_mysqlnd_res__fetch_lengths fetch_lengths; func_mysqlnd_res__store_result_fetch_data store_result_fetch_data; - func_mysqlnd_res__initialize_result_set_rest initialize_result_set_rest; func_mysqlnd_res__free_result_buffers free_result_buffers; func_mysqlnd_res__free_result free_result; func_mysqlnd_res__free_result_internal free_result_internal; func_mysqlnd_res__free_result_contents free_result_contents; - func_mysqlnd_res__free_buffered_data free_buffered_data; - func_mysqlnd_res__unbuffered_free_last_data unbuffered_free_last_data; - - /* for decoding - binary or text protocol */ - func_mysqlnd_res__row_decoder row_decoder; func_mysqlnd_res__result_meta_init result_meta_init; @@ -691,10 +690,45 @@ struct st_mysqlnd_res_methods }; +typedef uint64_t (*func_mysqlnd_result_unbuffered__num_rows)(const MYSQLND_RES_UNBUFFERED * const result TSRMLS_DC); +typedef unsigned long * (*func_mysqlnd_result_unbuffered__fetch_lengths)(MYSQLND_RES_UNBUFFERED * const result TSRMLS_DC); +typedef void (*func_mysqlnd_result_unbuffered__free_last_data)(MYSQLND_RES_UNBUFFERED * result, MYSQLND_STATS * const global_stats TSRMLS_DC); +typedef void (*func_mysqlnd_result_unbuffered__free_result)(MYSQLND_RES_UNBUFFERED * const result, MYSQLND_STATS * const global_stats TSRMLS_DC); + +struct st_mysqlnd_result_unbuffered_methods +{ + mysqlnd_fetch_row_func fetch_row; + func_mysqlnd_res__row_decoder row_decoder; + func_mysqlnd_result_unbuffered__num_rows num_rows; + func_mysqlnd_result_unbuffered__fetch_lengths fetch_lengths; + func_mysqlnd_result_unbuffered__free_last_data free_last_data; + func_mysqlnd_result_unbuffered__free_result free_result; +}; + +typedef uint64_t (*func_mysqlnd_result_buffered__num_rows)(const MYSQLND_RES_BUFFERED * const result TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_result_buffered__initialize_result_set_rest)(MYSQLND_RES_BUFFERED * const result, MYSQLND_RES_METADATA * const meta, + MYSQLND_STATS * stats, zend_bool int_and_float_native TSRMLS_DC); +typedef unsigned long * (*func_mysqlnd_result_buffered__fetch_lengths)(MYSQLND_RES_BUFFERED * const result TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_result_buffered__data_seek)(MYSQLND_RES_BUFFERED * const result, const uint64_t row TSRMLS_DC); +typedef void (*func_mysqlnd_result_buffered__free_result)(MYSQLND_RES_BUFFERED * const result TSRMLS_DC); + +struct st_mysqlnd_result_buffered_methods +{ + mysqlnd_fetch_row_func fetch_row; + func_mysqlnd_res__row_decoder row_decoder; + func_mysqlnd_result_buffered__num_rows num_rows; + func_mysqlnd_result_buffered__fetch_lengths fetch_lengths; + func_mysqlnd_result_buffered__data_seek data_seek; + func_mysqlnd_result_buffered__initialize_result_set_rest initialize_result_set_rest; + func_mysqlnd_result_buffered__free_result free_result; +}; + + typedef const MYSQLND_FIELD * (*func_mysqlnd_res_meta__fetch_field)(MYSQLND_RES_METADATA * const meta TSRMLS_DC); -typedef const MYSQLND_FIELD * (*func_mysqlnd_res_meta__fetch_field_direct)(const MYSQLND_RES_METADATA * const meta, MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC); +typedef const MYSQLND_FIELD * (*func_mysqlnd_res_meta__fetch_field_direct)(const MYSQLND_RES_METADATA * const meta, const MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC); typedef const MYSQLND_FIELD * (*func_mysqlnd_res_meta__fetch_fields)(MYSQLND_RES_METADATA * const meta TSRMLS_DC); typedef MYSQLND_FIELD_OFFSET (*func_mysqlnd_res_meta__field_tell)(const MYSQLND_RES_METADATA * const meta TSRMLS_DC); +typedef MYSQLND_FIELD_OFFSET (*func_mysqlnd_res_meta__field_seek)(MYSQLND_RES_METADATA * const meta, const MYSQLND_FIELD_OFFSET field_offset TSRMLS_DC); typedef enum_func_status (*func_mysqlnd_res_meta__read_metadata)(MYSQLND_RES_METADATA * const meta, MYSQLND_CONN_DATA * conn TSRMLS_DC); typedef MYSQLND_RES_METADATA * (*func_mysqlnd_res_meta__clone_metadata)(const MYSQLND_RES_METADATA * const meta, zend_bool persistent TSRMLS_DC); typedef void (*func_mysqlnd_res_meta__free_metadata)(MYSQLND_RES_METADATA * meta TSRMLS_DC); @@ -705,6 +739,7 @@ struct st_mysqlnd_res_meta_methods func_mysqlnd_res_meta__fetch_field_direct fetch_field_direct; func_mysqlnd_res_meta__fetch_fields fetch_fields; func_mysqlnd_res_meta__field_tell field_tell; + func_mysqlnd_res_meta__field_seek field_seek; func_mysqlnd_res_meta__read_metadata read_metadata; func_mysqlnd_res_meta__clone_metadata clone_metadata; func_mysqlnd_res_meta__free_metadata free_metadata; @@ -922,9 +957,6 @@ struct st_mysqlnd_connection_data /* Temporal storage for mysql_query */ unsigned int field_count; - /* persistent connection */ - zend_bool persistent; - /* options */ MYSQLND_OPTIONS * options; MYSQLND_OPTIONS options_impl; @@ -932,7 +964,12 @@ struct st_mysqlnd_connection_data /* stats */ MYSQLND_STATS * stats; + unsigned int client_api_capabilities; + struct st_mysqlnd_conn_data_methods * m; + + /* persistent connection */ + zend_bool persistent; }; @@ -962,32 +999,83 @@ struct st_mysqlnd_result_metadata size_t bit_fields_total_len; /* trailing \0 not counted */ zend_bool persistent; - struct st_mysqlnd_res_meta_methods *m; + struct st_mysqlnd_res_meta_methods * m; }; -struct st_mysqlnd_buffered_result +#define def_mysqlnd_buffered_result_parent \ + MYSQLND_MEMORY_POOL_CHUNK **row_buffers; \ + uint64_t row_count; \ + uint64_t initialized_rows; \ + \ + /* Column lengths of current row - both buffered and unbuffered. For buffered results it duplicates the data found in **data */ \ + unsigned long *lengths; \ + \ + MYSQLND_MEMORY_POOL *result_set_memory_pool; \ + \ + unsigned int references; \ + \ + MYSQLND_ERROR_INFO error_info; \ + \ + unsigned int field_count; \ + zend_bool ps; \ + zend_bool persistent; \ + struct st_mysqlnd_result_buffered_methods m; \ + enum mysqlnd_buffered_type type; \ + void * unused1; \ + void * unused2; \ + void * unused3 + + +struct st_mysqlnd_buffered_result_parent { - zval **data; - zval **data_cursor; - MYSQLND_MEMORY_POOL_CHUNK **row_buffers; - uint64_t row_count; - uint64_t initialized_rows; + def_mysqlnd_buffered_result_parent; +}; - unsigned int references; - MYSQLND_ERROR_INFO error_info; +struct st_mysqlnd_buffered_result_zval +{ + def_mysqlnd_buffered_result_parent; + + zval **data; + zval **data_cursor; +}; + + +struct st_mysqlnd_buffered_result_c +{ + def_mysqlnd_buffered_result_parent; + + zend_uchar *initialized; /* every row is a single bit */ + uint64_t current_row; }; struct st_mysqlnd_unbuffered_result { + /* For unbuffered (both normal and PS) */ zval **last_row_data; MYSQLND_MEMORY_POOL_CHUNK *last_row_buffer; + /* + Column lengths of current row - both buffered and unbuffered. + For buffered results it duplicates the data found in **data + */ + unsigned long *lengths; + + MYSQLND_MEMORY_POOL *result_set_memory_pool; + + struct st_mysqlnd_packet_row * row_packet; + uint64_t row_count; zend_bool eof_reached; + + unsigned int field_count; + zend_bool ps; + zend_bool persistent; + + struct st_mysqlnd_result_unbuffered_methods m; }; @@ -1001,18 +1089,9 @@ struct st_mysqlnd_res MYSQLND_RES_METADATA *meta; /* To be used with store_result() - both normal and PS */ - MYSQLND_RES_BUFFERED *stored_data; - MYSQLND_RES_UNBUFFERED *unbuf; - - /* - Column lengths of current row - both buffered and unbuffered. - For buffered results it duplicates the data found in **data - */ - unsigned long *lengths; - - struct st_mysqlnd_packet_row * row_packet; + MYSQLND_RES_BUFFERED *stored_data; + MYSQLND_RES_UNBUFFERED *unbuf; - MYSQLND_MEMORY_POOL * result_set_memory_pool; zend_bool persistent; struct st_mysqlnd_res_methods m; diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c index d78d29a2f1..ecce71d9c2 100644 --- a/ext/mysqlnd/mysqlnd_wireprotocol.c +++ b/ext/mysqlnd/mysqlnd_wireprotocol.c @@ -90,7 +90,8 @@ const char * const mysqlnd_command_to_text[COM_END] = "TIME", "DELAYED_INSERT", "CHANGE_USER", "BINLOG_DUMP", "TABLE_DUMP", "CONNECT_OUT", "REGISTER_SLAVE", "STMT_PREPARE", "STMT_EXECUTE", "STMT_SEND_LONG_DATA", "STMT_CLOSE", - "STMT_RESET", "SET_OPTION", "STMT_FETCH", "DAEMON" + "STMT_RESET", "SET_OPTION", "STMT_FETCH", "DAEMON", "BINLOG_DUMP_GTID", + "RESET_CONNECTION" }; /* }}} */ @@ -1571,13 +1572,13 @@ php_mysqlnd_rowp_read_binary_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zv } /* }}} */ - /* {{{ php_mysqlnd_rowp_read_text_protocol */ enum_func_status -php_mysqlnd_rowp_read_text_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, +php_mysqlnd_rowp_read_text_protocol_aux(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, unsigned int field_count, const MYSQLND_FIELD * fields_metadata, - zend_bool as_int_or_float, MYSQLND_STATS * stats TSRMLS_DC) + zend_bool as_int_or_float, zend_bool copy_data, MYSQLND_STATS * stats TSRMLS_DC) { + unsigned int i; zend_bool last_field_was_string = FALSE; zval **current_field, **end_field, **start_field; @@ -1585,7 +1586,7 @@ php_mysqlnd_rowp_read_text_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval size_t data_size = row_buffer->app; zend_uchar * bit_area = (zend_uchar*) row_buffer->ptr + data_size + 1; /* we allocate from here */ - DBG_ENTER("php_mysqlnd_rowp_read_text_protocol"); + DBG_ENTER("php_mysqlnd_rowp_read_text_protocol_aux"); if (!fields) { DBG_RETURN(FAIL); @@ -1607,7 +1608,7 @@ php_mysqlnd_rowp_read_text_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval /* php_mysqlnd_net_field_length() call should be after *this_field_len_pos = p; */ unsigned long len = php_mysqlnd_net_field_length(&p); - if (current_field > start_field && last_field_was_string) { + if (copy_data == FALSE && current_field > start_field && last_field_was_string) { /* Normal queries: We have to put \0 now to the end of the previous field, if it was @@ -1732,21 +1733,22 @@ php_mysqlnd_rowp_read_text_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval p -= len; if (Z_TYPE_PP(current_field) == IS_LONG) { bit_area += 1 + sprintf((char *)start, "%ld", Z_LVAL_PP(current_field)); - ZVAL_STRINGL(*current_field, (char *) start, bit_area - start - 1, 0); + ZVAL_STRINGL(*current_field, (char *) start, bit_area - start - 1, copy_data); } else if (Z_TYPE_PP(current_field) == IS_STRING){ memcpy(bit_area, Z_STRVAL_PP(current_field), Z_STRLEN_PP(current_field)); bit_area += Z_STRLEN_PP(current_field); *bit_area++ = '\0'; zval_dtor(*current_field); - ZVAL_STRINGL(*current_field, (char *) start, bit_area - start - 1, 0); + ZVAL_STRINGL(*current_field, (char *) start, bit_area - start - 1, copy_data); } - } else - ZVAL_STRINGL(*current_field, (char *)p, len, 0); + } else { + ZVAL_STRINGL(*current_field, (char *)p, len, copy_data); + } p += len; last_field_was_string = TRUE; } } - if (last_field_was_string) { + if (copy_data == FALSE && last_field_was_string) { /* Normal queries: The buffer has one more byte at the end, because we need it */ row_buffer->ptr[data_size] = '\0'; } @@ -1756,6 +1758,36 @@ php_mysqlnd_rowp_read_text_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval /* }}} */ +/* {{{ php_mysqlnd_rowp_read_text_protocol_zval */ +enum_func_status +php_mysqlnd_rowp_read_text_protocol_zval(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, + unsigned int field_count, const MYSQLND_FIELD * fields_metadata, + zend_bool as_int_or_float, MYSQLND_STATS * stats TSRMLS_DC) +{ + enum_func_status ret; + DBG_ENTER("php_mysqlnd_rowp_read_text_protocol_zval"); + ret = php_mysqlnd_rowp_read_text_protocol_aux(row_buffer, fields, field_count, fields_metadata, as_int_or_float, FALSE, stats TSRMLS_CC); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ php_mysqlnd_rowp_read_text_protocol_c */ +enum_func_status +php_mysqlnd_rowp_read_text_protocol_c(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, + unsigned int field_count, const MYSQLND_FIELD * fields_metadata, + zend_bool as_int_or_float, MYSQLND_STATS * stats TSRMLS_DC) +{ + enum_func_status ret; + DBG_ENTER("php_mysqlnd_rowp_read_text_protocol_c"); + ret = php_mysqlnd_rowp_read_text_protocol_aux(row_buffer, fields, field_count, fields_metadata, as_int_or_float, TRUE, stats TSRMLS_CC); + DBG_RETURN(ret); +} +/* }}} */ + + + + /* {{{ php_mysqlnd_rowp_read */ /* if normal statements => packet->fields is created by this function, diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.h b/ext/mysqlnd/mysqlnd_wireprotocol.h index 4bd33592bf..7e8b9624ac 100644 --- a/ext/mysqlnd/mysqlnd_wireprotocol.h +++ b/ext/mysqlnd/mysqlnd_wireprotocol.h @@ -302,6 +302,7 @@ PHPAPI void php_mysqlnd_scramble(zend_uchar * const buffer, const zend_uchar * c unsigned long php_mysqlnd_net_field_length(zend_uchar **packet); zend_uchar * php_mysqlnd_net_store_length(zend_uchar *packet, uint64_t length); +size_t php_mysqlnd_net_store_length_size(uint64_t length); PHPAPI const extern char * const mysqlnd_empty_string; @@ -311,7 +312,11 @@ enum_func_status php_mysqlnd_rowp_read_binary_protocol(MYSQLND_MEMORY_POOL_CHUNK zend_bool as_int_or_float, MYSQLND_STATS * stats TSRMLS_DC); -enum_func_status php_mysqlnd_rowp_read_text_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, +enum_func_status php_mysqlnd_rowp_read_text_protocol_zval(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, + unsigned int field_count, const MYSQLND_FIELD * fields_metadata, + zend_bool as_int_or_float, MYSQLND_STATS * stats TSRMLS_DC); + +enum_func_status php_mysqlnd_rowp_read_text_protocol_c(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, unsigned int field_count, const MYSQLND_FIELD * fields_metadata, zend_bool as_int_or_float, MYSQLND_STATS * stats TSRMLS_DC); diff --git a/ext/mysqlnd/php_mysqlnd.c b/ext/mysqlnd/php_mysqlnd.c index cfa2b1f8fc..c2c53604e5 100644 --- a/ext/mysqlnd/php_mysqlnd.c +++ b/ext/mysqlnd/php_mysqlnd.c @@ -107,17 +107,17 @@ static void mysqlnd_minfo_dump_api_plugins(smart_str * buffer TSRMLS_DC) { HashTable *ht = mysqlnd_reverse_api_get_api_list(TSRMLS_C); - uint idx; - Bucket *p; + HashPosition pos; + MYSQLND_REVERSE_API **ext; - for (idx = 0; idx < ht->nNumUsed; idx++) { - p = ht->arData + idx; - if (!p->xData) continue; - MYSQLND_REVERSE_API * ext = (MYSQLND_REVERSE_API *) p->xData; + for (zend_hash_internal_pointer_reset_ex(ht, &pos); + zend_hash_get_current_data_ex(ht, (void **) &ext, &pos) == SUCCESS; + zend_hash_move_forward_ex(ht, &pos) + ) { if (buffer->len) { smart_str_appendc(buffer, ','); } - smart_str_appends(buffer, ext->module->name); + smart_str_appends(buffer, (*ext)->module->name); } } /* }}} */ @@ -209,6 +209,7 @@ static PHP_GINIT_FUNCTION(mysqlnd) mysqlnd_globals->debug_calloc_fail_threshold = -1; mysqlnd_globals->debug_realloc_fail_threshold = -1; mysqlnd_globals->sha256_server_public_key = NULL; + mysqlnd_globals->fetch_data_copy = FALSE; } /* }}} */ @@ -227,8 +228,8 @@ static PHP_INI_MH(OnUpdateNetCmdBufferSize) /* {{{ PHP_INI_BEGIN */ PHP_INI_BEGIN() - STD_PHP_INI_BOOLEAN("mysqlnd.collect_statistics", "1", PHP_INI_ALL, OnUpdateBool, collect_statistics, zend_mysqlnd_globals, mysqlnd_globals) - STD_PHP_INI_BOOLEAN("mysqlnd.collect_memory_statistics", "0", PHP_INI_SYSTEM, OnUpdateBool, collect_memory_statistics, zend_mysqlnd_globals, mysqlnd_globals) + STD_PHP_INI_BOOLEAN("mysqlnd.collect_statistics", "1", PHP_INI_ALL, OnUpdateBool, collect_statistics, zend_mysqlnd_globals, mysqlnd_globals) + STD_PHP_INI_BOOLEAN("mysqlnd.collect_memory_statistics","0",PHP_INI_SYSTEM, OnUpdateBool, collect_memory_statistics, zend_mysqlnd_globals, mysqlnd_globals) STD_PHP_INI_ENTRY("mysqlnd.debug", NULL, PHP_INI_SYSTEM, OnUpdateString, debug, zend_mysqlnd_globals, mysqlnd_globals) STD_PHP_INI_ENTRY("mysqlnd.trace_alloc", NULL, PHP_INI_SYSTEM, OnUpdateString, trace_alloc_settings, zend_mysqlnd_globals, mysqlnd_globals) STD_PHP_INI_ENTRY("mysqlnd.net_cmd_buffer_size", MYSQLND_NET_CMD_BUFFER_MIN_SIZE_STR, PHP_INI_ALL, OnUpdateNetCmdBufferSize, net_cmd_buffer_size, zend_mysqlnd_globals, mysqlnd_globals) @@ -237,7 +238,7 @@ PHP_INI_BEGIN() STD_PHP_INI_ENTRY("mysqlnd.log_mask", "0", PHP_INI_ALL, OnUpdateLong, log_mask, zend_mysqlnd_globals, mysqlnd_globals) STD_PHP_INI_ENTRY("mysqlnd.mempool_default_size","16000", PHP_INI_ALL, OnUpdateLong, mempool_default_size, zend_mysqlnd_globals, mysqlnd_globals) STD_PHP_INI_ENTRY("mysqlnd.sha256_server_public_key",NULL, PHP_INI_PERDIR, OnUpdateString, sha256_server_public_key, zend_mysqlnd_globals, mysqlnd_globals) - + STD_PHP_INI_BOOLEAN("mysqlnd.fetch_data_copy", "0", PHP_INI_ALL, OnUpdateBool, fetch_data_copy, zend_mysqlnd_globals, mysqlnd_globals) #if PHP_DEBUG STD_PHP_INI_ENTRY("mysqlnd.debug_emalloc_fail_threshold","-1", PHP_INI_SYSTEM, OnUpdateLong, debug_emalloc_fail_threshold, zend_mysqlnd_globals, mysqlnd_globals) STD_PHP_INI_ENTRY("mysqlnd.debug_ecalloc_fail_threshold","-1", PHP_INI_SYSTEM, OnUpdateLong, debug_ecalloc_fail_threshold, zend_mysqlnd_globals, mysqlnd_globals) |