mirror of
https://github.com/php/php-src.git
synced 2025-08-16 05:58:45 +02:00
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.
This commit is contained in:
parent
315f3f8dc9
commit
bc166844e3
5 changed files with 230 additions and 91 deletions
|
@ -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...
|
||||
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");
|
||||
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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue