summaryrefslogtreecommitdiff
path: root/ext/mysqlnd
diff options
context:
space:
mode:
authorNikita Popov <nikita.ppv@gmail.com>2020-12-16 12:12:06 +0100
committerNikita Popov <nikita.ppv@gmail.com>2020-12-18 10:19:13 +0100
commitbc166844e37a6e1531a18dc0916fbe508152fc6c (patch)
treec7f9d77387bba95a1ef7feee421da07c4c104448 /ext/mysqlnd
parent315f3f8dc9b3ba3639c9284301933286fc99a825 (diff)
downloadphp-git-bc166844e37a6e1531a18dc0916fbe508152fc6c.tar.gz
MySQLnd: Support cursors in store/get result
This fixes two related issues: 1. When a PS with cursor is used in store_result/get_result, perform a COM_FETCH with maximum number of rows rather than silently switching to an unbuffered result set (in the case of store_result) or erroring (in the case of get_result). In the future, we might want to make get_result unbuffered for PS with cursors, as using cursors with buffered result sets doesn't really make sense. Unlike store_result, get_result isn't very explicit about what kind of result set is desired. 2. If the client did not request a cursor, but the server reports that a cursor exists, ignore this and treat the PS as if it has no cursor (i.e. to not use COM_FETCH). It appears to be a server side bug that a cursor used inside an SP will be reported to the client, even though the client cannot use the cursor. Fixes bug #64638, bug #72862, bug #77935. Closes GH-6518.
Diffstat (limited to 'ext/mysqlnd')
-rw-r--r--ext/mysqlnd/mysqlnd_ps.c131
1 files changed, 72 insertions, 59 deletions
diff --git a/ext/mysqlnd/mysqlnd_ps.c b/ext/mysqlnd/mysqlnd_ps.c
index eda9c312d5..12aace6ec2 100644
--- a/ext/mysqlnd/mysqlnd_ps.c
+++ b/ext/mysqlnd/mysqlnd_ps.c
@@ -40,6 +40,36 @@ enum_func_status mysqlnd_stmt_execute_batch_generate_request(MYSQLND_STMT * cons
static void mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const stmt);
static void mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const stmt, const unsigned int param_no);
+static enum_func_status mysqlnd_stmt_send_cursor_fetch_command(
+ const MYSQLND_STMT_DATA *stmt, unsigned max_rows)
+{
+ MYSQLND_CONN_DATA *conn = stmt->conn;
+ zend_uchar buf[MYSQLND_STMT_ID_LENGTH /* statement id */ + 4 /* number of rows to fetch */];
+ const MYSQLND_CSTRING payload = {(const char*) buf, sizeof(buf)};
+
+ int4store(buf, stmt->stmt_id);
+ int4store(buf + MYSQLND_STMT_ID_LENGTH, max_rows);
+
+ if (conn->command->stmt_fetch(conn, payload) == FAIL) {
+ COPY_CLIENT_ERROR(stmt->error_info, *conn->error_info);
+ return FAIL;
+ }
+ return PASS;
+}
+
+static zend_bool mysqlnd_stmt_check_state(const MYSQLND_STMT_DATA *stmt)
+{
+ const MYSQLND_CONN_DATA *conn = stmt->conn;
+ if (stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE) {
+ return 0;
+ }
+ if (stmt->cursor_exists) {
+ return GET_CONNECTION_STATE(&conn->state) == CONN_READY;
+ } else {
+ return GET_CONNECTION_STATE(&conn->state) == CONN_FETCHING_DATA;
+ }
+}
+
/* {{{ mysqlnd_stmt::store_result */
static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_stmt, store_result)(MYSQLND_STMT * const s)
@@ -60,14 +90,8 @@ MYSQLND_METHOD(mysqlnd_stmt, store_result)(MYSQLND_STMT * const s)
DBG_RETURN(NULL);
}
- if (stmt->cursor_exists) {
- /* Silently convert buffered to unbuffered, for now */
- DBG_RETURN(s->m->use_result(s));
- }
-
/* Nothing to store for UPSERT/LOAD DATA*/
- if (GET_CONNECTION_STATE(&conn->state) != CONN_FETCHING_DATA || stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE)
- {
+ if (!mysqlnd_stmt_check_state(stmt)) {
SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
DBG_RETURN(NULL);
}
@@ -78,6 +102,12 @@ MYSQLND_METHOD(mysqlnd_stmt, store_result)(MYSQLND_STMT * const s)
SET_EMPTY_ERROR(conn->error_info);
MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_PS_BUFFERED_SETS);
+ if (stmt->cursor_exists) {
+ if (mysqlnd_stmt_send_cursor_fetch_command(stmt, -1) == FAIL) {
+ DBG_RETURN(NULL);
+ }
+ }
+
result = stmt->result;
result->type = MYSQLND_RES_PS_BUF;
/* result->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol; */
@@ -152,19 +182,8 @@ MYSQLND_METHOD(mysqlnd_stmt, get_result)(MYSQLND_STMT * const s)
DBG_RETURN(NULL);
}
- if (stmt->cursor_exists) {
- /* Prepared statement cursors are not supported as of yet */
- char * msg;
- mnd_sprintf(&msg, 0, "%s() cannot be used with cursors", get_active_function_name());
- SET_CLIENT_ERROR(stmt->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, msg);
- if (msg) {
- mnd_sprintf_free(msg);
- }
- DBG_RETURN(NULL);
- }
-
/* Nothing to store for UPSERT/LOAD DATA*/
- if (GET_CONNECTION_STATE(&conn->state) != CONN_FETCHING_DATA || stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE) {
+ if (!mysqlnd_stmt_check_state(stmt)) {
SET_CLIENT_ERROR(stmt->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
DBG_RETURN(NULL);
}
@@ -173,6 +192,12 @@ MYSQLND_METHOD(mysqlnd_stmt, get_result)(MYSQLND_STMT * const s)
SET_EMPTY_ERROR(conn->error_info);
MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_BUFFERED_SETS);
+ if (stmt->cursor_exists) {
+ if (mysqlnd_stmt_send_cursor_fetch_command(stmt, -1) == FAIL) {
+ DBG_RETURN(NULL);
+ }
+ }
+
do {
result = conn->m->result_init(stmt->result->field_count);
if (!result) {
@@ -561,28 +586,30 @@ mysqlnd_stmt_execute_parse_response(MYSQLND_STMT * const s, enum_mysqlnd_parse_e
DBG_INF_FMT("server_status=%u cursor=%u", UPSERT_STATUS_GET_SERVER_STATUS(stmt->upsert_status),
UPSERT_STATUS_GET_SERVER_STATUS(stmt->upsert_status) & SERVER_STATUS_CURSOR_EXISTS);
- if (UPSERT_STATUS_GET_SERVER_STATUS(stmt->upsert_status) & SERVER_STATUS_CURSOR_EXISTS) {
- DBG_INF("cursor exists");
- stmt->cursor_exists = TRUE;
- SET_CONNECTION_STATE(&conn->state, CONN_READY);
- /* Only cursor read */
- stmt->default_rset_handler = s->m->use_result;
- DBG_INF("use_result");
- } else if (stmt->flags & CURSOR_TYPE_READ_ONLY) {
- DBG_INF("asked for cursor but got none");
- /*
- We have asked for CURSOR but got no cursor, because the condition
- above is not fulfilled. Then...
-
- This is a single-row result set, a result set with no rows, EXPLAIN,
- SHOW VARIABLES, or some other command which either a) bypasses the
- cursors framework in the server and writes rows directly to the
- network or b) is more efficient if all (few) result set rows are
- precached on client and server's resources are freed.
- */
- /* preferred is buffered read */
- stmt->default_rset_handler = s->m->store_result;
- DBG_INF("store_result");
+ if (stmt->flags & CURSOR_TYPE_READ_ONLY) {
+ if (UPSERT_STATUS_GET_SERVER_STATUS(stmt->upsert_status) & SERVER_STATUS_CURSOR_EXISTS) {
+ DBG_INF("cursor exists");
+ stmt->cursor_exists = TRUE;
+ SET_CONNECTION_STATE(&conn->state, CONN_READY);
+ /* Only cursor read */
+ stmt->default_rset_handler = s->m->use_result;
+ DBG_INF("use_result");
+ } else {
+ DBG_INF("asked for cursor but got none");
+ /*
+ We have asked for CURSOR but got no cursor, because the condition
+ above is not fulfilled. Then...
+
+ This is a single-row result set, a result set with no rows, EXPLAIN,
+ SHOW VARIABLES, or some other command which either a) bypasses the
+ cursors framework in the server and writes rows directly to the
+ network or b) is more efficient if all (few) result set rows are
+ precached on client and server's resources are freed.
+ */
+ /* preferred is buffered read */
+ stmt->default_rset_handler = s->m->store_result;
+ DBG_INF("store_result");
+ }
} else {
DBG_INF("no cursor");
/* preferred is unbuffered read */
@@ -940,11 +967,7 @@ MYSQLND_METHOD(mysqlnd_stmt, use_result)(MYSQLND_STMT * s)
}
DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
- if (!stmt->field_count ||
- (!stmt->cursor_exists && GET_CONNECTION_STATE(&conn->state) != CONN_FETCHING_DATA) ||
- (stmt->cursor_exists && GET_CONNECTION_STATE(&conn->state) != CONN_READY) ||
- (stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE))
- {
+ if (!stmt->field_count || !mysqlnd_stmt_check_state(stmt)) {
SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
DBG_ERR("command out of sync");
DBG_RETURN(NULL);
@@ -974,7 +997,6 @@ mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES * result, void * param, const unsigned
MYSQLND_STMT * s = (MYSQLND_STMT *) param;
MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
- zend_uchar buf[MYSQLND_STMT_ID_LENGTH /* statement id */ + 4 /* number of rows to fetch */];
MYSQLND_PACKET_ROW * row_packet;
DBG_ENTER("mysqlnd_fetch_stmt_row_cursor");
@@ -998,18 +1020,9 @@ mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES * result, void * param, const unsigned
SET_EMPTY_ERROR(stmt->error_info);
SET_EMPTY_ERROR(conn->error_info);
- int4store(buf, stmt->stmt_id);
- int4store(buf + MYSQLND_STMT_ID_LENGTH, 1); /* for now fetch only one row */
-
- {
- const MYSQLND_CSTRING payload = {(const char*) buf, sizeof(buf)};
-
- ret = conn->command->stmt_fetch(conn, payload);
- if (ret == FAIL) {
- COPY_CLIENT_ERROR(stmt->error_info, *conn->error_info);
- DBG_RETURN(FAIL);
- }
-
+ /* for now fetch only one row */
+ if (mysqlnd_stmt_send_cursor_fetch_command(stmt, 1) == FAIL) {
+ DBG_RETURN(FAIL);
}
row_packet->skip_extraction = stmt->result_bind? FALSE:TRUE;