From: Andrey Hristov Date: Tue, 18 Nov 2008 16:54:38 +0000 (+0000) Subject: Asynchronous queries for mysqli, when mysqlnd is enabled. X-Git-Tag: BEFORE_HEAD_NS_CHANGES_MERGE~155 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=983f575f22903842490d597848bcbbdc465dbe75;p=php Asynchronous queries for mysqli, when mysqlnd is enabled. Includes 4 tests for mysqli_poll --- diff --git a/ext/mysqli/mysqli.c b/ext/mysqli/mysqli.c index 547b7f1e86..861952d6ca 100644 --- a/ext/mysqli/mysqli.c +++ b/ext/mysqli/mysqli.c @@ -800,6 +800,9 @@ PHP_MINIT_FUNCTION(mysqli) #if defined(MYSQLI_USE_MYSQLND) && defined(MYSQLND_THREADED) REGISTER_LONG_CONSTANT("MYSQLI_BG_STORE_RESULT", MYSQLI_BG_STORE_RESULT, CONST_CS | CONST_PERSISTENT); #endif +#if defined (MYSQLI_USE_MYSQLND) + REGISTER_LONG_CONSTANT("MYSQLI_ASYNC", MYSQLI_ASYNC, CONST_CS | CONST_PERSISTENT); +#endif /* for mysqli_fetch_assoc */ REGISTER_LONG_CONSTANT("MYSQLI_ASSOC", MYSQLI_ASSOC, CONST_CS | CONST_PERSISTENT); diff --git a/ext/mysqli/mysqli_fe.c b/ext/mysqli/mysqli_fe.c index 56c9f0a8d8..e17cdeaff4 100644 --- a/ext/mysqli/mysqli_fe.c +++ b/ext/mysqli/mysqli_fe.c @@ -115,12 +115,18 @@ const zend_function_entry mysqli_functions[] = { PHP_FE(mysqli_num_rows, NULL) PHP_FE(mysqli_options, NULL) PHP_FE(mysqli_ping, NULL) +#if defined(MYSQLI_USE_MYSQLND) + PHP_FE(mysqli_poll, NULL) +#endif PHP_FE(mysqli_prepare, NULL) PHP_FE(mysqli_report, NULL) PHP_FE(mysqli_query, NULL) PHP_FE(mysqli_real_connect, NULL) PHP_FE(mysqli_real_escape_string, NULL) PHP_FE(mysqli_real_query, NULL) +#if defined(MYSQLI_USE_MYSQLND) + PHP_FE(mysqli_reap_async_query, NULL) +#endif PHP_FE(mysqli_rollback, NULL) PHP_FE(mysqli_select_db, NULL) #ifdef HAVE_MYSQLI_SET_CHARSET @@ -221,6 +227,10 @@ const zend_function_entry mysqli_link_methods[] = { PHP_FALIAS(query,mysqli_query,NULL) PHP_FALIAS(real_connect,mysqli_real_connect,NULL) PHP_FALIAS(real_escape_string,mysqli_real_escape_string,NULL) +#if defined(MYSQLI_USE_MYSQLND) + PHP_FALIAS(poll,mysqli_poll,NULL) + PHP_FALIAS(reap_async_query,mysqli_reap_async_query,NULL) +#endif PHP_FALIAS(escape_string, mysqli_real_escape_string,NULL) PHP_FALIAS(real_query,mysqli_real_query,NULL) PHP_FALIAS(rollback,mysqli_rollback,NULL) diff --git a/ext/mysqli/mysqli_mysqlnd.h b/ext/mysqli/mysqli_mysqlnd.h index ae1ed0caac..0c2a04d8a1 100644 --- a/ext/mysqli/mysqli_mysqlnd.h +++ b/ext/mysqli/mysqli_mysqlnd.h @@ -38,5 +38,6 @@ #define mysqli_stmt_close(c, implicit) mysqlnd_stmt_close((c), (implicit)) #define mysqli_free_result(r, implicit) mysqlnd_free_result((r), (implicit)) #define mysqli_bg_store_result(r) mysqlnd_bg_store_result((r)) +#define mysqli_async_query(c, q, l) mysqlnd_async_query((c), (q), (l)) #endif diff --git a/ext/mysqli/mysqli_nonapi.c b/ext/mysqli/mysqli_nonapi.c index 6363c59c01..0f44a67859 100644 --- a/ext/mysqli/mysqli_nonapi.c +++ b/ext/mysqli/mysqli_nonapi.c @@ -61,6 +61,7 @@ void mysqli_common_connect(INTERNAL_FUNCTION_PARAMETERS, zend_bool is_real_conne zend_rsrc_list_entry *le; mysqli_plist_entry *plist = NULL; + #if !defined(MYSQL_USE_MYSQLND) if ((MYSQL_VERSION_ID / 100) != (mysql_get_client_version() / 100)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, @@ -109,6 +110,7 @@ void mysqli_common_connect(INTERNAL_FUNCTION_PARAMETERS, zend_bool is_real_conne mysqli_resource = ((mysqli_object *) zend_object_store_get_object(object TSRMLS_CC))->ptr; MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &object, "mysqli_link", MYSQLI_STATUS_INITIALIZED); + /* set some required options */ flags |= CLIENT_MULTI_RESULTS; /* needed for mysql_multi_query() */ /* remove some insecure options */ flags &= ~CLIENT_MULTI_STATEMENTS; /* don't allow multi_queries via connect parameter */ @@ -172,6 +174,7 @@ void mysqli_common_connect(INTERNAL_FUNCTION_PARAMETERS, zend_bool is_real_conne #endif /* reset variables */ /* todo: option for ping or change_user */ + #if G0 if (!mysql_change_user(mysql->mysql, username, passwd, dbname)) { #else @@ -541,9 +544,9 @@ PHP_FUNCTION(mysqli_query) php_error_docref(NULL TSRMLS_CC, E_WARNING, "Empty query"); RETURN_FALSE; } - if (resultmode != MYSQLI_USE_RESULT && resultmode != MYSQLI_STORE_RESULT + if ((resultmode & ~MYSQLI_ASYNC) != MYSQLI_USE_RESULT && (resultmode & ~MYSQLI_ASYNC) != MYSQLI_STORE_RESULT #if defined(MYSQLI_USE_MYSQLND) && defined(MYSQLND_THREADED) - && resultmode != MYSQLI_BG_STORE_RESULT + && (resultmode & ~MYSQLI_ASYNC) != MYSQLI_BG_STORE_RESULT #endif ) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid value for resultmode"); @@ -554,6 +557,18 @@ PHP_FUNCTION(mysqli_query) MYSQLI_DISABLE_MQ; + +#ifdef MYSQLI_USE_MYSQLND + if (resultmode & MYSQLI_ASYNC) { + if (mysqli_async_query(mysql->mysql, query, query_len)) { + MYSQLI_REPORT_MYSQL_ERROR(mysql->mysql); + RETURN_FALSE; + } + mysql->async_result_fetch_type = resultmode & ~MYSQLI_ASYNC; + RETURN_TRUE; + } +#endif + if (mysql_real_query(mysql->mysql, query, query_len)) { MYSQLI_REPORT_MYSQL_ERROR(mysql->mysql); RETURN_FALSE; @@ -599,6 +614,244 @@ PHP_FUNCTION(mysqli_query) #if defined(MYSQLI_USE_MYSQLND) +#include "php_network.h" +/* {{{ mysqlnd_zval_array_to_mysqlnd_array functions */ +static int mysqlnd_zval_array_to_mysqlnd_array(zval *in_array, MYSQLND ***out_array TSRMLS_DC) +{ + zval **elem; + int i = 0, current = 0; + + if (Z_TYPE_P(in_array) != IS_ARRAY) { + return 0; + } + *out_array = ecalloc(zend_hash_num_elements(Z_ARRVAL_P(in_array)) + 1, sizeof(MYSQLND *)); + for (zend_hash_internal_pointer_reset(Z_ARRVAL_P(in_array)); + zend_hash_get_current_data(Z_ARRVAL_P(in_array), (void **) &elem) == SUCCESS; + zend_hash_move_forward(Z_ARRVAL_P(in_array))) { + i++; + if (Z_TYPE_PP(elem) != IS_OBJECT || + !instanceof_function(Z_OBJCE_PP(elem), mysqli_link_class_entry TSRMLS_CC)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Parameter %d not a mysqli object", i); + } else { + MY_MYSQL *mysql; + MYSQLI_RESOURCE *my_res; + mysqli_object *intern = (mysqli_object *)zend_object_store_get_object(*elem TSRMLS_CC); + if (!(my_res = (MYSQLI_RESOURCE *)intern->ptr)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "[%d] Couldn't fetch %v", i, intern->zo.ce->name); + continue; + } + mysql = (MY_MYSQL*) my_res->ptr; + if (MYSQLI_STATUS_VALID && my_res->status < MYSQLI_STATUS_VALID) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid object %d or resource %v", i, intern->zo.ce->name); + continue; + } + (*out_array)[current++] = mysql->mysql; + } + } + return 0; +} +/* }}} */ + + +/* {{{ mysqlnd_zval_array_from_mysqlnd_array */ +static int mysqlnd_zval_array_from_mysqlnd_array(MYSQLND **in_array, zval *out_array TSRMLS_DC) +{ + MYSQLND **p = in_array; + HashTable *new_hash; + zval **elem, **dest_elem; + int ret = 0; + + ALLOC_HASHTABLE(new_hash); + zend_hash_init(new_hash, zend_hash_num_elements(Z_ARRVAL_P(out_array)), NULL, ZVAL_PTR_DTOR, 0); + + for (zend_hash_internal_pointer_reset(Z_ARRVAL_P(out_array)); + zend_hash_get_current_data(Z_ARRVAL_P(out_array), (void **) &elem) == SUCCESS; + zend_hash_move_forward(Z_ARRVAL_P(out_array))) + { + if (Z_TYPE_PP(elem) != IS_OBJECT || !instanceof_function(Z_OBJCE_PP(elem), mysqli_link_class_entry TSRMLS_CC)) { + continue; + } + { + MY_MYSQL *mysql; + mysqli_object *intern = (mysqli_object *)zend_object_store_get_object(*elem TSRMLS_CC); + mysql = (MY_MYSQL *) ((MYSQLI_RESOURCE *)intern->ptr)->ptr; + if (mysql->mysql == *p) { + zend_hash_next_index_insert(new_hash, (void *)elem, sizeof(zval *), (void **)&dest_elem); + if (dest_elem) { + zval_add_ref(dest_elem); + } + ret++; + p++; + } + } + } + + /* destroy old array and add new one */ + zend_hash_destroy(Z_ARRVAL_P(out_array)); + efree(Z_ARRVAL_P(out_array)); + + zend_hash_internal_pointer_reset(new_hash); + Z_ARRVAL_P(out_array) = new_hash; + + return 0; +} +/* }}} */ + + +/* {{{ mysqlnd_dont_poll_zval_array_from_mysqlnd_array */ +static int mysqlnd_dont_poll_zval_array_from_mysqlnd_array(MYSQLND **in_array, zval *in_zval_array, zval *out_array TSRMLS_DC) +{ + MYSQLND **p = in_array; + HashTable *new_hash; + zval **elem, **dest_elem; + int ret = 0; + + ALLOC_HASHTABLE(new_hash); + zend_hash_init(new_hash, zend_hash_num_elements(Z_ARRVAL_P(in_zval_array)), NULL, ZVAL_PTR_DTOR, 0); + if (in_array) { + for (zend_hash_internal_pointer_reset(Z_ARRVAL_P(in_zval_array)); + zend_hash_get_current_data(Z_ARRVAL_P(in_zval_array), (void **) &elem) == SUCCESS; + zend_hash_move_forward(Z_ARRVAL_P(in_zval_array))) + { + MY_MYSQL *mysql; + mysqli_object *intern = (mysqli_object *)zend_object_store_get_object(*elem TSRMLS_CC); + mysql = (MY_MYSQL *) ((MYSQLI_RESOURCE *)intern->ptr)->ptr; + if (mysql->mysql == *p) { + zend_hash_next_index_insert(new_hash, (void *)elem, sizeof(zval *), (void **)&dest_elem); + if (dest_elem) { + zval_add_ref(dest_elem); + } + ret++; + p++; + } + } + } + + /* destroy old array and add new one */ + zend_hash_destroy(Z_ARRVAL_P(out_array)); + efree(Z_ARRVAL_P(out_array)); + + zend_hash_internal_pointer_reset(new_hash); + Z_ARRVAL_P(out_array) = new_hash; + + return 0; +} +/* }}} */ + + +/* {{{ proto int mysqli_poll(array read, array write, array error, long sec [, long usec]) U + Poll connections */ +PHP_FUNCTION(mysqli_poll) +{ + zval *r_array, *e_array, *dont_poll_array; + MYSQLND **new_r_array = NULL, **new_e_array = NULL, **new_dont_poll_array = NULL; + long sec = 0, usec = 0; + enum_func_status ret; + uint desc_num; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a!a!al|l", &r_array, &e_array, &dont_poll_array, &sec, &usec) == FAILURE) { + return; + } + if (sec < 0 || usec < 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Negative values passed for sec and/or usec"); + RETURN_FALSE; + } + + if (r_array != NULL) { + mysqlnd_zval_array_to_mysqlnd_array(r_array, &new_r_array TSRMLS_CC); + } + if (e_array != NULL) { + mysqlnd_zval_array_to_mysqlnd_array(e_array, &new_e_array TSRMLS_CC); + } + + ret = mysqlnd_poll(new_r_array, new_e_array, &new_dont_poll_array, sec, usec, &desc_num); + + mysqlnd_dont_poll_zval_array_from_mysqlnd_array(r_array != NULL ? new_dont_poll_array:NULL, r_array, dont_poll_array TSRMLS_CC); + + if (r_array != NULL) { + mysqlnd_zval_array_from_mysqlnd_array(new_r_array, r_array TSRMLS_CC); + } + if (e_array != NULL) { + mysqlnd_zval_array_from_mysqlnd_array(new_e_array, e_array TSRMLS_CC); + } + + if (new_dont_poll_array) { + efree(new_dont_poll_array); + } + if (new_r_array) { + efree(new_r_array); + } + if (new_e_array) { + efree(new_e_array); + } + if (ret == PASS) { + RETURN_LONG(desc_num); + } else { + RETURN_FALSE; + } +} +/* }}} */ + + +/* {{{ proto int mysqli_reap_async_query(object link) U + Poll connections */ +PHP_FUNCTION(mysqli_reap_async_query) +{ + MY_MYSQL *mysql; + zval *mysql_link; + MYSQLI_RESOURCE *mysqli_resource; + MYSQL_RES *result; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { + return; + } + + MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL*, &mysql_link, "mysqli_link", MYSQLI_STATUS_VALID); + + if (FAIL == mysqlnd_reap_async_query(mysql->mysql)) { + RETURN_FALSE; + } + + if (!mysql_field_count(mysql->mysql)) { + /* no result set - not a SELECT */ + if (MyG(report_mode) & MYSQLI_REPORT_INDEX) { +/* php_mysqli_report_index("n/a", mysqli_server_status(mysql->mysql) TSRMLS_CC); */ + } + RETURN_TRUE; + } + + switch (mysql->async_result_fetch_type) { + case MYSQLI_STORE_RESULT: + result = mysql_store_result(mysql->mysql); + break; + case MYSQLI_USE_RESULT: + result = mysql_use_result(mysql->mysql); + break; +#if defined(MYSQLI_USE_MYSQLND) && defined(MYSQLND_THREADED) + case MYSQLI_BG_STORE_RESULT: + result = mysqli_bg_store_result(mysql->mysql); + break; +#endif + } + + if (!result) { + php_mysqli_throw_sql_exception((char *)mysql_sqlstate(mysql->mysql), mysql_errno(mysql->mysql) TSRMLS_CC, + "%s", mysql_error(mysql->mysql)); + RETURN_FALSE; + } + + if (MyG(report_mode) & MYSQLI_REPORT_INDEX) { +/* php_mysqli_report_index("n/a", mysqli_server_status(mysql->mysql) TSRMLS_CC);*/ + } + + mysqli_resource = (MYSQLI_RESOURCE *)ecalloc (1, sizeof(MYSQLI_RESOURCE)); + mysqli_resource->ptr = (void *)result; + mysqli_resource->status = MYSQLI_STATUS_VALID; + MYSQLI_RETURN_RESOURCE(mysqli_resource, mysqli_result_class_entry); +} +/* }}} */ + + /* {{{ proto object mysqli_stmt_get_result(object link) U Buffer result set on client */ PHP_FUNCTION(mysqli_stmt_get_result) diff --git a/ext/mysqli/mysqli_prop.c b/ext/mysqli/mysqli_prop.c index cba95c8609..f48b821068 100644 --- a/ext/mysqli/mysqli_prop.c +++ b/ext/mysqli/mysqli_prop.c @@ -187,18 +187,18 @@ static int link_affected_rows_read(mysqli_object *obj, zval **retval TSRMLS_DC) /* }}} */ /* link properties */ -MYSQLI_MAP_PROPERTY_FUNC_LONG(link_errno_read, mysql_errno, MYSQLI_GET_MYSQL(MYSQLI_STATUS_INITIALIZED), ulong, "%llu"); +MYSQLI_MAP_PROPERTY_FUNC_LONG(link_errno_read, mysql_errno, MYSQLI_GET_MYSQL(MYSQLI_STATUS_INITIALIZED), ulong, "%lu"); MYSQLI_MAP_PROPERTY_FUNC_STRING(link_error_read, mysql_error, MYSQLI_GET_MYSQL(MYSQLI_STATUS_INITIALIZED)); -MYSQLI_MAP_PROPERTY_FUNC_LONG(link_field_count_read, mysql_field_count, MYSQLI_GET_MYSQL(MYSQLI_STATUS_VALID), ulong, "%llu"); +MYSQLI_MAP_PROPERTY_FUNC_LONG(link_field_count_read, mysql_field_count, MYSQLI_GET_MYSQL(MYSQLI_STATUS_VALID), ulong, "%lu"); MYSQLI_MAP_PROPERTY_FUNC_STRING(link_host_info_read, mysql_get_host_info, MYSQLI_GET_MYSQL(MYSQLI_STATUS_VALID)); MYSQLI_MAP_PROPERTY_FUNC_STRING(link_info_read, mysql_info, MYSQLI_GET_MYSQL(MYSQLI_STATUS_VALID)); MYSQLI_MAP_PROPERTY_FUNC_LONG(link_insert_id_read, mysql_insert_id, MYSQLI_GET_MYSQL(MYSQLI_STATUS_VALID), my_ulonglong, MYSQLI_LLU_SPEC); -MYSQLI_MAP_PROPERTY_FUNC_LONG(link_protocol_version_read, mysql_get_proto_info, MYSQLI_GET_MYSQL(MYSQLI_STATUS_VALID), ulong, "%llu"); +MYSQLI_MAP_PROPERTY_FUNC_LONG(link_protocol_version_read, mysql_get_proto_info, MYSQLI_GET_MYSQL(MYSQLI_STATUS_VALID), ulong, "%lu"); MYSQLI_MAP_PROPERTY_FUNC_STRING(link_server_info_read, mysql_get_server_info, MYSQLI_GET_MYSQL(MYSQLI_STATUS_VALID)); -MYSQLI_MAP_PROPERTY_FUNC_LONG(link_server_version_read, mysql_get_server_version, MYSQLI_GET_MYSQL(MYSQLI_STATUS_VALID), ulong, "%llu"); +MYSQLI_MAP_PROPERTY_FUNC_LONG(link_server_version_read, mysql_get_server_version, MYSQLI_GET_MYSQL(MYSQLI_STATUS_VALID), ulong, "%lu"); MYSQLI_MAP_PROPERTY_FUNC_STRING(link_sqlstate_read, mysql_sqlstate, MYSQLI_GET_MYSQL(MYSQLI_STATUS_VALID)); -MYSQLI_MAP_PROPERTY_FUNC_LONG(link_thread_id_read, mysql_thread_id, MYSQLI_GET_MYSQL(MYSQLI_STATUS_VALID), ulong, "%llu"); -MYSQLI_MAP_PROPERTY_FUNC_LONG(link_warning_count_read, mysql_warning_count, MYSQLI_GET_MYSQL(MYSQLI_STATUS_VALID), ulong, "%llu"); +MYSQLI_MAP_PROPERTY_FUNC_LONG(link_thread_id_read, mysql_thread_id, MYSQLI_GET_MYSQL(MYSQLI_STATUS_VALID), ulong, "%lu"); +MYSQLI_MAP_PROPERTY_FUNC_LONG(link_warning_count_read, mysql_warning_count, MYSQLI_GET_MYSQL(MYSQLI_STATUS_VALID), ulong, "%lu"); /* result properties */ /* {{{ property result_type_read */ @@ -246,8 +246,8 @@ static int result_lengths_read(mysqli_object *obj, zval **retval TSRMLS_DC) /* }}} */ -MYSQLI_MAP_PROPERTY_FUNC_LONG(result_current_field_read, mysql_field_tell, MYSQLI_GET_RESULT(MYSQLI_STATUS_VALID), ulong, "%llu"); -MYSQLI_MAP_PROPERTY_FUNC_LONG(result_field_count_read, mysql_num_fields, MYSQLI_GET_RESULT(MYSQLI_STATUS_VALID), ulong, "%llu"); +MYSQLI_MAP_PROPERTY_FUNC_LONG(result_current_field_read, mysql_field_tell, MYSQLI_GET_RESULT(MYSQLI_STATUS_VALID), ulong, "%lu"); +MYSQLI_MAP_PROPERTY_FUNC_LONG(result_field_count_read, mysql_num_fields, MYSQLI_GET_RESULT(MYSQLI_STATUS_VALID), ulong, "%lu"); MYSQLI_MAP_PROPERTY_FUNC_LONG(result_num_rows_read, mysql_num_rows, MYSQLI_GET_RESULT(MYSQLI_STATUS_VALID), my_ulonglong, MYSQLI_LLU_SPEC); /* statement properties */ @@ -296,7 +296,7 @@ static int stmt_affected_rows_read(mysqli_object *obj, zval **retval TSRMLS_DC) ZVAL_LONG(*retval, rc); } else { char *ret; - int l = spprintf(&ret, 0, MYSQLI_LLU_SPEC, (my_ulonglong) rc); + int l = spprintf(&ret, 0, MYSQLI_LLU_SPEC, rc); ZVAL_STRINGL(*retval, ret, l, 0); } } @@ -306,9 +306,9 @@ static int stmt_affected_rows_read(mysqli_object *obj, zval **retval TSRMLS_DC) MYSQLI_MAP_PROPERTY_FUNC_LONG(stmt_insert_id_read, mysql_stmt_insert_id, MYSQLI_GET_STMT(MYSQLI_STATUS_VALID), my_ulonglong, MYSQLI_LLU_SPEC); MYSQLI_MAP_PROPERTY_FUNC_LONG(stmt_num_rows_read, mysql_stmt_num_rows, MYSQLI_GET_STMT(MYSQLI_STATUS_VALID), my_ulonglong, MYSQLI_LLU_SPEC); -MYSQLI_MAP_PROPERTY_FUNC_LONG(stmt_param_count_read, mysql_stmt_param_count, MYSQLI_GET_STMT(MYSQLI_STATUS_VALID), ulong, "%llu"); -MYSQLI_MAP_PROPERTY_FUNC_LONG(stmt_field_count_read, mysql_stmt_field_count, MYSQLI_GET_STMT(MYSQLI_STATUS_VALID), ulong, "%llu"); -MYSQLI_MAP_PROPERTY_FUNC_LONG(stmt_errno_read, mysql_stmt_errno, MYSQLI_GET_STMT(MYSQLI_STATUS_INITIALIZED), ulong, "%llu"); +MYSQLI_MAP_PROPERTY_FUNC_LONG(stmt_param_count_read, mysql_stmt_param_count, MYSQLI_GET_STMT(MYSQLI_STATUS_VALID), ulong, "%lu"); +MYSQLI_MAP_PROPERTY_FUNC_LONG(stmt_field_count_read, mysql_stmt_field_count, MYSQLI_GET_STMT(MYSQLI_STATUS_VALID), ulong, "%lu"); +MYSQLI_MAP_PROPERTY_FUNC_LONG(stmt_errno_read, mysql_stmt_errno, MYSQLI_GET_STMT(MYSQLI_STATUS_INITIALIZED), ulong, "%lu"); MYSQLI_MAP_PROPERTY_FUNC_STRING(stmt_error_read, mysql_stmt_error, MYSQLI_GET_STMT(MYSQLI_STATUS_INITIALIZED)); MYSQLI_MAP_PROPERTY_FUNC_STRING(stmt_sqlstate_read, mysql_stmt_sqlstate, MYSQLI_GET_STMT(MYSQLI_STATUS_INITIALIZED)); diff --git a/ext/mysqli/php_mysqli_structs.h b/ext/mysqli/php_mysqli_structs.h index bd5d5c8849..f785745a4e 100644 --- a/ext/mysqli/php_mysqli_structs.h +++ b/ext/mysqli/php_mysqli_structs.h @@ -108,9 +108,12 @@ typedef struct { char *hash_key; zval *li_read; php_stream *li_stream; - zend_bool persistent; unsigned int multi_query; UConverter *conv; + zend_bool persistent; +#if defined(MYSQLI_USE_MYSQLND) + int async_result_fetch_type; +#endif } MY_MYSQL; typedef struct { @@ -312,8 +315,12 @@ PHP_MYSQLI_EXPORT(zend_object_value) mysqli_objects_new(zend_class_entry * TSRML #define MYSQLI_USE_RESULT 1 #ifdef MYSQLI_USE_MYSQLND #ifdef MYSQLND_THREADED -#define MYSQLI_BG_STORE_RESULT 101 +#define MYSQLI_BG_STORE_RESULT 4 #endif +#define MYSQLI_ASYNC 8 +#else +/* libmysql */ +#define MYSQLI_ASYNC 0 #endif /* for mysqli_fetch_assoc */ @@ -447,6 +454,7 @@ PHP_FUNCTION(mysqli_num_fields); PHP_FUNCTION(mysqli_num_rows); PHP_FUNCTION(mysqli_options); PHP_FUNCTION(mysqli_ping); +PHP_FUNCTION(mysqli_poll); PHP_FUNCTION(mysqli_prepare); PHP_FUNCTION(mysqli_query); PHP_FUNCTION(mysqli_stmt_result_metadata); @@ -455,6 +463,7 @@ PHP_FUNCTION(mysqli_read_query_result); PHP_FUNCTION(mysqli_real_connect); PHP_FUNCTION(mysqli_real_query); PHP_FUNCTION(mysqli_real_escape_string); +PHP_FUNCTION(mysqli_reap_async_query); PHP_FUNCTION(mysqli_rollback); PHP_FUNCTION(mysqli_row_seek); PHP_FUNCTION(mysqli_select_db); diff --git a/ext/mysqli/tests/057.phpt b/ext/mysqli/tests/057.phpt index a6a9cb6f97..0efea3569c 100644 --- a/ext/mysqli/tests/057.phpt +++ b/ext/mysqli/tests/057.phpt @@ -1,8 +1,8 @@ --TEST-- mysqli_get_metadata --SKIPIF-- - --FILE-- diff --git a/ext/mysqli/tests/bug37090.phpt b/ext/mysqli/tests/bug37090.phpt index 651ba52b5b..003ace9d11 100644 --- a/ext/mysqli/tests/bug37090.phpt +++ b/ext/mysqli/tests/bug37090.phpt @@ -18,7 +18,7 @@ if (ini_get('unicode.semantics')) { $mysql = new mysqli($host, $user, $passwd, $db, $port, $socket); $cs = array(); - $cs[] = $mysql->set_charset("latin5"); + $cs[] = $mysql->set_charset("latin1"); $cs[] = $mysql->character_set_name(); $cs[] = $mysql->set_charset("utf8"); @@ -35,7 +35,7 @@ array(6) { [0]=> bool(true) [1]=> - string(6) "latin5" + string(6) "latin1" [2]=> bool(true) [3]=> @@ -45,4 +45,4 @@ array(6) { [5]=> string(4) "utf8" } -done! \ No newline at end of file +done! diff --git a/ext/mysqli/tests/mysqli_autocommit.phpt b/ext/mysqli/tests/mysqli_autocommit.phpt index 1bffb53ca7..f409baadc0 100644 --- a/ext/mysqli/tests/mysqli_autocommit.phpt +++ b/ext/mysqli/tests/mysqli_autocommit.phpt @@ -2,9 +2,9 @@ mysqli_autocommit() --SKIPIF-- --EXPECTF-- -done! \ No newline at end of file +done! diff --git a/ext/mysqli/tests/mysqli_character_set.phpt b/ext/mysqli/tests/mysqli_character_set.phpt index 4c469b0281..2e5d907d4c 100644 --- a/ext/mysqli/tests/mysqli_character_set.phpt +++ b/ext/mysqli/tests/mysqli_character_set.phpt @@ -2,9 +2,9 @@ Fetching results from tables of different charsets. --SKIPIF-- --FILE-- diff --git a/ext/mysqli/tests/mysqli_class_mysqli_interface.phpt b/ext/mysqli/tests/mysqli_class_mysqli_interface.phpt index 84f39fa8a4..30ed9d074a 100644 --- a/ext/mysqli/tests/mysqli_class_mysqli_interface.phpt +++ b/ext/mysqli/tests/mysqli_class_mysqli_interface.phpt @@ -1,7 +1,7 @@ --TEST-- Interface of the class mysqli --SKIPIF-- - --EXPECTF-- Warning: mysqli_get_charset(): Couldn't fetch mysqli in %s on line %d -done! \ No newline at end of file +done! diff --git a/ext/mysqli/tests/mysqli_poll.phpt b/ext/mysqli/tests/mysqli_poll.phpt new file mode 100644 index 0000000000..fc0980544f --- /dev/null +++ b/ext/mysqli/tests/mysqli_poll.phpt @@ -0,0 +1,124 @@ +--TEST-- +int mysqli_poll() simple +--SKIPIF-- + +--FILE-- + 0) { + printf("[%03d + 3] Error indicated through links array: %d/%s", + $offset, mysqli_errno($mysqli), mysqli_error($mysqli)); + } else { + printf("[%03d + 4] Cannot fetch and no error set - non resultset query (no SELECT)!\n", $offset); + } + } + + foreach ($errors as $mysqli) + printf("[%03d + 5] Error on %d: %d/%s\n", + $offset, mysqli_thread_id($mysqli), mysqli_errno($mysqli), mysqli_error($mysqli)); + + foreach ($reject as $mysqli) + printf("[%03d + 6] Rejecting thread %d: %d/%s\n", + $offset, mysqli_thread_id($mysqli), mysqli_errno($mysqli), mysqli_error($mysqli)); + + } + + // Connections on which no query has been send - 1 + $link = get_connection(); + $links = array($link); + $errors = array($link); + $reject = array($link); + poll_async(10, $links, $errors, $reject, 0); + mysqli_close($link); + + // Connections on which no query has been send - 2 + // Difference: pass $links twice + $link = get_connection(); + $links = array($link, $link); + $errors = array($link, $link); + $reject = array(); + poll_async(11, $links, $errors, $reject, 0); + + // Connections on which no query has been send - 3 + // Difference: pass two connections + $link = get_connection(); + $links = array($link, get_connection()); + $errors = array($link, $link); + $reject = array(); + poll_async(12, $links, $errors, $reject, 0); + + // Reference mess... + $link = get_connection(); + $links = array($link); + $errors = array($link); + $ref_errors =& $errors; + $reject = array(); + poll_async(13, $links, $ref_errors, $reject, 0); + + print "done!"; +?> +--EXPECTF-- +[010 + 6] Rejecting thread %d: 0/ +[011 + 6] Rejecting thread %d: 0/ +[011 + 6] Rejecting thread %d: 0/ +[012 + 6] Rejecting thread %d: 0/ +[012 + 6] Rejecting thread %d: 0/ +[013 + 6] Rejecting thread %d: 0/ +done! diff --git a/ext/mysqli/tests/mysqli_poll_kill.phpt b/ext/mysqli/tests/mysqli_poll_kill.phpt new file mode 100644 index 0000000000..5a5b05fdc7 --- /dev/null +++ b/ext/mysqli/tests/mysqli_poll_kill.phpt @@ -0,0 +1,197 @@ +--TEST-- +int mysqli_poll() and kill +--SKIPIF-- + +--FILE-- +errno > 0) { + printf("[005] Error: %d\n", $link->errno); + } + } + + // No error! + if (!is_array($errors) || !empty($errors)) + printf("[006] Expecting non-empty array got %s/%s\n", gettype($errors), var_export($errors, true)); + + if (!is_array($reject) || !empty($reject)) + printf("[007] Expecting empty array got %s/%s\n", gettype($reject), var_export($reject, true)); + + // Lets pass a dead connection + $links = array($link); + $errors = array($link); + $reject = array($link); + if (0 !== ($tmp = mysqli_poll($links, $errors, $reject, 1))) + printf("[008] There should be no connection ready! Returned %s/%s, expecting int/0.\n", + gettype($tmp), var_export($tmp, true)); + + if (!empty($errors)) + printf("[009] There should be no errors but one rejected connection\n"); + + foreach ($reject as $mysqli) + if (mysqli_thread_id($mysqli) != $thread_id) { + printf("[010] Rejected thread %d should have rejected thread %d\n", + mysqli_thread_id($mysqli), $thread_id); + } + + // Killing connection - 2 + + $link = get_connection(); + if (true !== ($tmp = mysqli_query($link, "SELECT 1", MYSQLI_ASYNC | MYSQLI_USE_RESULT))) + printf("[011] Expecting boolean/true got %s/%s\n", gettype($tmp), var_export($tmp, true)); + + usleep(100000); + $thread_id = mysqli_thread_id($link); + mysqli_kill(get_connection(), $thread_id); + + // Yes, 1 - fetch OK packet of kill! + $processed = 0; + do { + $links = array($link, $link); + $errors = array($link, $link); + $reject = array($link, $link); + $ready = mysqli_poll($links, $errors, $reject, 1); + + if (!empty($errors)) { + foreach ($errors as $mysqli) { + printf("[012] Error on thread %d: %s/%s\n", + mysqli_thread_id($mysqli), + mysqli_errno($mysqli), + mysqli_error($mysqli)); + } + break; + } + + if (!empty($reject)) { + foreach ($reject as $mysqli) { + printf("[013] Rejecting thread %d: %s/%s\n", + mysqli_thread_id($mysqli), + mysqli_errno($mysqli), + mysqli_error($mysqli)); + } + $processed += count($reject); + } + + foreach ($links as $mysqli) { + if (is_object($res = mysqli_reap_async_query($mysqli))) { + printf("Fetching from thread %d...\n", mysqli_thread_id($mysqli)); + var_dump(mysqli_fetch_assoc($res)); + } else if (mysqli_errno($mysqli) > 0) { + printf("[014] %d/%s\n", mysqli_errno($mysqli), mysqli_error($mysqli)); + } + $processed++; + } + + } while ($processed < 2); + + + // Killing connection - 3 + + $link = get_connection(); + $thread_id = mysqli_thread_id($link); + mysqli_kill(get_connection(), $thread_id); + // Sleep 0.1s to ensure the KILL gets recognized + usleep(100000); + if (false !== ($tmp = mysqli_query($link, "SELECT 1 AS 'processed beofre killed'", MYSQLI_ASYNC | MYSQLI_USE_RESULT))) + printf("[015] Expecting boolean/false got %s/%s\n", gettype($tmp), var_export($tmp, true)); + + $links = array($link); + $errors = array($link); + $reject = array($link); + + // Yes, that is weird, right? Its the OK package we have to fetch + if (1 !== ($tmp = (mysqli_poll($links, $errors, $reject, 0, 10000)))) + printf("[016] Expecting int/0 got %s/%s\n", gettype($tmp), var_export($tmp, true)); + + if (!is_array($links) || empty($links)) + printf("[017] Expecting non-empty array got %s/%s\n", gettype($links), var_export($links, true)); + else + foreach ($links as $link) { + if (is_object($res = mysqli_reap_async_query($link))) { + // No, you cannot fetch the result + var_dump(mysqli_fetch_assoc($res)); + mysqli_free_result($res); + } else if ($link->errno > 0) { + // But you are supposed to handle the error the way its shown here! + printf("[018] Error: %d/%s\n", $link->errno, $link->error); + } + } + + // None of these will indicate an error, check errno on the list of returned connections! + if (!is_array($errors) || !empty($errors)) + printf("[019] Expecting non-empty array got %s/%s\n", gettype($errors), var_export($errors, true)); + + if (!is_array($reject) || !empty($reject)) + printf("[020] Expecting empty array got %s/%s\n", gettype($reject), var_export($reject, true)); + + + mysqli_close($link); + print "done!"; +?> +--EXPECTF-- +array(1) { + ["processed beofre killed"]=> + string(1) "1" +} +Fetching from thread %d... +array(1) { + [1]=> + string(1) "1" +} + +Warning: mysqli_reap_async_query(): GREET %s + +Warning: mysqli_reap_async_query(): MySQL server has gone away in %s on line %d +[014] 2014/%s + +Warning: Error while sending QUERY packet. PID=%d in %s on line %d + +Warning: mysqli_reap_async_query(): MySQL server has gone away in %s on line %d + +Warning: mysqli_reap_async_query(): Error reading result set's header in %s on line %d +[018] Error: 2006/%s +done! diff --git a/ext/mysqli/tests/mysqli_poll_mixing_insert_select.phpt b/ext/mysqli/tests/mysqli_poll_mixing_insert_select.phpt new file mode 100644 index 0000000000..ea063ee04c --- /dev/null +++ b/ext/mysqli/tests/mysqli_poll_mixing_insert_select.phpt @@ -0,0 +1,167 @@ +--TEST-- +mysqli_poll() & INSERT SELECT +--SKIPIF-- + +--FILE-- + 3', + 'UPDATE_FIX test SET id = 101 WHERE id > 3', + 'DROP TABLE IF EXISTS bogus', + 'DELETE FROM test WHERE id = @a', + 'DELETE FROM test WHERE id = 1', + ); + + $link = get_connection(); + $have_proc = false; + mysqli_real_query($link, "DROP PROCEDURE IF EXISTS p"); + if (mysqli_real_query($link, 'CREATE PROCEDURE p(IN ver_in VARCHAR(25), OUT ver_out VARCHAR(25)) BEGIN SELECT ver_in INTO ver_out; END;')) { + $have_proc = true; + $queries[] = 'CALL p("myversion", @version)'; + } + mysqli_close($link); + + $links = array(); + for ($i = 0; $i < count($queries); $i++) { + + $link = get_connection(); + + if (true !== ($tmp = mysqli_query($link, $queries[$i], MYSQLI_ASYNC | MYSQLI_USE_RESULT))) + printf("[002] Expecting true got %s/%s\n", gettype($tmp), var_export($tmp, true)); + + // WARNING KLUDGE NOTE + // Add a tiny delay to ensure that queries get executed in a certain order + // If your MySQL server is very slow the test may randomly fail! + usleep(20000); + + $links[mysqli_thread_id($link)] = array( + 'query' => $queries[$i], + 'link' => $link, + 'processed' => false, + ); + } + + $saved_errors = array(); + do { + $poll_links = $poll_errors = $poll_reject = array(); + foreach ($links as $thread_id => $link) { + if (!$link['processed']) { + $poll_links[] = $link['link']; + $poll_errors[] = $link['link']; + $poll_reject[] = $link['link']; + } + } + if (0 == count($poll_links)) + break; + + if (0 == ($num_ready = mysqli_poll($poll_links, $poll_errors, $poll_reject, 0, 200000))) + continue; + + if (!empty($poll_errors)) { + die(var_dump($poll_errors)); + } + + foreach ($poll_links as $link) { + $thread_id = mysqli_thread_id($link); + $links[$thread_id]['processed'] = true; + + if (is_object($res = mysqli_reap_async_query($link))) { + // result set object + while ($row = mysqli_fetch_assoc($res)) { + // eat up all results + ; + } + mysqli_free_result($res); + } else { + // either there is no result (no SELECT) or there is an error + if (mysqli_errno($link) > 0) { + $saved_errors[$thread_id] = mysqli_errno($link); + printf("[003] '%s' caused %d\n", $links[$thread_id]['query'], mysqli_errno($link)); + } + } + } + + } while (true); + + // Checking if all lines are still usable + foreach ($links as $thread_id => $link) { + if (isset($saved_errors[$thread_id]) && + $saved_errors[$thread_id] != mysqli_errno($link['link'])) { + printf("[004] Error state not saved for query '%s', %d != %d\n", $link['query'], + $saved_errors[$thread_id], mysqli_errno($link['link'])); + } + + if (!$res = mysqli_query($link['link'], 'SELECT * FROM test WHERE id = 100')) + printf("[005] Expecting true got %s/%s\n", gettype($tmp), var_export($tmp, true)); + if (!$row = mysqli_fetch_row($res)) + printf("[006] Expecting true got %s/%s\n", gettype($tmp), var_export($tmp, true)); + + mysqli_free_result($res); + } + + if ($res = mysqli_query($link['link'], "SELECT * FROM test WHERE id = 100")) { + $row = mysqli_fetch_assoc($res); + var_dump($row); + mysqli_free_result($res); + } + + if ($have_proc && ($res = mysqli_query($link['link'], "SELECT @version as _version"))) { + $row = mysqli_fetch_assoc($res); + if ($row['_version'] != 'myversion') { + printf("[007] Check procedures\n"); + } + mysqli_free_result($res); + } + + foreach ($links as $link) + mysqli_close($link['link']); + + $link = get_connection(); + if (!mysqli_query($link, 'SELECT 1', MYSQLI_ASYNC)) + printf("[008] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + if (!mysqli_query($link, 'SELECT 1', MYSQLI_ASYNC)) + printf("[009] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + mysqli_close($link); + + print "done!"; +?> +--EXPECTF-- +[003] 'SELECT' caused 1064 +[003] 'UPDATE test SET id = 101 WHERE id > 3' caused 1062 +[003] 'UPDATE_FIX test SET id = 101 WHERE id > 3' caused 1064 +array(2) { + ["id"]=> + string(3) "100" + ["label"]=> + string(1) "z" +} +[009] [2014] %s +done! diff --git a/ext/mysqli/tests/mysqli_poll_reference.phpt b/ext/mysqli/tests/mysqli_poll_reference.phpt new file mode 100644 index 0000000000..846dff0c72 --- /dev/null +++ b/ext/mysqli/tests/mysqli_poll_reference.phpt @@ -0,0 +1,220 @@ +--TEST-- +mysqli_poll() & references +--SKIPIF-- + +--FILE-- + 10) { + printf("[002] The queries should have finished already\n"); + break; + } + // WARNING: All arrays point to the same object - this will give bogus results! + // The behaviour is in line with stream_select(). Be warned, be careful. + $links = $errors = $reject = array($mysqli1, $mysqli2); + if (0 == ($ready = mysqli_poll($links, $errors, $reject, 0, 50000))) { + continue; + } + + foreach ($links as $link) { + if ($res = mysqli_reap_async_query($link)) { + mysqli_free_result($res); + } + $processed++; + } + } while ($processed < 2); + + mysqli_close($mysqli1); + mysqli_close($mysqli2); + + $mysqli1 = get_connection(); + $mysqli2 = get_connection(); + + var_dump(mysqli_query($mysqli1, "SELECT SLEEP(0.10)", MYSQLI_ASYNC | MYSQLI_USE_RESULT)); + var_dump(mysqli_query($mysqli2, "SELECT SLEEP(0.20)", MYSQLI_ASYNC | MYSQLI_USE_RESULT)); + + $processed = $loops = 0; + do { + $loops++; + if ($loops > 10) { + printf("[003] The queries should have finished already\n"); + break; + } + // WARNING: All arrays point to the same object - this will give bogus results! + $links = $errors = array($mysqli1, $mysqli2); + $reject = array($mysqli1, $mysqli2); + if (0 == ($ready = mysqli_poll($links, $errors, $reject, 0, 50000))) { + continue; + } + foreach ($links as $link) { + if ($res = mysqli_reap_async_query($link)) { + mysqli_free_result($res); + } + $processed++; + } + } while ($processed < 2); + + mysqli_close($mysqli1); + mysqli_close($mysqli2); + + $mysqli1 = get_connection(); + $mysqli2 = get_connection(); + + var_dump(mysqli_query($mysqli1, "SELECT SLEEP(0.10)", MYSQLI_ASYNC | MYSQLI_USE_RESULT)); + var_dump(mysqli_query($mysqli2, "SELECT SLEEP(0.20)", MYSQLI_ASYNC | MYSQLI_USE_RESULT)); + + $processed = $loops = 0; + do { + $loops++; + if ($loops > 10) { + printf("[004] The queries should have finished already\n"); + break; + } + // WARNING: All arrays point to the same object - this will give bogus results! + $links = array($mysqli1, $mysqli2); + $errors = $reject = array($mysqli1, $mysqli2); + if (0 == ($ready = mysqli_poll($links, $errors, $reject, 0, 50000))) { + continue; + } + foreach ($links as $link) { + if ($res = mysqli_reap_async_query($link)) { + mysqli_free_result($res); + } + $processed++; + } + } while ($processed < 2); + + mysqli_close($mysqli1); + mysqli_close($mysqli2); + + // This is bogus code and bogus usage - OK to throw no errors! + $mysqli1 = get_connection(); + $mysqli2 = get_connection(); + + var_dump(mysqli_query($mysqli1, "SELECT SLEEP(0.10)", MYSQLI_ASYNC | MYSQLI_USE_RESULT)); + $thread_id = mysqli_thread_id($mysqli2); + printf("Connection %d should be rejected...\n", $thread_id); + + $processed = $loops = 0; + do { + $loops++; + if ($loops > 10) { + printf("[005] The queries should have finished already\n"); + break; + } + $links = $errors = $reject = array($mysqli1, $mysqli2); + if (0 == ($ready = mysqli_poll($links, $errors, $reject, 0, 50000))) { + continue; + } + // WARNING: Due to the reference issue none of these should ever fire! + foreach ($reject as $links) { + printf("Connection %d was rejected...\n", mysqli_thread_id($link)); + $processed++; + } + foreach ($errors as $links) { + printf("Connection %d has an error...\n", mysqli_thread_id($link)); + $processed++; + } + foreach ($links as $link) { + if ($res = mysqli_reap_async_query($link)) { + mysqli_free_result($res); + $processed++; + } + } + } while ($processed < 2); + + mysqli_close($mysqli1); + mysqli_close($mysqli2); + + $mysqli1 = get_connection(); + $mysqli2 = get_connection(); + + var_dump(mysqli_query($mysqli1, "SELECT SLEEP(0.10)", MYSQLI_ASYNC | MYSQLI_USE_RESULT)); + var_dump(mysqli_query($mysqli2, "SELECT SLEEP(0.20)", MYSQLI_ASYNC | MYSQLI_USE_RESULT)); + + $processed = $loops = 0; + $all = array($mysqli1, $mysqli2); + do { + $loops++; + if ($loops > 10) { + printf("[006] The queries should have finished already\n"); + break; + } + $links = $errors = $reject = $all; + ob_start(); + if (0 == ($ready = mysqli_poll($links, $errors, $reject, 0, 50000))) { + $tmp = ob_get_contents(); + ob_end_clean(); + if ($tmp != '') { + printf("Expected error:\n%s\n", $tmp); + break; + } + continue; + } + foreach ($links as $link) { + if ($res = mysqli_reap_async_query($link)) { + mysqli_free_result($res); + } + $processed++; + } + } while ($processed < 2); + + mysqli_close($mysqli1); + mysqli_close($mysqli2); + + print "done!"; +?> +--EXPECTF-- +bool(true) +bool(true) +[002] The queries should have finished already +bool(true) +bool(true) +[003] The queries should have finished already +bool(true) +bool(true) +bool(true) +Connection %d should be rejected... +[005] The queries should have finished already +bool(true) +bool(true) +Expected error: + +Warning: mysqli_poll(): No stream arrays were passed in %s on line %d + +done! diff --git a/ext/mysqlnd/mysqlnd.c b/ext/mysqlnd/mysqlnd.c index 9506c24abc..a530d8f420 100644 --- a/ext/mysqlnd/mysqlnd.c +++ b/ext/mysqlnd/mysqlnd.c @@ -870,6 +870,217 @@ MYSQLND_METHOD(mysqlnd_conn, query)(MYSQLND *conn, const char *query, unsigned i /* }}} */ +/* {{{ mysqlnd_conn::send_query */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn, send_query)(MYSQLND *conn, const char *query, unsigned int query_len TSRMLS_DC) +{ + enum_func_status ret; + DBG_ENTER("mysqlnd_conn::send_query"); + DBG_INF_FMT("conn=%llu query=%s", conn->thread_id, query); + + ret = mysqlnd_simple_command(conn, COM_QUERY, query, query_len, + PROT_LAST /* we will handle the OK packet*/, + FALSE, FALSE TSRMLS_CC); + CONN_SET_STATE(conn, CONN_QUERY_SENT); + DBG_RETURN(ret); +} +/* }}} */ + +/* {{{ mysqlnd_conn::send_query */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn, reap_query)(MYSQLND * conn TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_conn::reap_query"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + enum_mysqlnd_connection_state state = CONN_GET_STATE(conn); + if (state <= CONN_READY || state == CONN_QUIT_SENT) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Connection not opened, clear or has been closed"); + DBG_RETURN(FAIL); + } + DBG_RETURN(mysqlnd_query_read_result_set_header(conn, NULL TSRMLS_CC)); +} +/* }}} */ + + +#include "php_network.h" + +MYSQLND ** mysqlnd_stream_array_check_for_readiness(MYSQLND ** conn_array TSRMLS_DC) +{ + int cnt = 0; + MYSQLND **p = conn_array, **p_p; + MYSQLND **ret = NULL; + + while (*p) { + if (CONN_GET_STATE(*p) <= CONN_READY || CONN_GET_STATE(*p) == CONN_QUIT_SENT) { + cnt++; + } + p++; + } + if (cnt) { + MYSQLND **ret_p = ret = ecalloc(cnt + 1, sizeof(MYSQLND *)); + p_p = p = conn_array; + while (*p) { + if (CONN_GET_STATE(*p) <= CONN_READY || CONN_GET_STATE(*p) == CONN_QUIT_SENT) { + *ret_p = *p; + *p = NULL; + ret_p++; + } else { + *p_p = *p; + p_p++; + } + p++; + } + *ret_p = NULL; + } + return ret; +} + + +/* {{{ stream_select mysqlnd_stream_array_to_fd_set functions */ +static int mysqlnd_stream_array_to_fd_set(MYSQLND **conn_array, fd_set *fds, php_socket_t *max_fd TSRMLS_DC) +{ + php_socket_t this_fd; + int cnt = 0; + MYSQLND **p = conn_array; + + while (*p) { + /* get the fd. + * NB: Most other code will NOT use the PHP_STREAM_CAST_INTERNAL flag + * when casting. It is only used here so that the buffered data warning + * is not displayed. + * */ + if (SUCCESS == php_stream_cast((*p)->net.stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL, + (void*)&this_fd, 1) && this_fd >= 0) { + + PHP_SAFE_FD_SET(this_fd, fds); + + if (this_fd > *max_fd) { + *max_fd = this_fd; + } + cnt++; + } + p++; + } + return cnt ? 1 : 0; +} + +static int mysqlnd_stream_array_from_fd_set(MYSQLND **conn_array, fd_set *fds TSRMLS_DC) +{ + php_socket_t this_fd; + int ret = 0; + zend_bool disproportion = FALSE; + + + MYSQLND **fwd = conn_array, **bckwd = conn_array; + + while (*fwd) { + if (SUCCESS == php_stream_cast((*fwd)->net.stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL, + (void*)&this_fd, 1) && this_fd >= 0) { + if (PHP_SAFE_FD_ISSET(this_fd, fds)) { + if (disproportion) { + *bckwd = *fwd; + } + bckwd++; + fwd++; + ret++; + continue; + } + } + disproportion = TRUE; + fwd++; + } + *bckwd = NULL;/* NULL-terminate the list */ + + return ret; +} + + +#ifndef PHP_WIN32 +#define php_select(m, r, w, e, t) select(m, r, w, e, t) +#else +#include "win32/select.h" +#endif + +/* {{{ _mysqlnd_poll */ +enum_func_status +_mysqlnd_poll(MYSQLND **r_array, MYSQLND **e_array, MYSQLND ***dont_poll, long sec, long usec, uint * desc_num TSRMLS_DC) +{ + + struct timeval tv; + struct timeval *tv_p = NULL; + fd_set rfds, wfds, efds; + php_socket_t max_fd = 0; + int retval, sets = 0; + int set_count, max_set_count = 0; + DBG_ENTER("mysqlnd_poll"); + + if (sec < 0 || usec < 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Negative values passed for sec and/or usec"); + DBG_RETURN(FAIL); + } + + *dont_poll = mysqlnd_stream_array_check_for_readiness(r_array TSRMLS_CC); + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&efds); + + if (r_array != NULL) { + set_count = mysqlnd_stream_array_to_fd_set(r_array, &rfds, &max_fd TSRMLS_CC); + if (set_count > max_set_count) { + max_set_count = set_count; + } + sets += set_count; + } + + if (e_array != NULL) { + set_count = mysqlnd_stream_array_to_fd_set(e_array, &efds, &max_fd TSRMLS_CC); + if (set_count > max_set_count) { + max_set_count = set_count; + } + sets += set_count; + } + + if (!sets) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, *dont_poll ? "All arrays passed are clear":"No stream arrays were passed"); + DBG_RETURN(FAIL); + } + + PHP_SAFE_MAX_FD(max_fd, max_set_count); + + /* Solaris + BSD do not like microsecond values which are >= 1 sec */ + if (usec > 999999) { + tv.tv_sec = sec + (usec / 1000000); + tv.tv_usec = usec % 1000000; + } else { + tv.tv_sec = sec; + tv.tv_usec = usec; + } + + tv_p = &tv; + + retval = php_select(max_fd + 1, &rfds, &wfds, &efds, tv_p); + + if (retval == -1) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to select [%d]: %s (max_fd=%d)", + errno, strerror(errno), max_fd); + DBG_RETURN(FAIL); + } + + if (r_array != NULL) { + mysqlnd_stream_array_from_fd_set(r_array, &rfds TSRMLS_CC); + } + if (e_array != NULL) { + mysqlnd_stream_array_from_fd_set(e_array, &efds TSRMLS_CC); + } + + *desc_num = retval; + + DBG_RETURN(PASS); +} +/* }}} */ + + /* COM_FIELD_LIST is special, different from a SHOW FIELDS FROM : - There is no result set header - status from the command, which @@ -1910,6 +2121,8 @@ MYSQLND_CLASS_METHODS_START(mysqlnd_conn) MYSQLND_METHOD(mysqlnd_conn, escape_string), MYSQLND_METHOD(mysqlnd_conn, set_charset), MYSQLND_METHOD(mysqlnd_conn, query), + MYSQLND_METHOD(mysqlnd_conn, send_query), + MYSQLND_METHOD(mysqlnd_conn, reap_query), MYSQLND_METHOD(mysqlnd_conn, use_result), MYSQLND_METHOD(mysqlnd_conn, store_result), MYSQLND_METHOD(mysqlnd_conn, background_store_result), diff --git a/ext/mysqlnd/mysqlnd.h b/ext/mysqlnd/mysqlnd.h index 39de34f695..9f34fe0cc2 100644 --- a/ext/mysqlnd/mysqlnd.h +++ b/ext/mysqlnd/mysqlnd.h @@ -106,8 +106,12 @@ PHPAPI void _mysqlnd_debug(const char *mode TSRMLS_DC); #define mysqlnd_close(conn,is_forced) (conn)->m->close((conn), (is_forced) TSRMLS_CC) #define mysqlnd_query(conn, query_str, query_len) (conn)->m->query((conn), (query_str), (query_len) TSRMLS_CC) +#define mysqlnd_async_query(conn, query_str, query_len) (conn)->m->send_query((conn), (query_str), (query_len) TSRMLS_CC) +#define mysqlnd_poll(r, err, d_pull,sec,usec,desc_num) _mysqlnd_poll((r), (err), (d_pull), (sec), (usec), (desc_num) TSRMLS_CC) +#define mysqlnd_reap_async_query(conn) (conn)->m->reap_query((conn) TSRMLS_CC) #define mysqlnd_unbuffered_skip_result(result) (result)->m.skip_result((result) TSRMLS_CC) +enum_func_status _mysqlnd_poll(MYSQLND **r_array, MYSQLND **e_array, MYSQLND ***dont_poll, long sec, long usec, uint * desc_num TSRMLS_DC); #define mysqlnd_use_result(conn) (conn)->m->use_result((conn) TSRMLS_CC) #define mysqlnd_store_result(conn) (conn)->m->store_result((conn) TSRMLS_CC) diff --git a/ext/mysqlnd/mysqlnd_portability.h b/ext/mysqlnd/mysqlnd_portability.h index a7c3563c9c..b2334182b9 100644 --- a/ext/mysqlnd/mysqlnd_portability.h +++ b/ext/mysqlnd/mysqlnd_portability.h @@ -163,8 +163,42 @@ typedef unsigned long long uint64_t; #define L64(x) x##i64 #endif #else + +#if __i386__ +#define MYSQLND_LL_SPEC "%lli" #define MYSQLND_LLU_SPEC "%llu" -#define MYSQLND_LL_SPEC "%lld" +#endif + +#if __ia64__ +#define MYSQLND_LL_SPEC "%li" +#define MYSQLND_LLU_SPEC "%lu" +#endif + +#if __powerpc64__ +#define MYSQLND_LL_SPEC "%li" +#define MYSQLND_LLU_SPEC "%lu" +#endif + +#if __x86_64__ +#define MYSQLND_LL_SPEC "%li" +#define MYSQLND_LLU_SPEC "%lu" +#endif + +#if __s390x__ +#define MYSQLND_LL_SPEC "%li" +#define MYSQLND_LLU_SPEC "%lu" +#endif + +#if __powerpc__ && !__powerpc64__ +#define MYSQLND_LL_SPEC "%lli" +#define MYSQLND_LLU_SPEC "%llu" +#endif + +#if __s390__ && !__s390x__ +#define MYSQLND_LL_SPEC "%lli" +#define MYSQLND_LLU_SPEC "%llu" +#endif + #define MYSQLND_SZ_T_SPEC "%zd" #ifndef L64 #define L64(x) x##LL diff --git a/ext/mysqlnd/mysqlnd_structs.h b/ext/mysqlnd/mysqlnd_structs.h index f309806487..ffeda103d0 100644 --- a/ext/mysqlnd/mysqlnd_structs.h +++ b/ext/mysqlnd/mysqlnd_structs.h @@ -237,6 +237,8 @@ struct st_mysqlnd_conn_methods ulong (*escape_string)(const MYSQLND * const conn, char *newstr, const char *escapestr, size_t escapestr_len TSRMLS_DC); enum_func_status (*set_charset)(MYSQLND * const conn, const char * const charset TSRMLS_DC); enum_func_status (*query)(MYSQLND *conn, const char *query, unsigned int query_len TSRMLS_DC); + enum_func_status (*send_query)(MYSQLND *conn, const char *query, unsigned int query_len TSRMLS_DC); + enum_func_status (*reap_query)(MYSQLND *conn TSRMLS_DC); MYSQLND_RES * (*use_result)(MYSQLND * const conn TSRMLS_DC); MYSQLND_RES * (*store_result)(MYSQLND * const conn TSRMLS_DC); MYSQLND_RES * (*background_store_result)(MYSQLND * const conn TSRMLS_DC);