📄 mysqlnd_ps.c
字号:
mysqlnd_fetch_stmt_row_buffered(MYSQLND_RES *result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC){ unsigned int i; MYSQLND_STMT *stmt = (MYSQLND_STMT *) param; /* If we haven't read everything */ if (result->data->data_cursor && (result->data->data_cursor - result->data->data) < result->data->row_count) { /* The user could have skipped binding - don't crash*/ if (stmt->result_bind) { zval **current_row = *result->data->data_cursor; for (i = 0; i < result->field_count; i++) { /* copy the type */ if (stmt->result_bind[i].bound == TRUE) { 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; } else { ZVAL_NULL(stmt->result_bind[i].zv); } } } } result->data->data_cursor++; *fetched_anything = TRUE; } else { result->data->data_cursor = NULL; *fetched_anything = FALSE;#ifndef MYSQLND_SILENT php_printf("NO MORE DATA\n ");#endif } return PASS;}/* }}} *//* {{{ mysqlnd_stmt_fetch_row_unbuffered */enum_func_statusmysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC){ enum_func_status ret; MYSQLND_STMT *stmt = (MYSQLND_STMT *) param; unsigned int i, field_count = result->field_count; php_mysql_packet_row *row_packet = result->row_packet; if (result->unbuf->eof_reached) { /* No more rows obviously */ *fetched_anything = FALSE; return PASS; } if (result->conn->state != CONN_FETCHING_DATA) { SET_CLIENT_ERROR(result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync); return FAIL; } /* Let the row packet fill our buffer and skip additional malloc + memcpy */ row_packet->skip_extraction = stmt && stmt->result_bind? FALSE:TRUE; /* If we skip rows (stmt == NULL || stmt->result_bind == NULL) we have to mysqlnd_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++; *fetched_anything = TRUE; if (!row_packet->skip_extraction) { mysqlnd_unbuffered_free_last_data(result 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; for (i = 0; i < field_count; i++) { if (stmt->result_bind[i].bound == TRUE) { zval *data = result->unbuf->last_row_data[i]; /* stmt->result_bind[i].zv has been already destructed in mysqlnd_unbuffered_free_last_data() */ if (IS_NULL != (Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(data)) ) { stmt->result_bind[i].zv->value = data->value; if ( (Z_TYPE_P(data) == IS_STRING#if PHP_MAJOR_VERSION >= 6 || Z_TYPE_P(data) == IS_UNICODE#endif ) && (result->meta->fields[i].max_length < Z_STRLEN_P(data))) { result->meta->fields[i].max_length = Z_STRLEN_P(data); } } } } } else { /* Data has been allocated and usually mysqlnd_unbuffered_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. */ efree(row_packet->row_buffer); row_packet->row_buffer = NULL; } } /* Mark the connection as usable again */ if (row_packet->eof) { result->unbuf->eof_reached = TRUE; result->conn->upsert_status.warning_count = row_packet->warning_count; result->conn->upsert_status.server_status = row_packet->server_status; /* result->row_packet will be cleaned when destroying the result object */ if (result->conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) { result->conn->state = CONN_NEXT_RESULT_PENDING; } else { result->conn->state = CONN_READY; } *fetched_anything = FALSE; } return ret;}/* }}} *//* {{{ mysqlnd_stmt_use_result */MYSQLND_RES * _mysqlnd_stmt_use_result(MYSQLND_STMT *stmt TSRMLS_DC){ MYSQLND_RES *result; MYSQLND *conn = stmt->conn; if (!stmt->field_count || (!stmt->cursor_exists && conn->state != CONN_FETCHING_DATA) || (stmt->cursor_exists && conn->state != CONN_READY) || (stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE)) { SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync); return NULL; } MYSQLND_INC_CONN_STATISTIC(&stmt->conn->stats, STAT_PS_UNBUFFERED_SETS); result = stmt->result; result->type = MYSQLND_RES_PS; result->m.fetch_row = stmt->cursor_exists? mysqlnd_fetch_stmt_row_cursor: mysqlnd_stmt_fetch_row_unbuffered; result->m.fetch_lengths = NULL; /* makes no sense */ result->zval_cache = NULL; result->unbuf = ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED)); /* Will be freed in the mysqlnd_internal_free_result_contents() called by the resource destructor. mysqlnd_fetch_row_unbuffered() expects this to be not NULL. */ PACKET_INIT(result->row_packet, PROT_ROW_PACKET, php_mysql_packet_row *); result->row_packet->field_count = result->field_count; result->row_packet->binary_protocol = TRUE; result->row_packet->fields_metadata = stmt->result->meta->fields; result->lengths = NULL; stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED; /* No multithreading issues as we don't share the connection :) */ return result;}/* }}} */#define STMT_ID_LENGTH 4/* {{{ mysqlnd_fetch_row_cursor */enum_func_statusmysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC){ enum_func_status ret; MYSQLND_STMT *stmt = (MYSQLND_STMT *) param; zend_uchar buf[STMT_ID_LENGTH /* statement id */ + 4 /* number of rows to fetch */]; php_mysql_packet_row *row_packet = result->row_packet; if (!stmt) { return FAIL; } if (stmt->state < MYSQLND_STMT_USER_FETCHING) { /* Only initted - error */ SET_CLIENT_ERROR(stmt->conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync); return FAIL; } int4store(buf, stmt->stmt_id); int4store(buf + STMT_ID_LENGTH, 1); /* for now fetch only one row */ if (FAIL == mysqlnd_simple_command(stmt->conn, COM_STMT_FETCH, (char *)buf, sizeof(buf), PROT_LAST /* we will handle the response packet*/, FALSE TSRMLS_CC)) { stmt->error_info = stmt->conn->error_info; return FAIL; } row_packet->skip_extraction = stmt->result_bind? FALSE:TRUE; if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) { unsigned int i, field_count = result->field_count; mysqlnd_unbuffered_free_last_data(result 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 (!row_packet->skip_extraction) { /* If no result bind, do nothing. We consumed the data */ for (i = 0; i < field_count; i++) { if (stmt->result_bind[i].bound == TRUE) { zval *data = result->unbuf->last_row_data[i]; /* stmt->result_bind[i].zv has been already destructed in mysqlnd_unbuffered_free_last_data() */ if (IS_NULL != (Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(data)) ) { stmt->result_bind[i].zv->value = data->value; if ((Z_TYPE_P(data) == IS_STRING#if PHP_MAJOR_VERSION >= 6 || Z_TYPE_P(data) == IS_UNICODE#endif ) && (result->meta->fields[i].max_length < Z_STRLEN_P(data))) { result->meta->fields[i].max_length = Z_STRLEN_P(data); } } } } } result->unbuf->row_count++; *fetched_anything = TRUE; /* We asked for one row, the next one should be EOF, eat it */ ret = PACKET_READ(row_packet, result->conn); if (row_packet->row_buffer) { efree(row_packet->row_buffer); row_packet->row_buffer = NULL; } } else { *fetched_anything = FALSE; stmt->upsert_status.warning_count = stmt->conn->upsert_status.warning_count = row_packet->warning_count; stmt->upsert_status.server_status = stmt->conn->upsert_status.server_status = row_packet->server_status; result->unbuf->eof_reached = row_packet->eof; } stmt->upsert_status.warning_count = stmt->conn->upsert_status.warning_count = row_packet->warning_count; stmt->upsert_status.server_status = stmt->conn->upsert_status.server_status = row_packet->server_status; return ret;}/* }}} *//* {{{ mysqlnd_stmt_fetch */PHPAPI enum_func_statusmysqlnd_stmt_fetch(MYSQLND_STMT * const stmt, zend_bool * const fetched_anything TSRMLS_DC){ if (!stmt->result || stmt->state < MYSQLND_STMT_WAITING_USE_OR_STORE) { return FAIL; } else if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) { /* Execute only once. We have to free the previous contents of user's bound vars */ stmt->default_rset_handler(stmt TSRMLS_CC); stmt->state = MYSQLND_STMT_USER_FETCHING; } /* no else please */ /* The user might have not bound any variables for result. Do the binding once she does it. */ if (stmt->result_bind && !stmt->result_zvals_separated_once) { unsigned int i; /* mysqlnd_stmt_store_result() has been called free the bind variables to prevent leaking of their previous content. */ for (i = 0; i < stmt->result->field_count; i++) { if (stmt->result_bind[i].bound == TRUE) { zval_dtor(stmt->result_bind[i].zv); ZVAL_NULL(stmt->result_bind[i].zv); } } stmt->result_zvals_separated_once = TRUE; } MYSQLND_INC_CONN_STATISTIC(&stmt->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT); return stmt->result->m.fetch_row(stmt->result, (void*)stmt, 0, fetched_anything TSRMLS_CC);}/* }}} *//* {{{ _mysqlnd_stmt_reset */static enum_func_status_mysqlnd_stmt_reset(MYSQLND_STMT * const stmt TSRMLS_DC){ enum_func_status ret = PASS; MYSQLND * conn = stmt->conn; zend_uchar cmd_buf[STMT_ID_LENGTH /* statement id */]; if (stmt->stmt_id) { if (stmt->param_bind) { unsigned int i; /* Reset Long Data */ for (i = 0; i < stmt->param_count; i++) { if (stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED) { stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED; } } } /* If the user decided to close the statement right after execute() We have to call the appropriate use_result() or store_result() and clean. */ if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) { stmt->default_rset_handler(stmt TSRMLS_CC); stmt->state = MYSQLND_STMT_USER_FETCHING; } if (stmt->result) { stmt->result->m.skip_result(stmt->result TSRMLS_CC); } /* Now the line should be free, if it wasn't */ int4store(cmd_buf, stmt->stmt_id); if (conn->state == CONN_READY && FAIL == (ret = mysqlnd_simple_command(conn, COM_STMT_RESET, (char *)cmd_buf, sizeof(cmd_buf), PROT_OK_PACKET, FALSE TSRMLS_CC))) { stmt->error_info = conn->error_info; } stmt->upsert_status = conn->upsert_status; stmt->state = MYSQLND_STMT_PREPARED; } return ret;}/* }}} *//* {{{ _mysqlnd_stmt_send_long_data */static enum_func_status_mysqlnd_stmt_send_long_data(MYSQLND_STMT * const stmt, unsigned int param_no, const char * const data, unsigned long length TSRMLS_DC){ enum_func_status ret = FAIL; MYSQLND * conn = stmt->conn; zend_uchar *cmd_buf; size_t packet_len; enum php_mysqlnd_server_command cmd = COM_STMT_SEND_LONG_DATA; if (stmt->state < MYSQLND_STMT_PREPARED || !stmt->param_bind) { return FAIL; } if (param_no >= stmt->param_count) { SET_STMT_ERROR(stmt, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number"); return FAIL; } if (stmt->param_bind[param_no].type != MYSQL_TYPE_LONG_BLOB) { SET_STMT_ERROR(stmt, CR_INVALID_BUFFER_USE, UNKNOWN_SQLSTATE, mysqlnd_not_bound_as_blob); return FAIL; } /* XXX: Unfortunately we have to allocate additional buffer to be able the additional data, which is like a header inside the payload. This should be optimised, but it will be a pervasive change, so mysqlnd_simple_command() will accept not a buffer, but actually MYSQLND_STRING* terminated by NULL, to send. If the strings are not big, we can collapse them on the buffer every connection has, but otherwise we will just send them one by one to the wire. */ if (conn->state == CONN_READY) { stmt->param_bind[param_no].flags |= MYSQLND_PARAM_BIND_BLOB_USED; cmd_buf = emalloc(packet_len = STMT_ID_LENGTH + 2 + length); int4store(cmd_buf, stmt->stmt_id); int2store(cmd_buf + STMT_ID_LENGTH, param_no); memcpy(cmd_buf + STMT_ID_LENGTH + 2, data, length); /* COM_STMT_SEND_LONG_DATA doesn't send an OK packet*/ ret = mysqlnd_simple_command(conn, cmd, (char *)cmd_buf, packet_len, PROT_LAST , FALSE TSRMLS_CC); efree(cmd_buf); if (FAIL == ret) { stmt->error_info = conn->error_info; } /* Cover protocol error: COM_STMT_SEND_LONG_DATA was designed to be quick and not sent response packets. According to documentation the only way to get an error is to have out-of-memory on the server-side. However, that's not true, as if max_allowed_packet_size is smaller than the chunk being sent to the server, the latter will complain with an error message. However, normally we don't expect an error message, thus we continue. When sending the next command, which expects response we will read the unexpected data and error message will look weird. Therefore we do non-blocking read to clean the line, if there is a need. Nevertheless, there is a built-in protection when sending a command packet, that checks if the line is clear - useful for debug purposes and to be switched off in release builds. Maybe we can make it automatic by checking what's the value of max_allowed_packet_size on the server and resending the data. */#if HAVE_USLEEP usleep(120000);#endif if ((packet_len = php_mysqlnd_consume_uneaten_data(conn, cmd TSRMLS_CC))) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "There was an error " "while sending long data. Probably max_allowed_packet_size " "is smaller than the data. You have to increase it or send " "smaller chunks of data. Answer was %u bytes long.", packet_len); SET_STMT_ERROR(stmt, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, "Server responded to COM_STMT_SEND_LONG_DATA."); ret = FAIL; } } return ret;}/* }}} *//* {{{ _mysqlnd_stmt_bind_param */static enum_func_status_mysqlnd_stmt_bind_param(MYSQLND_STMT * const stmt, MYSQLND_PARAM_BIND * const param_bind){ unsigned int i = 0; if (stmt->state < MYSQLND_STMT_PREPARED) { SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared); return FAIL; } if (stmt->param_count) { if (!param_bind) { SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, "Re-binding (still) not supported"); return FAIL; } else if (stmt->param_bind) { /* There is already result bound. Forbid for now re-binding!! */ for (i = 0; i < stmt->param_count; i++) { /* For BLOBS zv is NULL */ if (stmt->param_bind[i].zv) { /* We may have the last reference, then call zval_ptr_dtor() or we may leak memory. */ zval_ptr_dtor(&stmt->param_bind[i].zv); stmt->param_bind[i].zv = NULL;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -