367 lines
14 KiB
C++
367 lines
14 KiB
C++
/* Copyright (c) 2022, 2024, Oracle and/or its affiliates.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License, version 2.0,
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is designed to work with certain software (including
|
|
* but not limited to OpenSSL) that is licensed under separate terms,
|
|
* as designated in a particular file or component or in included license
|
|
* documentation. The authors of MySQL hereby grant you an additional
|
|
* permission to link the program and your derivative works with the
|
|
* separately licensed software that they have either included with
|
|
* the program or referenced in the documentation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License, version 2.0, for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "mysql_command_backend.h"
|
|
#include <mysql/components/component_implementation.h>
|
|
#include <mysql/components/services/mysql_admin_session.h>
|
|
#include "include/mysql.h"
|
|
#include "include/mysqld_errmsg.h"
|
|
#include "include/sql_common.h"
|
|
#include "mysql/service_srv_session.h"
|
|
#include "mysql_command_delegates.h"
|
|
#include "sql/current_thd.h"
|
|
#include "sql/server_component/mysql_command_consumer_imp.h"
|
|
#include "sql/server_component/mysql_command_services_imp.h"
|
|
#include "sql/server_component/security_context_imp.h"
|
|
#include "sql/srv_session.h"
|
|
|
|
extern SERVICE_TYPE_NO_CONST(registry) * srv_registry;
|
|
extern SERVICE_TYPE_NO_CONST(registry) * srv_registry_no_lock;
|
|
|
|
namespace cs {
|
|
|
|
MYSQL_METHODS mysql_methods = {
|
|
csi_connect, csi_read_query_result, csi_advanced_command,
|
|
csi_read_rows, csi_use_result, csi_fetch_row,
|
|
csi_fetch_lengths, csi_flush_use_result, csi_read_change_user_result,
|
|
#if !defined(MYSQL_SERVER) && !defined(MYSQL_COMPONENT)
|
|
nullptr, // csi_list_fields,
|
|
nullptr, // csi_read_prepare_result,
|
|
nullptr, // csi_stmt_execute,
|
|
nullptr, // csi_read_binary_rows,
|
|
nullptr, // csi_unbuffered_fetch,
|
|
nullptr, // csi_free_embedded_thd,
|
|
nullptr, // csi_read_statistics,
|
|
nullptr, // csi_next_result,
|
|
nullptr, // csi_read_rows_from_cursor
|
|
#endif // ! MYSQL_SERVER
|
|
nullptr, /* read_query_result_nonblocking */
|
|
nullptr, /* advanced_command_nonblocking */
|
|
nullptr, /* read_rows_nonblocking */
|
|
nullptr, /* flush_use_result_nonblocking */
|
|
nullptr, /* next_result_nonblocking */
|
|
nullptr, /* read_change_user_result_nonblocking */
|
|
};
|
|
|
|
static mysql_state_machine_status acquire_services(
|
|
mysql_command_consumer_refs *consumer_refs,
|
|
mysql_service_registry_t *srv_registry) {
|
|
my_h_service h_command_consumer = nullptr;
|
|
my_h_service h_command_consumer_srv = nullptr;
|
|
if (consumer_refs->factory_srv == nullptr) {
|
|
if (srv_registry->acquire("mysql_text_consumer_factory_v1.mysql_server",
|
|
&h_command_consumer))
|
|
return STATE_MACHINE_FAILED;
|
|
else
|
|
consumer_refs->factory_srv = reinterpret_cast<SERVICE_TYPE_NO_CONST(
|
|
mysql_text_consumer_factory_v1) *>(h_command_consumer);
|
|
}
|
|
|
|
if (consumer_refs->metadata_srv == nullptr) {
|
|
if (srv_registry->acquire_related("mysql_text_consumer_metadata_v1",
|
|
h_command_consumer,
|
|
&h_command_consumer_srv))
|
|
return STATE_MACHINE_FAILED;
|
|
else
|
|
consumer_refs->metadata_srv = reinterpret_cast<SERVICE_TYPE_NO_CONST(
|
|
mysql_text_consumer_metadata_v1) *>(h_command_consumer_srv);
|
|
}
|
|
|
|
if (consumer_refs->row_factory_srv == nullptr) {
|
|
if (srv_registry->acquire_related("mysql_text_consumer_row_factory_v1",
|
|
h_command_consumer,
|
|
&h_command_consumer_srv))
|
|
return STATE_MACHINE_FAILED;
|
|
else
|
|
consumer_refs->row_factory_srv = reinterpret_cast<SERVICE_TYPE_NO_CONST(
|
|
mysql_text_consumer_row_factory_v1) *>(h_command_consumer_srv);
|
|
}
|
|
|
|
if (consumer_refs->error_srv == nullptr) {
|
|
if (srv_registry->acquire_related("mysql_text_consumer_error_v1",
|
|
h_command_consumer,
|
|
&h_command_consumer_srv))
|
|
return STATE_MACHINE_FAILED;
|
|
else
|
|
consumer_refs->error_srv = reinterpret_cast<SERVICE_TYPE_NO_CONST(
|
|
mysql_text_consumer_error_v1) *>(h_command_consumer_srv);
|
|
}
|
|
|
|
if (consumer_refs->get_null_srv == nullptr) {
|
|
if (srv_registry->acquire_related("mysql_text_consumer_get_null_v1",
|
|
h_command_consumer,
|
|
&h_command_consumer_srv))
|
|
return STATE_MACHINE_FAILED;
|
|
else
|
|
consumer_refs->get_null_srv = reinterpret_cast<SERVICE_TYPE_NO_CONST(
|
|
mysql_text_consumer_get_null_v1) *>(h_command_consumer_srv);
|
|
}
|
|
|
|
if (consumer_refs->get_integer_srv == nullptr) {
|
|
if (srv_registry->acquire_related("mysql_text_consumer_get_integer_v1",
|
|
h_command_consumer,
|
|
&h_command_consumer_srv))
|
|
return STATE_MACHINE_FAILED;
|
|
else
|
|
consumer_refs->get_integer_srv = reinterpret_cast<SERVICE_TYPE_NO_CONST(
|
|
mysql_text_consumer_get_integer_v1) *>(h_command_consumer_srv);
|
|
}
|
|
|
|
if (consumer_refs->get_longlong_srv == nullptr) {
|
|
if (srv_registry->acquire_related("mysql_text_consumer_get_longlong_v1",
|
|
h_command_consumer,
|
|
&h_command_consumer_srv))
|
|
return STATE_MACHINE_FAILED;
|
|
else
|
|
consumer_refs->get_longlong_srv = reinterpret_cast<SERVICE_TYPE_NO_CONST(
|
|
mysql_text_consumer_get_longlong_v1) *>(h_command_consumer_srv);
|
|
}
|
|
|
|
if (consumer_refs->get_decimal_srv == nullptr) {
|
|
if (srv_registry->acquire_related("mysql_text_consumer_get_decimal_v1",
|
|
h_command_consumer,
|
|
&h_command_consumer_srv))
|
|
return STATE_MACHINE_FAILED;
|
|
else
|
|
consumer_refs->get_decimal_srv = reinterpret_cast<SERVICE_TYPE_NO_CONST(
|
|
mysql_text_consumer_get_decimal_v1) *>(h_command_consumer_srv);
|
|
}
|
|
|
|
if (consumer_refs->get_double_srv == nullptr) {
|
|
if (srv_registry->acquire_related("mysql_text_consumer_get_double_v1",
|
|
h_command_consumer,
|
|
&h_command_consumer_srv))
|
|
return STATE_MACHINE_FAILED;
|
|
else
|
|
consumer_refs->get_double_srv = reinterpret_cast<SERVICE_TYPE_NO_CONST(
|
|
mysql_text_consumer_get_double_v1) *>(h_command_consumer_srv);
|
|
}
|
|
|
|
if (consumer_refs->get_date_time_srv == nullptr) {
|
|
if (srv_registry->acquire_related("mysql_text_consumer_get_date_time_v1",
|
|
h_command_consumer,
|
|
&h_command_consumer_srv))
|
|
return STATE_MACHINE_FAILED;
|
|
else
|
|
consumer_refs->get_date_time_srv = reinterpret_cast<SERVICE_TYPE_NO_CONST(
|
|
mysql_text_consumer_get_date_time_v1) *>(h_command_consumer_srv);
|
|
}
|
|
|
|
if (consumer_refs->get_string_srv == nullptr) {
|
|
if (srv_registry->acquire_related("mysql_text_consumer_get_string_v1",
|
|
h_command_consumer,
|
|
&h_command_consumer_srv))
|
|
return STATE_MACHINE_FAILED;
|
|
else
|
|
consumer_refs->get_string_srv = reinterpret_cast<SERVICE_TYPE_NO_CONST(
|
|
mysql_text_consumer_get_string_v1) *>(h_command_consumer_srv);
|
|
}
|
|
|
|
if (consumer_refs->client_capabilities_srv == nullptr) {
|
|
if (srv_registry->acquire_related(
|
|
"mysql_text_consumer_client_capabilities_v1", h_command_consumer,
|
|
&h_command_consumer_srv))
|
|
return STATE_MACHINE_FAILED;
|
|
else
|
|
consumer_refs->client_capabilities_srv =
|
|
reinterpret_cast<SERVICE_TYPE_NO_CONST(
|
|
mysql_text_consumer_client_capabilities_v1) *>(
|
|
h_command_consumer_srv);
|
|
}
|
|
return STATE_MACHINE_DONE;
|
|
}
|
|
|
|
mysql_state_machine_status cssm_begin_connect(mysql_async_connect *ctx) {
|
|
MYSQL *mysql = ctx->mysql;
|
|
Mysql_handle mysql_handle;
|
|
mysql_handle.mysql = mysql;
|
|
auto mcs_extn = MYSQL_COMMAND_SERVICE_EXTN(mysql);
|
|
assert(mcs_extn);
|
|
const char *host = ctx->host;
|
|
const char *user = ctx->user;
|
|
const char *db = ctx->db;
|
|
MYSQL_THD thd;
|
|
bool no_lock_registry = false;
|
|
MYSQL_SESSION mysql_session = nullptr;
|
|
|
|
if (mysql_command_services_imp::get(
|
|
(MYSQL_H)&mysql_handle, MYSQL_NO_LOCK_REGISTRY, &no_lock_registry))
|
|
return STATE_MACHINE_FAILED;
|
|
mysql_service_registry_t *registry_service =
|
|
no_lock_registry ? srv_registry_no_lock : srv_registry;
|
|
|
|
if (mcs_extn->mcs_thd == nullptr || mcs_extn->session_svc == nullptr) {
|
|
/*
|
|
Avoid possibility of nested txn in the current thd.
|
|
If it is called, for example from a UDF.
|
|
*/
|
|
my_service<SERVICE_TYPE(mysql_admin_session)> service(
|
|
"mysql_admin_session.mysql_server", registry_service);
|
|
if (service.is_valid()) mysql_session = service->open(nullptr, ctx);
|
|
if (mysql_session == nullptr) return STATE_MACHINE_FAILED;
|
|
thd = mysql_session->get_thd();
|
|
mcs_extn->is_thd_associated = false;
|
|
Security_context_handle sc;
|
|
if (mysql_security_context_imp::get(thd, &sc)) return STATE_MACHINE_FAILED;
|
|
if (mysql_security_context_imp::lookup(sc, user, host, nullptr, db))
|
|
return STATE_MACHINE_FAILED;
|
|
mcs_extn->mcs_thd = thd;
|
|
mysql->thd = thd;
|
|
mcs_extn->session_svc = mysql_session;
|
|
} else {
|
|
mysql->thd = reinterpret_cast<void *>(mcs_extn->mcs_thd);
|
|
}
|
|
/*
|
|
These references might be created in mysql_command_services_imp::set api.
|
|
If not, we will create here.
|
|
*/
|
|
if (mcs_extn->command_consumer_services == nullptr) {
|
|
/*
|
|
Provide default implementations for mysql command consumer services
|
|
and will be released in close() api.
|
|
*/
|
|
mcs_extn->command_consumer_services = new mysql_command_consumer_refs();
|
|
}
|
|
mysql_command_consumer_refs *consumer_refs =
|
|
(mysql_command_consumer_refs *)mcs_extn->command_consumer_services;
|
|
/* The above new allocation failed */
|
|
if (consumer_refs == nullptr) return STATE_MACHINE_FAILED;
|
|
/* If the services are not acquired by mysql_command_services_imp::set api,
|
|
then it will be acquired. */
|
|
auto status = acquire_services(consumer_refs, registry_service);
|
|
if (status == STATE_MACHINE_FAILED) return status;
|
|
mysql->client_flag = 0; /* For handshake */
|
|
mysql->server_status = SERVER_STATUS_AUTOCOMMIT;
|
|
return STATE_MACHINE_DONE;
|
|
}
|
|
|
|
MYSQL *csi_connect(mysql_async_connect *ctx) {
|
|
assert(ctx);
|
|
ctx->state_function = cs::cssm_begin_connect;
|
|
return connect_helper(ctx);
|
|
}
|
|
|
|
bool csi_read_query_result(MYSQL *mysql) {
|
|
mysql->status = MYSQL_STATUS_GET_RESULT;
|
|
mysql->resultset_metadata = RESULTSET_METADATA_FULL;
|
|
return false;
|
|
}
|
|
|
|
bool csi_advanced_command(MYSQL *mysql, enum enum_server_command command,
|
|
const uchar *, size_t, const uchar *arg,
|
|
size_t arg_length, bool, MYSQL_STMT *) {
|
|
COM_DATA data;
|
|
memset(&data, 0, sizeof(data));
|
|
data.com_query.query = (const char *)arg;
|
|
data.com_query.length = arg_length;
|
|
char err_msg[1][256];
|
|
SRV_CTX_H srv_ctx_h = nullptr;
|
|
Mysql_handle mysql_handle;
|
|
THD *thd = (THD *)mysql->thd;
|
|
auto mcs_extn = MYSQL_COMMAND_SERVICE_EXTN(mysql);
|
|
void *command_consumer_srv = nullptr;
|
|
bool ret = true;
|
|
|
|
/* mcs_extn->command_consumer_services will be set in connect api */
|
|
if (mcs_extn->command_consumer_services) {
|
|
command_consumer_srv = mcs_extn->command_consumer_services;
|
|
} else {
|
|
return ret;
|
|
}
|
|
|
|
mysql_handle.mysql = mysql;
|
|
if (mcs_extn->consumer_srv_data != nullptr)
|
|
srv_ctx_h = reinterpret_cast<SRV_CTX_H>(mcs_extn->consumer_srv_data);
|
|
else if (((class mysql_command_consumer_refs *)(command_consumer_srv))
|
|
->factory_srv->start(&srv_ctx_h, (MYSQL_H *)&mysql_handle)) {
|
|
sprintf(*err_msg, "Could not create %s service",
|
|
"mysql_text_consumer_factory_v1");
|
|
goto error;
|
|
}
|
|
|
|
{
|
|
Callback_command_delegate callback_delegate(command_consumer_srv,
|
|
srv_ctx_h);
|
|
if (command_service_run_command(
|
|
mcs_extn->session_svc, command, &data, thd->charset(),
|
|
callback_delegate.callbacks(), callback_delegate.representation(),
|
|
&callback_delegate) ||
|
|
thd->is_error()) {
|
|
uint32_t err_num;
|
|
char **ch_ptr = reinterpret_cast<char **>(&err_msg[0]);
|
|
((class mysql_command_consumer_refs *)(command_consumer_srv))
|
|
->error_srv->error(srv_ctx_h, &err_num,
|
|
const_cast<const char **>(ch_ptr));
|
|
strcpy(*err_msg, *ch_ptr);
|
|
goto error;
|
|
}
|
|
}
|
|
ret = false;
|
|
error:
|
|
if (ret) my_error(ER_COMMAND_SERVICE_BACKEND_FAILED, MYF(0), err_msg);
|
|
return ret ? true : false;
|
|
}
|
|
|
|
MYSQL_DATA *csi_read_rows(MYSQL *mysql,
|
|
MYSQL_FIELD *mysql_fields [[maybe_unused]],
|
|
unsigned int fields [[maybe_unused]]) {
|
|
auto mcs_extn = MYSQL_COMMAND_SERVICE_EXTN(mysql);
|
|
return std::exchange(mcs_extn->data, nullptr);
|
|
}
|
|
|
|
MYSQL_RES *csi_use_result(MYSQL *mysql) { return use_result(mysql); }
|
|
|
|
void csi_fetch_lengths(ulong *to, MYSQL_ROW column, unsigned int field_count) {
|
|
for (unsigned int i = 0; i < field_count; i++) {
|
|
if (!*column) {
|
|
*to = 0; /* Null */
|
|
continue;
|
|
}
|
|
*to = strlen(*column);
|
|
column++;
|
|
to++;
|
|
}
|
|
}
|
|
|
|
void csi_flush_use_result(MYSQL *, bool) {
|
|
// Dummy.
|
|
// We already have entire result set. Therefore, there is
|
|
// no need of the flusing the partial result set.
|
|
}
|
|
|
|
int csi_read_change_user_result(MYSQL *) {
|
|
return static_cast<int> packet_error;
|
|
}
|
|
|
|
MYSQL_ROW csi_fetch_row(MYSQL_RES *res) {
|
|
MYSQL_ROW tmp;
|
|
if (!res->data_cursor) {
|
|
DBUG_PRINT("info", ("end of data"));
|
|
return res->current_row = (MYSQL_ROW) nullptr;
|
|
}
|
|
tmp = res->data_cursor->data;
|
|
res->data_cursor = res->data_cursor->next;
|
|
return res->current_row = tmp;
|
|
}
|
|
} // namespace cs
|