mirror of
https://github.com/MariaDB/server.git
synced 2025-12-28 00:01:00 +00:00
MDEV-36737: Research and Estimation for Adapting VIDEX to MariaDB
Some checks failed
Build on Windows ARM64 / build (push) Has been cancelled
Some checks failed
Build on Windows ARM64 / build (push) Has been cancelled
VIDEX is a Disaggregated and Extensible Virtual Index Engine designed to perform efficient and accurate what-if analysis for tasks such as index recommendation. Fix template linking error for gcc debian: Add packaging for the VIDEX plugin This commit adds the necessary files to build `mariadb-plugin-videx` as a separate Debian package. - Add `COMPONENT videx-engine` to CMakeLists.txt to register the plugin. - Define the `mariadb-plugin-videx` package in debian/control. - Create `debian/mariadb-plugin-videx.install` to include the plugin .so and .cnf files. debian: fix indent in debian/control fix bugs from empty table videx: fix RPM autobake by adding CPACK summary/description
This commit is contained in:
parent
4a01157492
commit
068a095750
11
debian/control
vendored
11
debian/control
vendored
@ -1206,6 +1206,17 @@ Description: Snappy compression support in the server and storage engines
|
|||||||
Note that these affect InnoDB and Mroonga only;
|
Note that these affect InnoDB and Mroonga only;
|
||||||
RocksDB still uses the compression algorithms from its own library
|
RocksDB still uses the compression algorithms from its own library
|
||||||
|
|
||||||
|
Package: mariadb-plugin-videx
|
||||||
|
Architecture: any
|
||||||
|
Depends: mariadb-server (= ${server:Version}),
|
||||||
|
${misc:Depends},
|
||||||
|
${shlibs:Depends}
|
||||||
|
Description: Virtual index engine for what-if analysis in MariaDB
|
||||||
|
The VIDEX storage engine allows developers and DBAs to perform what-if
|
||||||
|
analysis by evaluating the impact of potential indexes on query plans.
|
||||||
|
It simulates the cost and behavior of engines like InnoDB without requiring
|
||||||
|
the indexes to be physically built, saving significant time and resources.
|
||||||
|
|
||||||
Package: mariadb-test
|
Package: mariadb-test
|
||||||
Architecture: any
|
Architecture: any
|
||||||
Depends: libnet-ssleay-perl,
|
Depends: libnet-ssleay-perl,
|
||||||
|
|||||||
2
debian/mariadb-plugin-videx.install
vendored
Normal file
2
debian/mariadb-plugin-videx.install
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
etc/mysql/mariadb.conf.d/videx.cnf
|
||||||
|
usr/lib/mysql/plugin/ha_videx.so
|
||||||
52
storage/videx/CMakeLists.txt
Normal file
52
storage/videx/CMakeLists.txt
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# Copyright (c) 2025 Bytedance Ltd. and 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 also distributed 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 included with MySQL.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Check necessary dependencies
|
||||||
|
FIND_PACKAGE(CURL)
|
||||||
|
IF(NOT CURL_FOUND)
|
||||||
|
MESSAGE(STATUS "CURL not found. VIDEX will not be compiled")
|
||||||
|
RETURN()
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
|
SET_PACKAGE_PROPERTIES(CURL PROPERTIES TYPE REQUIRED)
|
||||||
|
SET(CPACK_RPM_videx-engine_PACKAGE_SUMMARY "VIDEX AI-enhanced virtual index engine for MariaDB" PARENT_SCOPE)
|
||||||
|
SET(CPACK_RPM_videx-engine_PACKAGE_DESCRIPTION "VIDEX enables What-If Analysis via virtual indexes to simulate
|
||||||
|
query plans, forwarding requests via HTTP to external AI servers for enhanced cost estimation." PARENT_SCOPE)
|
||||||
|
|
||||||
|
INCLUDE_DIRECTORIES(
|
||||||
|
${CMAKE_SOURCE_DIR}/sql
|
||||||
|
${CMAKE_SOURCE_DIR}/include
|
||||||
|
${CURL_INCLUDE_DIRS}
|
||||||
|
${ZLIB_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
SET(VIDEX_SOURCES
|
||||||
|
videx_utils.cc
|
||||||
|
ha_videx.cc
|
||||||
|
)
|
||||||
|
|
||||||
|
MYSQL_ADD_PLUGIN(videx
|
||||||
|
${VIDEX_SOURCES}
|
||||||
|
STORAGE_ENGINE
|
||||||
|
LINK_LIBRARIES ${ZLIB_LIBRARIES} ${CURL_LIBRARIES}
|
||||||
|
COMPONENT videx-engine
|
||||||
|
)
|
||||||
932
storage/videx/ha_videx.cc
Normal file
932
storage/videx/ha_videx.cc
Normal file
@ -0,0 +1,932 @@
|
|||||||
|
/* Copyright (c) 2025 Bytedance Ltd. 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 also distributed 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 included with MySQL.
|
||||||
|
|
||||||
|
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 "my_global.h" /* ulonglong */
|
||||||
|
#include "thr_lock.h" /* THR_LOCK, THR_LOCK_DATA */
|
||||||
|
#include "handler.h" /* handler */
|
||||||
|
#include "my_base.h" /* ha_rows */
|
||||||
|
#include "table.h"
|
||||||
|
#include <sql_acl.h>
|
||||||
|
#include <sql_class.h>
|
||||||
|
#include <my_sys.h>
|
||||||
|
#include "scope.h"
|
||||||
|
#include "videx_utils.h"
|
||||||
|
#include <replication.h>
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
/** Shared state used by all open VIDEX handlers. */
|
||||||
|
class videx_share : public Handler_share
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
mysql_mutex_t mutex;
|
||||||
|
THR_LOCK lock;
|
||||||
|
videx_share();
|
||||||
|
~videx_share()
|
||||||
|
{
|
||||||
|
thr_lock_delete(&lock);
|
||||||
|
mysql_mutex_destroy(&mutex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Storage engine class. */
|
||||||
|
class ha_videx : public handler
|
||||||
|
{
|
||||||
|
THR_LOCK_DATA lock;
|
||||||
|
videx_share *share;
|
||||||
|
videx_share *get_share();
|
||||||
|
|
||||||
|
public:
|
||||||
|
ha_videx(handlerton *hton, TABLE_SHARE *table_arg);
|
||||||
|
~ha_videx() override;
|
||||||
|
|
||||||
|
const char *table_type() const override;
|
||||||
|
|
||||||
|
Table_flags table_flags() const override;
|
||||||
|
|
||||||
|
ulong index_flags(uint idx, uint part, bool all_parts) const override;
|
||||||
|
|
||||||
|
uint max_supported_keys() const override;
|
||||||
|
|
||||||
|
uint max_supported_key_length() const override;
|
||||||
|
|
||||||
|
uint max_supported_key_part_length() const override;
|
||||||
|
|
||||||
|
void column_bitmaps_signal() override;
|
||||||
|
|
||||||
|
int open(const char *name, int mode, uint test_if_locked) override;
|
||||||
|
|
||||||
|
int close(void) override;
|
||||||
|
|
||||||
|
handler *clone(const char *name, MEM_ROOT *mem_root) override;
|
||||||
|
|
||||||
|
int rnd_init(bool scan) override;
|
||||||
|
|
||||||
|
int rnd_next(uchar *buf) override;
|
||||||
|
|
||||||
|
int rnd_pos(uchar *buf, uchar *pos) override;
|
||||||
|
|
||||||
|
void position(const uchar *record) override;
|
||||||
|
|
||||||
|
int info(uint) override;
|
||||||
|
|
||||||
|
THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to,
|
||||||
|
thr_lock_type lock_type) override;
|
||||||
|
|
||||||
|
ha_rows records_in_range(uint inx, const key_range *min_key,
|
||||||
|
const key_range *max_key,
|
||||||
|
page_range *pages) override;
|
||||||
|
|
||||||
|
int create(const char *name, TABLE *table_arg,
|
||||||
|
HA_CREATE_INFO *create_info) override;
|
||||||
|
|
||||||
|
int delete_table(const char *name) override;
|
||||||
|
|
||||||
|
int rename_table(const char *from, const char *to) override;
|
||||||
|
|
||||||
|
int multi_range_read_init(RANGE_SEQ_IF *seq, void *seq_init_param,
|
||||||
|
uint n_ranges, uint mode,
|
||||||
|
HANDLER_BUFFER *buf) override;
|
||||||
|
|
||||||
|
int multi_range_read_next(range_id_t *range_info) override;
|
||||||
|
|
||||||
|
ha_rows multi_range_read_info_const(uint keyno, RANGE_SEQ_IF *seq,
|
||||||
|
void *seq_init_param, uint n_ranges,
|
||||||
|
uint *bufsz, uint *flags, ha_rows limit,
|
||||||
|
Cost_estimate *cost) override;
|
||||||
|
|
||||||
|
ha_rows multi_range_read_info(uint keyno, uint n_ranges, uint keys,
|
||||||
|
uint key_parts, uint *bufsz, uint *flags,
|
||||||
|
Cost_estimate *cost) override;
|
||||||
|
|
||||||
|
int multi_range_read_explain_info(uint mrr_mode, char *str,
|
||||||
|
size_t size) override;
|
||||||
|
|
||||||
|
Item *idx_cond_push(uint keyno, Item *idx_cond) override;
|
||||||
|
|
||||||
|
int info_low(uint flag, bool is_analyze);
|
||||||
|
|
||||||
|
/** The multi range read session object */
|
||||||
|
DsMrr_impl m_ds_mrr;
|
||||||
|
|
||||||
|
/** Flags that specificy the handler instance (table) capability. */
|
||||||
|
Table_flags m_int_table_flags;
|
||||||
|
|
||||||
|
/** Index into the server's primary key meta-data table->key_info{} */
|
||||||
|
uint m_primary_key;
|
||||||
|
|
||||||
|
/** this is set to 1 when we are starting a table scan but have
|
||||||
|
not yet fetched any row, else false */
|
||||||
|
bool m_start_of_scan;
|
||||||
|
};
|
||||||
|
|
||||||
|
static MYSQL_THDVAR_STR(server_ip, PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC,
|
||||||
|
"VIDEX server address (host:port)", nullptr, nullptr,
|
||||||
|
"127.0.0.1:5001");
|
||||||
|
|
||||||
|
static MYSQL_THDVAR_STR(options, PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC,
|
||||||
|
"VIDEX connection options (JSON format)", nullptr,
|
||||||
|
nullptr, "{}");
|
||||||
|
|
||||||
|
static struct st_mysql_sys_var *videx_system_variables[]= {
|
||||||
|
MYSQL_SYSVAR(server_ip),
|
||||||
|
MYSQL_SYSVAR(options), NULL};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write callback function for cURL.
|
||||||
|
*
|
||||||
|
* @param contents Pointer to the received data.
|
||||||
|
* @param size Size of each data element.
|
||||||
|
* @param nmemb Number of data elements.
|
||||||
|
* @param outString Pointer to the string where the data will be appended.
|
||||||
|
* @return The total size of the data processed.
|
||||||
|
*/
|
||||||
|
size_t write_callback(void *contents, size_t size, size_t nmemb,
|
||||||
|
std::string *outString)
|
||||||
|
{
|
||||||
|
size_t totalSize= size * nmemb;
|
||||||
|
outString->append((char *) contents, totalSize);
|
||||||
|
return totalSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a request to the Videx HTTP server and validates the response.
|
||||||
|
* If the response is successful (code=200), the passed-in &request will be
|
||||||
|
* filled.
|
||||||
|
*
|
||||||
|
* @param request The VidexJsonItem object containing the request data.
|
||||||
|
* @param res_json The VidexStringMap object to store the response data.
|
||||||
|
* @param thd Pointer to the current thread's THD object.
|
||||||
|
* @return 0 if the request is successful, 1 otherwise.
|
||||||
|
*/
|
||||||
|
int ask_from_videx_http(VidexJsonItem &request, VidexStringMap &res_json,
|
||||||
|
THD *thd)
|
||||||
|
{
|
||||||
|
const char *host_ip= THDVAR(thd, server_ip);
|
||||||
|
if (!host_ip)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *videx_options= THDVAR(thd, options);
|
||||||
|
if (!videx_options)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
DBUG_PRINT("info", ("VIDEX OPTIONS: %s IP: %s", videx_options, host_ip));
|
||||||
|
request.add_property("videx_options", videx_options);
|
||||||
|
|
||||||
|
std::string url= std::string("http://") + host_ip + "/ask_videx";
|
||||||
|
CURL *curl;
|
||||||
|
CURLcode res_code;
|
||||||
|
std::string readBuffer;
|
||||||
|
|
||||||
|
curl= curl_easy_init();
|
||||||
|
if (curl)
|
||||||
|
{
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||||
|
curl_easy_setopt(curl, CURLOPT_POST, 1);
|
||||||
|
|
||||||
|
std::string request_str= request.to_json();
|
||||||
|
DBUG_PRINT("info", ("request_str: %s", request_str.c_str()));
|
||||||
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request_str.c_str());
|
||||||
|
|
||||||
|
// Set the headers
|
||||||
|
struct curl_slist *headers= NULL;
|
||||||
|
headers= curl_slist_append(headers, "Content-Type: application/json");
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
|
||||||
|
|
||||||
|
// Disallow connection reuse, so libcurl will close the connection
|
||||||
|
// immediately after completing a request.
|
||||||
|
curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1L);
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
||||||
|
|
||||||
|
res_code= curl_easy_perform(curl);
|
||||||
|
if (res_code != CURLE_OK)
|
||||||
|
{
|
||||||
|
sql_print_warning(
|
||||||
|
"VIDEX: access videx_server failed res_code != curle_ok: %s",
|
||||||
|
host_ip);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int code;
|
||||||
|
std::string message;
|
||||||
|
int error=
|
||||||
|
videx_parse_simple_json(readBuffer.c_str(), code, message, res_json);
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
sql_print_warning("VIDEX: JSON parse error: %s", message.c_str());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (message == "OK")
|
||||||
|
{
|
||||||
|
DBUG_PRINT("info", ("access videx_server success: %s", host_ip));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sql_print_warning(
|
||||||
|
"VIDEX: access videx_server success but msg != OK: %s",
|
||||||
|
readBuffer.c_str());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sql_print_warning("VIDEX: access videx_server failed curl = false: %s",
|
||||||
|
host_ip);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a request to the Videx HTTP server and aims to return an integer
|
||||||
|
* value. If the response is successful (code=200), it extracts the integer
|
||||||
|
* value from `response_json["value"]`.
|
||||||
|
*
|
||||||
|
* @param request The VidexJsonItem object containing the request data.
|
||||||
|
* @param result_str Reference to the string where the result value will be
|
||||||
|
* stored.
|
||||||
|
* @param thd Pointer to the current thread's THD object.
|
||||||
|
* @return 0 if the request is successful, 1 otherwise.
|
||||||
|
*/
|
||||||
|
int ask_from_videx_http(VidexJsonItem &request, std::string &result_str,
|
||||||
|
THD *thd)
|
||||||
|
{
|
||||||
|
VidexJsonItem result_item;
|
||||||
|
VidexStringMap res_json_string_map;
|
||||||
|
int error= ask_from_videx_http(request, res_json_string_map, thd);
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
else if (videx_contains_key(res_json_string_map, "value"))
|
||||||
|
{
|
||||||
|
result_str= res_json_string_map["value"];
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// HTTP request returned successfully, but the result does not contain a
|
||||||
|
// "value" field. This indicates an invalid format. Set the error_code to 1
|
||||||
|
// and return.
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static handler *videx_create_handler(handlerton *hton, TABLE_SHARE *table,
|
||||||
|
MEM_ROOT *mem_root);
|
||||||
|
|
||||||
|
handlerton *videx_hton;
|
||||||
|
|
||||||
|
static const char *ha_videx_exts[]= {NullS};
|
||||||
|
|
||||||
|
#ifdef HAVE_PSI_INTERFACE
|
||||||
|
static PSI_mutex_key ex_key_mutex_videx_share_mutex;
|
||||||
|
|
||||||
|
static PSI_mutex_info all_videx_mutexes[]= {
|
||||||
|
{&ex_key_mutex_videx_share_mutex, "videx_share::mutex", 0}};
|
||||||
|
|
||||||
|
static void init_videx_psi_keys()
|
||||||
|
{
|
||||||
|
const char *category= "videx";
|
||||||
|
int count;
|
||||||
|
|
||||||
|
count= array_elements(all_videx_mutexes);
|
||||||
|
mysql_mutex_register(category, all_videx_mutexes, count);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static void init_videx_psi_keys() {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
videx_share::videx_share()
|
||||||
|
{
|
||||||
|
thr_lock_init(&lock);
|
||||||
|
mysql_mutex_init(ex_key_mutex_videx_share_mutex, &mutex, MY_MUTEX_INIT_FAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void videx_update_optimizer_costs(OPTIMIZER_COSTS *costs)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The following values were taken from MariaDB Server 11.8 in the function
|
||||||
|
* innobase_update_optimizer_costs(OPTIMIZER_COSTS *costs). See more details
|
||||||
|
* in
|
||||||
|
* https://github.com/MariaDB/server/blob/11.8/storage/innobase/handler/ha_innodb.cc
|
||||||
|
*/
|
||||||
|
costs->row_next_find_cost= 0.00007013;
|
||||||
|
costs->row_lookup_cost= 0.00076597;
|
||||||
|
costs->key_next_find_cost= 0.00009900;
|
||||||
|
costs->key_lookup_cost= 0.00079112;
|
||||||
|
costs->row_copy_cost= 0.00006087;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int videx_init(void *p)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("videx_init");
|
||||||
|
|
||||||
|
init_videx_psi_keys();
|
||||||
|
|
||||||
|
videx_hton= static_cast<handlerton *>(p);
|
||||||
|
|
||||||
|
videx_hton->create= videx_create_handler;
|
||||||
|
videx_hton->flags= HTON_SUPPORTS_EXTENDED_KEYS | HTON_SUPPORTS_FOREIGN_KEYS |
|
||||||
|
HTON_NATIVE_SYS_VERSIONING | HTON_WSREP_REPLICATION |
|
||||||
|
HTON_REQUIRES_CLOSE_AFTER_TRUNCATE |
|
||||||
|
HTON_TRUNCATE_REQUIRES_EXCLUSIVE_USE |
|
||||||
|
HTON_REQUIRES_NOTIFY_TABLEDEF_CHANGED_AFTER_COMMIT;
|
||||||
|
|
||||||
|
videx_hton->update_optimizer_costs= videx_update_optimizer_costs;
|
||||||
|
videx_hton->tablefile_extensions= ha_videx_exts;
|
||||||
|
|
||||||
|
DBUG_RETURN(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
videx_share *ha_videx::get_share()
|
||||||
|
{
|
||||||
|
videx_share *tmp_share;
|
||||||
|
|
||||||
|
DBUG_ENTER("ha_videx::get_share()");
|
||||||
|
|
||||||
|
lock_shared_ha_data();
|
||||||
|
if (!(tmp_share= static_cast<videx_share *>(get_ha_share_ptr())))
|
||||||
|
{
|
||||||
|
tmp_share= new videx_share;
|
||||||
|
if (!tmp_share)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
set_ha_share_ptr(static_cast<Handler_share *>(tmp_share));
|
||||||
|
}
|
||||||
|
err:
|
||||||
|
unlock_shared_ha_data();
|
||||||
|
DBUG_RETURN(tmp_share);
|
||||||
|
}
|
||||||
|
|
||||||
|
static handler *videx_create_handler(handlerton *hton, TABLE_SHARE *table,
|
||||||
|
MEM_ROOT *mem_root)
|
||||||
|
{
|
||||||
|
return new (mem_root) ha_videx(hton, table);
|
||||||
|
}
|
||||||
|
|
||||||
|
ha_videx::ha_videx(handlerton *hton, TABLE_SHARE *table_arg)
|
||||||
|
: handler(hton, table_arg),
|
||||||
|
m_int_table_flags(
|
||||||
|
HA_REC_NOT_IN_SEQ | HA_NULL_IN_KEY | HA_CAN_VIRTUAL_COLUMNS |
|
||||||
|
HA_CAN_INDEX_BLOBS | HA_CAN_SQL_HANDLER |
|
||||||
|
HA_REQUIRES_KEY_COLUMNS_FOR_DELETE |
|
||||||
|
HA_PRIMARY_KEY_REQUIRED_FOR_POSITION | HA_PRIMARY_KEY_IN_READ_INDEX |
|
||||||
|
HA_BINLOG_ROW_CAPABLE | HA_CAN_GEOMETRY | HA_PARTIAL_COLUMN_READ |
|
||||||
|
HA_TABLE_SCAN_ON_INDEX | HA_CAN_EXPORT | HA_ONLINE_ANALYZE |
|
||||||
|
HA_CAN_RTREEKEYS | HA_CAN_TABLES_WITHOUT_ROLLBACK |
|
||||||
|
HA_CAN_ONLINE_BACKUPS | HA_CONCURRENT_OPTIMIZE | HA_CAN_SKIP_LOCKED),
|
||||||
|
m_start_of_scan()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ha_videx::~ha_videx()= default;
|
||||||
|
|
||||||
|
const char *ha_videx::table_type() const { return "VIDEX"; }
|
||||||
|
|
||||||
|
handler::Table_flags ha_videx::table_flags() const
|
||||||
|
{
|
||||||
|
THD *thd= ha_thd();
|
||||||
|
handler::Table_flags flags= m_int_table_flags;
|
||||||
|
|
||||||
|
if (thd_sql_command(thd) == SQLCOM_CREATE_TABLE)
|
||||||
|
{
|
||||||
|
flags|= HA_REQUIRE_PRIMARY_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thd_tx_isolation(thd) <= ISO_READ_COMMITTED)
|
||||||
|
{
|
||||||
|
return (flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (flags | HA_BINLOG_STMT_CAPABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong ha_videx::index_flags(uint key, uint, bool) const
|
||||||
|
{
|
||||||
|
if (table_share->key_info[key].algorithm == HA_KEY_ALG_FULLTEXT)
|
||||||
|
{
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For spatial index, we don't support descending scan
|
||||||
|
and ICP so far. */
|
||||||
|
if (table_share->key_info[key].algorithm == HA_KEY_ALG_RTREE)
|
||||||
|
{
|
||||||
|
return HA_READ_NEXT | HA_READ_ORDER | HA_READ_RANGE | HA_KEYREAD_ONLY |
|
||||||
|
HA_KEY_SCAN_NOT_ROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong flags= key == table_share->primary_key
|
||||||
|
? HA_CLUSTERED_INDEX
|
||||||
|
: HA_KEYREAD_ONLY | HA_DO_RANGE_FILTER_PUSHDOWN;
|
||||||
|
|
||||||
|
flags|= HA_READ_NEXT | HA_READ_PREV | HA_READ_ORDER | HA_READ_RANGE |
|
||||||
|
HA_DO_INDEX_COND_PUSHDOWN;
|
||||||
|
return (flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint ha_videx::max_supported_keys() const { return (MAX_KEY); }
|
||||||
|
|
||||||
|
uint ha_videx::max_supported_key_length() const
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This value was taken from MariaDB Server 11.8 in the function
|
||||||
|
* innobase_max_supported_key_length() See more details in
|
||||||
|
* https://github.com/MariaDB/server/blob/11.8/storage/innobase/handler/ha_innodb.cc
|
||||||
|
*/
|
||||||
|
return (3500);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint ha_videx::max_supported_key_part_length() const
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This value was taken from MariaDB Server 11.8 in the function
|
||||||
|
* innobase_max_supported_key_part_length() See more details in
|
||||||
|
* https://github.com/MariaDB/server/blob/11.8/storage/innobase/handler/ha_innodb.cc
|
||||||
|
*/
|
||||||
|
return (3072);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ha_videx::column_bitmaps_signal()
|
||||||
|
{
|
||||||
|
DBUG_ENTER("ha_videx::column_bitmaps_signal");
|
||||||
|
// TODO: handle indexed virtual columns for VIDEX engine
|
||||||
|
DBUG_VOID_RETURN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief
|
||||||
|
Used for opening tables. The name will be the name of the file.
|
||||||
|
|
||||||
|
@details
|
||||||
|
A table is opened when it needs to be opened; e.g. when a request comes
|
||||||
|
in for a SELECT on the table (tables are not open and closed for each
|
||||||
|
request, they are cached).
|
||||||
|
|
||||||
|
Called from handler.cc by handler::ha_open(). The server opens all
|
||||||
|
tables by calling ha_open() which then calls the handler specific open().
|
||||||
|
|
||||||
|
@see
|
||||||
|
handler::ha_open() in handler.cc
|
||||||
|
*/
|
||||||
|
|
||||||
|
int ha_videx::open(const char *name, int mode, uint test_if_locked)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("ha_videx::open");
|
||||||
|
|
||||||
|
if (!(share= get_share()))
|
||||||
|
DBUG_RETURN(1);
|
||||||
|
thr_lock_data_init(&share->lock, &lock, NULL);
|
||||||
|
|
||||||
|
m_primary_key= table->s->primary_key;
|
||||||
|
|
||||||
|
if (m_primary_key >= MAX_KEY)
|
||||||
|
{
|
||||||
|
ref_length= 6; // DATA_ROW_ID_LEN;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ref_length= table->key_info[m_primary_key].key_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This value was taken from MariaDB Server 11.8 in the function open(),
|
||||||
|
* where stats.block_size is set to srv_page_size. See more details in
|
||||||
|
* https://github.com/MariaDB/server/blob/11.8/storage/innobase/handler/ha_innodb.cc
|
||||||
|
*/
|
||||||
|
stats.block_size= 16384;
|
||||||
|
|
||||||
|
info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST |
|
||||||
|
HA_STATUS_OPEN);
|
||||||
|
|
||||||
|
DBUG_RETURN(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief
|
||||||
|
Closes a table.
|
||||||
|
|
||||||
|
@details
|
||||||
|
Called from sql_base.cc, sql_select.cc, and table.cc. In sql_select.cc
|
||||||
|
it is only used to close up temporary tables or during the process where a
|
||||||
|
temporary table is converted over to being a myisam table.
|
||||||
|
|
||||||
|
For sql_base.cc look at close_data_tables().
|
||||||
|
|
||||||
|
@see
|
||||||
|
sql_base.cc, sql_select.cc and table.cc
|
||||||
|
*/
|
||||||
|
|
||||||
|
int ha_videx::close(void)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("ha_videx::close");
|
||||||
|
DBUG_RETURN(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
handler *ha_videx::clone(const char *name, /*!< in: table name */
|
||||||
|
MEM_ROOT *mem_root) /*!< in: memory context */
|
||||||
|
{
|
||||||
|
DBUG_ENTER("ha_videx::clone");
|
||||||
|
DBUG_RETURN(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
rnd_init() is called when the system wants the storage engine to do a table
|
||||||
|
scan. Not required for VIDEX.
|
||||||
|
*/
|
||||||
|
int ha_videx::rnd_init(bool scan)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("ha_videx::rnd_init");
|
||||||
|
DBUG_RETURN(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
This is called for each row of the table scan. Not required for VIDEX.
|
||||||
|
*/
|
||||||
|
int ha_videx::rnd_next(uchar *buf)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
DBUG_ENTER("ha_videx::rnd_next");
|
||||||
|
rc= HA_ERR_END_OF_FILE;
|
||||||
|
DBUG_RETURN(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
This is like rnd_next, but you are given a position to use
|
||||||
|
to determine the row. Not required for VIDEX.
|
||||||
|
*/
|
||||||
|
int ha_videx::rnd_pos(uchar *buf, uchar *pos)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
DBUG_ENTER("ha_videx::rnd_pos");
|
||||||
|
rc= HA_ERR_WRONG_COMMAND;
|
||||||
|
DBUG_RETURN(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
position() is called after each call to rnd_next() if the data needs
|
||||||
|
to be ordered. Not required for VIDEX.
|
||||||
|
*/
|
||||||
|
void ha_videx::position(const uchar *record)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("ha_videx::position");
|
||||||
|
DBUG_VOID_RETURN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns table statistics to the server; fills fields in the handler object.
|
||||||
|
@return 0 on success or HA_ERR_*.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int ha_videx::info(uint flag) { return (info_low(flag, false)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
Converts a MySQL table lock in 'lock' to the engine representation.
|
||||||
|
Not required for VIDEX.
|
||||||
|
@return pointer to the current element in 'to'.
|
||||||
|
*/
|
||||||
|
|
||||||
|
THR_LOCK_DATA **ha_videx::store_lock(THD *thd, THR_LOCK_DATA **to,
|
||||||
|
enum thr_lock_type lock_type)
|
||||||
|
{
|
||||||
|
if (lock_type != TL_IGNORE && lock.type == TL_UNLOCK)
|
||||||
|
lock.type= lock_type;
|
||||||
|
*to++= &lock;
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimates the number of index records in a specific range via VIDEX HTTP.
|
||||||
|
* Working principle: VIDEX will forward requests via HTTP to the external
|
||||||
|
* VIDEX-Statistic-Server (launched in a RESTful manner). If the request fails,
|
||||||
|
* a default value of 10 will be returned. Reference link: For an
|
||||||
|
* implementation of VIDEX-Statistic-Server, see
|
||||||
|
* https://github.com/bytedance/videx. We plan to introduce it into the
|
||||||
|
* official MariaDB repository in a subsequent PR.
|
||||||
|
* @param keynr: The index number of the key
|
||||||
|
* @param min_key: The minimum key value of the range
|
||||||
|
* @param max_key: The maximum key value of the range
|
||||||
|
* @param pages: Page range information (present as a parameter but not used in
|
||||||
|
* the function)
|
||||||
|
* @return estimated number of rows.
|
||||||
|
*/
|
||||||
|
|
||||||
|
ha_rows ha_videx::records_in_range(uint keynr, const key_range *min_key,
|
||||||
|
const key_range *max_key, page_range *pages)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("ha_videx::records_in_range");
|
||||||
|
KEY *key;
|
||||||
|
active_index= keynr;
|
||||||
|
key= table->key_info + active_index;
|
||||||
|
|
||||||
|
VidexJsonItem request_item= construct_request(
|
||||||
|
table->s->db.str, table->s->table_name.str, __PRETTY_FUNCTION__);
|
||||||
|
serializeKeyRangeToJson(min_key, max_key, key, &request_item);
|
||||||
|
|
||||||
|
std::string n_rows_str;
|
||||||
|
ha_rows n_rows;
|
||||||
|
THD *thd= ha_thd();
|
||||||
|
int error= ask_from_videx_http(request_item, n_rows_str, thd);
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
n_rows= 10; // default number to force index
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
n_rows= std::stoull(n_rows_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid returning 0 to the optimizer, similar to InnoDB's behavior
|
||||||
|
if (n_rows == 0)
|
||||||
|
{
|
||||||
|
n_rows= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
DBUG_RETURN(n_rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a new table to an VIDEX database. Not required for VIDEX. */
|
||||||
|
|
||||||
|
int ha_videx::create(const char *name, TABLE *table_arg,
|
||||||
|
HA_CREATE_INFO *create_info)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("ha_videx::create");
|
||||||
|
DBUG_PRINT("info", ("name: %s, table_arg: %p, create_info: %p", name,
|
||||||
|
table_arg, create_info));
|
||||||
|
DBUG_RETURN(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ha_videx::delete_table(const char *name)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("ha_videx::delete_table");
|
||||||
|
DBUG_RETURN(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ha_videx::rename_table(const char *from, const char *to)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("ha_videx::rename_table");
|
||||||
|
DBUG_RETURN(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
DS-MRR implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int ha_videx::multi_range_read_init(RANGE_SEQ_IF *seq, void *seq_init_param,
|
||||||
|
uint n_ranges, uint mode,
|
||||||
|
HANDLER_BUFFER *buf)
|
||||||
|
{
|
||||||
|
return (m_ds_mrr.dsmrr_init(this, seq, seq_init_param, n_ranges, mode, buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
int ha_videx::multi_range_read_next(range_id_t *range_info)
|
||||||
|
{
|
||||||
|
return (m_ds_mrr.dsmrr_next(range_info));
|
||||||
|
}
|
||||||
|
|
||||||
|
ha_rows ha_videx::multi_range_read_info_const(uint keyno, RANGE_SEQ_IF *seq,
|
||||||
|
void *seq_init_param,
|
||||||
|
uint n_ranges, uint *bufsz,
|
||||||
|
uint *flags, ha_rows limit,
|
||||||
|
Cost_estimate *cost)
|
||||||
|
{
|
||||||
|
m_ds_mrr.init(this, table);
|
||||||
|
|
||||||
|
return (m_ds_mrr.dsmrr_info_const(keyno, seq, seq_init_param, n_ranges,
|
||||||
|
bufsz, flags, limit, cost));
|
||||||
|
}
|
||||||
|
|
||||||
|
ha_rows ha_videx::multi_range_read_info(uint keyno, uint n_ranges, uint keys,
|
||||||
|
uint key_parts, uint *bufsz,
|
||||||
|
uint *flags, Cost_estimate *cost)
|
||||||
|
{
|
||||||
|
m_ds_mrr.init(this, table);
|
||||||
|
return (m_ds_mrr.dsmrr_info(keyno, n_ranges, keys, key_parts, bufsz, flags,
|
||||||
|
cost));
|
||||||
|
}
|
||||||
|
|
||||||
|
int ha_videx::multi_range_read_explain_info(uint mrr_mode, char *str,
|
||||||
|
size_t size)
|
||||||
|
{
|
||||||
|
return (m_ds_mrr.dsmrr_explain_info(mrr_mode, str, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to push down an index condition.
|
||||||
|
* @param keyno MySQL key number
|
||||||
|
* @param idx_cond Index condition to be checked
|
||||||
|
* @return Part of idx_cond which the handler will not evaluate
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Item *ha_videx::idx_cond_push(uint keyno, class Item *idx_cond)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("ha_videx::idx_cond_push");
|
||||||
|
DBUG_ASSERT(keyno != MAX_KEY);
|
||||||
|
DBUG_ASSERT(idx_cond != NULL);
|
||||||
|
|
||||||
|
pushed_idx_cond= idx_cond;
|
||||||
|
pushed_idx_cond_keyno= keyno;
|
||||||
|
in_range_check_pushed_down= TRUE;
|
||||||
|
DBUG_RETURN(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A very important function. When a query arrives, MariaDB calls this function
|
||||||
|
* to initialize the information of a table. In a session, this function is
|
||||||
|
* only called once by the MariaDB query optimizer. VIDEX requests the
|
||||||
|
* `videx_stats_server` to return various statistics of a single table,
|
||||||
|
* including: stat_n_rows: The number of rows in the table.
|
||||||
|
* stat_clustered_index_size: The size of the clustered index.
|
||||||
|
* stat_sum_of_other_index_sizes: The sum of sizes of other indexes.
|
||||||
|
* data_file_length: The size of the data file.
|
||||||
|
* index_file_length: The length of the index file.
|
||||||
|
* data_free_length: The length of free space in the data file.
|
||||||
|
*
|
||||||
|
* [Very Important]
|
||||||
|
* rec_per_key of several columns: require NDV algorithm
|
||||||
|
*
|
||||||
|
* Returns statistics information of the table to the MariaDB interpreter, in
|
||||||
|
* various fields of the handle object.
|
||||||
|
* @param[in] flag what information is requested
|
||||||
|
* @param[in] is_analyze True if called from "::analyze()".
|
||||||
|
* @return HA_ERR_* error code or 0
|
||||||
|
*/
|
||||||
|
|
||||||
|
int ha_videx::info_low(uint flag, bool is_analyze)
|
||||||
|
{
|
||||||
|
uint64_t n_rows;
|
||||||
|
|
||||||
|
DBUG_ENTER("ha_videx::info_low");
|
||||||
|
DEBUG_SYNC_C("ha_videx_info_low");
|
||||||
|
|
||||||
|
// construct request
|
||||||
|
VidexStringMap res_json;
|
||||||
|
VidexJsonItem request_item= construct_request(
|
||||||
|
table->s->db.str, table->s->table_name.str, __PRETTY_FUNCTION__);
|
||||||
|
for (uint i= 0; i < table->s->keys; i++)
|
||||||
|
{
|
||||||
|
KEY *key= &table->key_info[i];
|
||||||
|
VidexJsonItem *keyItem= request_item.create("key");
|
||||||
|
keyItem->add_property("name", key->name.str);
|
||||||
|
keyItem->add_property_nonan("key_length", key->key_length);
|
||||||
|
for (ulong j= 0; j < key->usable_key_parts; j++)
|
||||||
|
{
|
||||||
|
if ((key->flags & HA_KEY_ALG_FULLTEXT) ||
|
||||||
|
(key->flags & HA_SPATIAL_legacy))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
VidexJsonItem *field= keyItem->create("field");
|
||||||
|
field->add_property("name", key->key_part[j].field->field_name.str);
|
||||||
|
field->add_property_nonan("store_length", key->key_part[j].store_length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DBUG_PRINT("info", ("Request JSON: %s", request_item.to_json().c_str()));
|
||||||
|
|
||||||
|
THD *thd= ha_thd();
|
||||||
|
int error= ask_from_videx_http(request_item, res_json, thd);
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
DBUG_RETURN(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// validate the returned json
|
||||||
|
// stat_n_rows, stat_clustered_index_size, stat_sum_of_other_index_sizes,
|
||||||
|
// data_file_length, index_file_length, data_free_length
|
||||||
|
if (!(videx_contains_key(res_json, "stat_n_rows") &&
|
||||||
|
videx_contains_key(res_json, "stat_clustered_index_size") &&
|
||||||
|
videx_contains_key(res_json, "stat_sum_of_other_index_sizes") &&
|
||||||
|
videx_contains_key(res_json, "data_file_length") &&
|
||||||
|
videx_contains_key(res_json, "index_file_length") &&
|
||||||
|
videx_contains_key(res_json, "data_free_length")))
|
||||||
|
{
|
||||||
|
sql_print_warning("VIDEX: res_json data error=0 but miss some key.");
|
||||||
|
DBUG_RETURN(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flag & HA_STATUS_VARIABLE)
|
||||||
|
{
|
||||||
|
n_rows= std::stoull(res_json["stat_n_rows"]);
|
||||||
|
if (n_rows == 0 && !(flag & (HA_STATUS_TIME | HA_STATUS_OPEN)))
|
||||||
|
{
|
||||||
|
n_rows++;
|
||||||
|
}
|
||||||
|
|
||||||
|
stats.records= (ha_rows) n_rows;
|
||||||
|
stats.deleted= 0;
|
||||||
|
|
||||||
|
stats.data_file_length= std::stoull(res_json["data_file_length"]);
|
||||||
|
stats.index_file_length= std::stoull(res_json["index_file_length"]);
|
||||||
|
if (flag & HA_STATUS_VARIABLE_EXTRA)
|
||||||
|
{
|
||||||
|
stats.delete_length= std::stoull(res_json["data_free_length"]);
|
||||||
|
}
|
||||||
|
stats.check_time= 0;
|
||||||
|
stats.mrr_length_per_rec=
|
||||||
|
(uint) ref_length + 8; // 8 = max(sizeof(void *));
|
||||||
|
|
||||||
|
if (stats.records == 0)
|
||||||
|
{
|
||||||
|
stats.mean_rec_length= 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stats.mean_rec_length= (ulong) (stats.data_file_length / stats.records);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flag & HA_STATUS_CONST)
|
||||||
|
{
|
||||||
|
for (uint i= 0; i < table->s->keys; i++)
|
||||||
|
{
|
||||||
|
ulong j;
|
||||||
|
KEY *key= &table->key_info[i];
|
||||||
|
|
||||||
|
for (j= 0; j < key->ext_key_parts; j++)
|
||||||
|
{
|
||||||
|
|
||||||
|
if ((key->algorithm == HA_KEY_ALG_FULLTEXT) ||
|
||||||
|
(key->algorithm == HA_KEY_ALG_RTREE))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The #@# separator combines the index name and field name, making it
|
||||||
|
// easier to extract the corresponding statistical values from the JSON
|
||||||
|
// response.
|
||||||
|
std::string concat_key=
|
||||||
|
"rec_per_key #@# " + std::string(key->name.str) + " #@# " +
|
||||||
|
std::string(key->key_part[j].field->field_name.str);
|
||||||
|
ulong rec_per_key_int= 0;
|
||||||
|
if (videx_contains_key(res_json, concat_key.c_str()))
|
||||||
|
{
|
||||||
|
rec_per_key_int= std::stoul(res_json[concat_key.c_str()]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rec_per_key_int= stats.records;
|
||||||
|
}
|
||||||
|
if (rec_per_key_int == 0)
|
||||||
|
{
|
||||||
|
rec_per_key_int= 1;
|
||||||
|
}
|
||||||
|
key->rec_per_key[j]= rec_per_key_int;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DBUG_RETURN(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct st_mysql_storage_engine videx_storage_engine= {
|
||||||
|
MYSQL_HANDLERTON_INTERFACE_VERSION};
|
||||||
|
|
||||||
|
maria_declare_plugin(videx){
|
||||||
|
MYSQL_STORAGE_ENGINE_PLUGIN,
|
||||||
|
&videx_storage_engine,
|
||||||
|
"VIDEX",
|
||||||
|
"Rong Kang, Haibo Yang",
|
||||||
|
"Disaggregated, Extensible Virtual Index Engine for What-If Analysis",
|
||||||
|
PLUGIN_LICENSE_GPL,
|
||||||
|
videx_init, /* Plugin Init */
|
||||||
|
NULL, /* Plugin Deinit */
|
||||||
|
0x0001, /* version number (0.1) */
|
||||||
|
NULL, /* status variables */
|
||||||
|
videx_system_variables, /* system variables */
|
||||||
|
"0.1", /* string version */
|
||||||
|
MariaDB_PLUGIN_MATURITY_EXPERIMENTAL /* maturity */
|
||||||
|
} maria_declare_plugin_end;
|
||||||
48
storage/videx/mysql-test/videx/create-table-and-index.result
Normal file
48
storage/videx/mysql-test/videx/create-table-and-index.result
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
CREATE TABLE `orders` (
|
||||||
|
`O_ORDERKEY` int NOT NULL,
|
||||||
|
`O_CUSTKEY` int NOT NULL,
|
||||||
|
`O_ORDERSTATUS` char(1) NOT NULL,
|
||||||
|
`O_TOTALPRICE` decimal(15,2) NOT NULL,
|
||||||
|
`O_ORDERDATE` date NOT NULL,
|
||||||
|
`O_ORDERPRIORITY` char(15) NOT NULL,
|
||||||
|
`O_CLERK` char(15) NOT NULL,
|
||||||
|
`O_SHIPPRIORITY` int NOT NULL,
|
||||||
|
`O_COMMENT` varchar(79) NOT NULL,
|
||||||
|
PRIMARY KEY (`O_ORDERKEY`),
|
||||||
|
KEY `ORDERS_FK1` (`O_CUSTKEY`)
|
||||||
|
) ENGINE=VIDEX;
|
||||||
|
SET SESSION videx_server_ip=NULL;
|
||||||
|
SHOW CREATE TABLE orders;
|
||||||
|
Table Create Table
|
||||||
|
orders CREATE TABLE `orders` (
|
||||||
|
`O_ORDERKEY` int(11) NOT NULL,
|
||||||
|
`O_CUSTKEY` int(11) NOT NULL,
|
||||||
|
`O_ORDERSTATUS` char(1) NOT NULL,
|
||||||
|
`O_TOTALPRICE` decimal(15,2) NOT NULL,
|
||||||
|
`O_ORDERDATE` date NOT NULL,
|
||||||
|
`O_ORDERPRIORITY` char(15) NOT NULL,
|
||||||
|
`O_CLERK` char(15) NOT NULL,
|
||||||
|
`O_SHIPPRIORITY` int(11) NOT NULL,
|
||||||
|
`O_COMMENT` varchar(79) NOT NULL,
|
||||||
|
PRIMARY KEY (`O_ORDERKEY`),
|
||||||
|
KEY `ORDERS_FK1` (`O_CUSTKEY`)
|
||||||
|
) ENGINE=VIDEX DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci
|
||||||
|
CREATE INDEX idx_orders_status_date ON orders (O_ORDERSTATUS, O_ORDERDATE);
|
||||||
|
SHOW CREATE TABLE orders;
|
||||||
|
Table Create Table
|
||||||
|
orders CREATE TABLE `orders` (
|
||||||
|
`O_ORDERKEY` int(11) NOT NULL,
|
||||||
|
`O_CUSTKEY` int(11) NOT NULL,
|
||||||
|
`O_ORDERSTATUS` char(1) NOT NULL,
|
||||||
|
`O_TOTALPRICE` decimal(15,2) NOT NULL,
|
||||||
|
`O_ORDERDATE` date NOT NULL,
|
||||||
|
`O_ORDERPRIORITY` char(15) NOT NULL,
|
||||||
|
`O_CLERK` char(15) NOT NULL,
|
||||||
|
`O_SHIPPRIORITY` int(11) NOT NULL,
|
||||||
|
`O_COMMENT` varchar(79) NOT NULL,
|
||||||
|
PRIMARY KEY (`O_ORDERKEY`),
|
||||||
|
KEY `ORDERS_FK1` (`O_CUSTKEY`),
|
||||||
|
KEY `idx_orders_status_date` (`O_ORDERSTATUS`,`O_ORDERDATE`)
|
||||||
|
) ENGINE=VIDEX DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci
|
||||||
|
DROP TABLE orders;
|
||||||
|
SET SESSION videx_server_ip=DEFAULT;
|
||||||
27
storage/videx/mysql-test/videx/create-table-and-index.test
Normal file
27
storage/videx/mysql-test/videx/create-table-and-index.test
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
--source include/have_videx.inc
|
||||||
|
|
||||||
|
CREATE TABLE `orders` (
|
||||||
|
`O_ORDERKEY` int NOT NULL,
|
||||||
|
`O_CUSTKEY` int NOT NULL,
|
||||||
|
`O_ORDERSTATUS` char(1) NOT NULL,
|
||||||
|
`O_TOTALPRICE` decimal(15,2) NOT NULL,
|
||||||
|
`O_ORDERDATE` date NOT NULL,
|
||||||
|
`O_ORDERPRIORITY` char(15) NOT NULL,
|
||||||
|
`O_CLERK` char(15) NOT NULL,
|
||||||
|
`O_SHIPPRIORITY` int NOT NULL,
|
||||||
|
`O_COMMENT` varchar(79) NOT NULL,
|
||||||
|
PRIMARY KEY (`O_ORDERKEY`),
|
||||||
|
KEY `ORDERS_FK1` (`O_CUSTKEY`)
|
||||||
|
) ENGINE=VIDEX;
|
||||||
|
|
||||||
|
SET SESSION videx_server_ip=NULL;
|
||||||
|
|
||||||
|
SHOW CREATE TABLE orders;
|
||||||
|
|
||||||
|
CREATE INDEX idx_orders_status_date ON orders (O_ORDERSTATUS, O_ORDERDATE);
|
||||||
|
|
||||||
|
SHOW CREATE TABLE orders;
|
||||||
|
|
||||||
|
DROP TABLE orders;
|
||||||
|
|
||||||
|
SET SESSION videx_server_ip=DEFAULT;
|
||||||
5
storage/videx/mysql-test/videx/include/have_videx.inc
Normal file
5
storage/videx/mysql-test/videx/include/have_videx.inc
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
if (!`SELECT COUNT(*) FROM information_schema.ENGINES
|
||||||
|
WHERE (SUPPORT='YES' OR SUPPORT='DEFAULT') AND ENGINE='VIDEX'`)
|
||||||
|
{
|
||||||
|
skip Need VIDEX engine;
|
||||||
|
}
|
||||||
26
storage/videx/mysql-test/videx/set-debug-skip-http.result
Normal file
26
storage/videx/mysql-test/videx/set-debug-skip-http.result
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
CREATE TABLE `part` (
|
||||||
|
`P_PARTKEY` int NOT NULL,
|
||||||
|
`P_NAME` varchar(55) NOT NULL,
|
||||||
|
`P_MFGR` char(25) NOT NULL,
|
||||||
|
`P_BRAND` char(10) NOT NULL,
|
||||||
|
`P_TYPE` varchar(25) NOT NULL,
|
||||||
|
`P_SIZE` int NOT NULL,
|
||||||
|
`P_CONTAINER` char(10) NOT NULL,
|
||||||
|
`P_RETAILPRICE` decimal(15,2) NOT NULL,
|
||||||
|
`P_COMMENT` varchar(23) NOT NULL,
|
||||||
|
PRIMARY KEY (`P_PARTKEY`)
|
||||||
|
) ENGINE=VIDEX;
|
||||||
|
SET SESSION videx_server_ip=NULL;
|
||||||
|
SHOW SESSION VARIABLES LIKE 'videx%';
|
||||||
|
Variable_name Value
|
||||||
|
videx_options {}
|
||||||
|
videx_server_ip
|
||||||
|
EXPLAIN SELECT COUNT(*) from part;
|
||||||
|
id select_type table type possible_keys key key_len ref rows Extra
|
||||||
|
1 SIMPLE part ALL NULL NULL NULL NULL 0
|
||||||
|
DROP TABLE part;
|
||||||
|
SET SESSION videx_server_ip=DEFAULT;
|
||||||
|
SHOW SESSION VARIABLES LIKE 'videx%';
|
||||||
|
Variable_name Value
|
||||||
|
videx_options {}
|
||||||
|
videx_server_ip 127.0.0.1:5001
|
||||||
26
storage/videx/mysql-test/videx/set-debug-skip-http.test
Normal file
26
storage/videx/mysql-test/videx/set-debug-skip-http.test
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
--source include/have_videx.inc
|
||||||
|
|
||||||
|
CREATE TABLE `part` (
|
||||||
|
`P_PARTKEY` int NOT NULL,
|
||||||
|
`P_NAME` varchar(55) NOT NULL,
|
||||||
|
`P_MFGR` char(25) NOT NULL,
|
||||||
|
`P_BRAND` char(10) NOT NULL,
|
||||||
|
`P_TYPE` varchar(25) NOT NULL,
|
||||||
|
`P_SIZE` int NOT NULL,
|
||||||
|
`P_CONTAINER` char(10) NOT NULL,
|
||||||
|
`P_RETAILPRICE` decimal(15,2) NOT NULL,
|
||||||
|
`P_COMMENT` varchar(23) NOT NULL,
|
||||||
|
PRIMARY KEY (`P_PARTKEY`)
|
||||||
|
) ENGINE=VIDEX;
|
||||||
|
|
||||||
|
SET SESSION videx_server_ip=NULL;
|
||||||
|
|
||||||
|
SHOW SESSION VARIABLES LIKE 'videx%';
|
||||||
|
|
||||||
|
EXPLAIN SELECT COUNT(*) from part;
|
||||||
|
|
||||||
|
DROP TABLE part;
|
||||||
|
|
||||||
|
SET SESSION videx_server_ip=DEFAULT;
|
||||||
|
|
||||||
|
SHOW SESSION VARIABLES LIKE 'videx%';
|
||||||
2
storage/videx/mysql-test/videx/suite.opt
Normal file
2
storage/videx/mysql-test/videx/suite.opt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
--plugin-load-add=$HA_VIDEX_SO
|
||||||
|
--loose-videx
|
||||||
12
storage/videx/mysql-test/videx/suite.pm
Normal file
12
storage/videx/mysql-test/videx/suite.pm
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package My::Suite::Videx;
|
||||||
|
|
||||||
|
@ISA = qw(My::Suite);
|
||||||
|
|
||||||
|
return "No VIDEX" unless $ENV{HA_VIDEX_SO} or
|
||||||
|
$::mysqld_variables{'videx'} eq "ON";
|
||||||
|
|
||||||
|
return "Not run for embedded server" if $::opt_embedded_server;
|
||||||
|
|
||||||
|
sub is_default { 1 }
|
||||||
|
|
||||||
|
bless { };
|
||||||
509
storage/videx/videx_utils.cc
Normal file
509
storage/videx/videx_utils.cc
Normal file
@ -0,0 +1,509 @@
|
|||||||
|
/* Copyright (c) 2024 Bytedance Ltd. 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 also distributed 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 included with MySQL.
|
||||||
|
|
||||||
|
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 "videx_utils.h"
|
||||||
|
#include <mysql/service_thd_alloc.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It's to provide a simple but robust parsing function here,
|
||||||
|
* since rapid_json always encounters strange segmentation faults across
|
||||||
|
* platforms, especially on MacOS.
|
||||||
|
*
|
||||||
|
* @param json
|
||||||
|
* @param code
|
||||||
|
* @param message
|
||||||
|
* @param data_dict
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
|
||||||
|
int videx_parse_simple_json(const std::string &json, int &code,
|
||||||
|
std::string &message,
|
||||||
|
std::map<std::string, std::string> &data_dict)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// find code and message
|
||||||
|
std::size_t pos_code= json.find("\"code\":");
|
||||||
|
std::size_t pos_message= json.find("\"message\":");
|
||||||
|
std::size_t pos_data= json.find("\"data\":");
|
||||||
|
|
||||||
|
if (pos_code == std::string::npos || pos_message == std::string::npos ||
|
||||||
|
pos_data == std::string::npos)
|
||||||
|
{
|
||||||
|
throw std::invalid_argument("Missing essential components in JSON.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse code
|
||||||
|
std::size_t start= json.find_first_of("0123456789", pos_code);
|
||||||
|
std::size_t end= json.find(',', start);
|
||||||
|
code= std::stoi(json.substr(start, end - start));
|
||||||
|
|
||||||
|
// parse message
|
||||||
|
start= json.find('\"', pos_message + 10) + 1;
|
||||||
|
end= json.find('\"', start);
|
||||||
|
message= json.substr(start, end - start);
|
||||||
|
|
||||||
|
// parse data
|
||||||
|
start= json.find('{', pos_data) + 1;
|
||||||
|
end= json.find('}', start);
|
||||||
|
std::string data_content= json.substr(start, end - start);
|
||||||
|
std::istringstream data_stream(data_content);
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
while (std::getline(data_stream, line, ','))
|
||||||
|
{
|
||||||
|
std::size_t colon_pos= line.find(':');
|
||||||
|
if (colon_pos == std::string::npos)
|
||||||
|
{
|
||||||
|
continue; // Skip malformed line
|
||||||
|
}
|
||||||
|
std::string key= line.substr(0, colon_pos);
|
||||||
|
std::string value= line.substr(colon_pos + 1);
|
||||||
|
|
||||||
|
// clean key and value
|
||||||
|
auto trim_quotes_and_space= [](std::string &str) {
|
||||||
|
size_t first= str.find_first_not_of(" \t\n\"");
|
||||||
|
size_t last= str.find_last_not_of(" \t\n\"");
|
||||||
|
if (first == std::string::npos || last == std::string::npos)
|
||||||
|
{
|
||||||
|
str.clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
str= str.substr(first, last - first + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
trim_quotes_and_space(key);
|
||||||
|
trim_quotes_and_space(value);
|
||||||
|
|
||||||
|
data_dict[key]= value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch (std::exception &e)
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to parse JSON: " << e.what() << std::endl;
|
||||||
|
message= e.what();
|
||||||
|
code= -1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is used to escape double quotes in a string.
|
||||||
|
* @param input
|
||||||
|
* @param len
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
|
||||||
|
std::string videx_escape_double_quotes(const std::string &input, size_t len)
|
||||||
|
{
|
||||||
|
if (len == std::string::npos)
|
||||||
|
len= input.length();
|
||||||
|
|
||||||
|
std::string output= input.substr(0, len);
|
||||||
|
size_t pos= output.find('\\');
|
||||||
|
while (pos != std::string::npos)
|
||||||
|
{
|
||||||
|
output.replace(pos, 1, "\\\\");
|
||||||
|
pos= output.find('\\', pos + 2);
|
||||||
|
}
|
||||||
|
pos= output.find('\"');
|
||||||
|
while (pos != std::string::npos)
|
||||||
|
{
|
||||||
|
output.replace(pos, 1, "\\\"");
|
||||||
|
pos= output.find('\"', pos + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
pos= output.find('\n');
|
||||||
|
while (pos != std::string::npos)
|
||||||
|
{
|
||||||
|
output.replace(pos, 1, " ");
|
||||||
|
pos= output.find('\n', pos + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pos= output.find('\t');
|
||||||
|
while (pos != std::string::npos)
|
||||||
|
{
|
||||||
|
output.replace(pos, 1, " ");
|
||||||
|
pos= output.find('\t', pos + 1);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
VidexJsonItem *VidexJsonItem::create(const std::string &new_item_type)
|
||||||
|
{
|
||||||
|
data.push_back(VidexJsonItem(new_item_type, depth + 1));
|
||||||
|
return &data.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
VidexJsonItem *VidexJsonItem::create(const std::string &item_type, const char *prompt)
|
||||||
|
{
|
||||||
|
VidexJsonItem newOne= VidexJsonItem(item_type, depth + 1);
|
||||||
|
newOne.add_property("prompt", prompt);
|
||||||
|
data.push_back(newOne);
|
||||||
|
return &data.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VidexJsonItem::add_property(const std::string &key, const std::string &value)
|
||||||
|
{
|
||||||
|
properties[key]= videx_escape_double_quotes(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VidexJsonItem::add_property(const std::string &key, const char *value)
|
||||||
|
{
|
||||||
|
if (value != NULL)
|
||||||
|
{
|
||||||
|
properties[key]= videx_escape_double_quotes(value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
properties[key]= "NULL";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VidexJsonItem::add_property(const std::string &key, const String &value)
|
||||||
|
{
|
||||||
|
if (!value.is_alloced() || !value.ptr() || !value.alloced_length() ||
|
||||||
|
(value.alloced_length() < (value.length() + 1)))
|
||||||
|
{
|
||||||
|
properties[key]= "NULL";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
properties[key]= videx_escape_double_quotes(value.ptr(), value.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VidexJsonItem::add_property(const std::string &key, const String *value)
|
||||||
|
{
|
||||||
|
if (value == NULL)
|
||||||
|
{
|
||||||
|
properties[key]= "NULL";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
add_property(key, *value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string VidexJsonItem::to_json() const
|
||||||
|
{
|
||||||
|
std::string json= "{";
|
||||||
|
|
||||||
|
json+= "\"item_type\":\"" + item_type + "\",";
|
||||||
|
|
||||||
|
json+= "\"properties\":{";
|
||||||
|
for (std::map<std::string, std::string>::const_iterator it=
|
||||||
|
properties.begin();
|
||||||
|
it != properties.end(); ++it)
|
||||||
|
{
|
||||||
|
json+= "\"" + it->first + "\":\"" + it->second + "\",";
|
||||||
|
}
|
||||||
|
if (!properties.empty())
|
||||||
|
{
|
||||||
|
json.erase(json.length() - 1); // remove trailing comma
|
||||||
|
}
|
||||||
|
json+= "},";
|
||||||
|
|
||||||
|
json+= "\"data\":[";
|
||||||
|
for (std::list<VidexJsonItem>::const_iterator it= data.begin();
|
||||||
|
it != data.end(); ++it)
|
||||||
|
{
|
||||||
|
json+= it->to_json() + ",";
|
||||||
|
}
|
||||||
|
if (!data.empty())
|
||||||
|
{
|
||||||
|
json.erase(json.length() - 1); // remove trailing comma
|
||||||
|
}
|
||||||
|
json+= "]}";
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Return printable field name; MariaDB 11.0 lacks functional index names.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const char *get_field_name_or_expression(const Field *field)
|
||||||
|
{
|
||||||
|
return field->field_name.str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Print a key to a string
|
||||||
|
referring to print_key_value - sql/range_optimizer/range_optimizer.cc:1429
|
||||||
|
|
||||||
|
@param[out] out String the key is appended to
|
||||||
|
@param[in] key_part Index components description
|
||||||
|
@param[in] key Key tuple
|
||||||
|
*/
|
||||||
|
|
||||||
|
void videx_print_key_value(String *out, const KEY_PART_INFO *key_part,
|
||||||
|
const uchar *uchar_key)
|
||||||
|
{
|
||||||
|
Field *field= key_part->field;
|
||||||
|
|
||||||
|
if (field->flags & BLOB_FLAG)
|
||||||
|
{
|
||||||
|
// Byte 0 of a nullable key is the null-byte. If set, key is NULL.
|
||||||
|
if (field->maybe_null() && *uchar_key)
|
||||||
|
{
|
||||||
|
out->append(STRING_WITH_LEN("NULL"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (field->type() == MYSQL_TYPE_GEOMETRY)
|
||||||
|
{
|
||||||
|
out->append(STRING_WITH_LEN("unprintable_geometry_value"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// if uncomment, videx will return fixed "unprintable_blob_value"
|
||||||
|
// out->append(STRING_WITH_LEN("unprintable_blob_value"));
|
||||||
|
// return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint store_length= key_part->store_length;
|
||||||
|
|
||||||
|
if (field->maybe_null())
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Byte 0 of key is the null-byte. If set, key is NULL.
|
||||||
|
Otherwise, print the key value starting immediately after the
|
||||||
|
null-byte
|
||||||
|
*/
|
||||||
|
if (*uchar_key)
|
||||||
|
{
|
||||||
|
out->append(STRING_WITH_LEN("NULL"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uchar_key++; // Skip null byte
|
||||||
|
store_length--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Binary data cannot be converted to UTF8 which is what the
|
||||||
|
optimizer trace expects. If the column is binary, the hex
|
||||||
|
representation is printed to the trace instead.
|
||||||
|
*/
|
||||||
|
if (field->result_type() == STRING_RESULT &&
|
||||||
|
field->charset() == &my_charset_bin)
|
||||||
|
{
|
||||||
|
out->append(STRING_WITH_LEN("0x"));
|
||||||
|
for (uint i= 0; i < store_length; i++)
|
||||||
|
{
|
||||||
|
out->append(_dig_vec_lower[*(uchar_key + i) >> 4]);
|
||||||
|
out->append(_dig_vec_lower[*(uchar_key + i) & 0x0F]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuffer<128> tmp(system_charset_info);
|
||||||
|
bool add_quotes= field->result_type() == STRING_RESULT;
|
||||||
|
|
||||||
|
TABLE *table= field->table;
|
||||||
|
MY_BITMAP *old_sets[2];
|
||||||
|
|
||||||
|
dbug_tmp_use_all_columns(table, old_sets, &table->read_set,
|
||||||
|
&table->write_set);
|
||||||
|
|
||||||
|
field->set_key_image(uchar_key, key_part->length);
|
||||||
|
if (field->type() == MYSQL_TYPE_BIT)
|
||||||
|
{
|
||||||
|
(void) field->val_int_as_str(&tmp, true); // may change tmp's charset
|
||||||
|
add_quotes= false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
field->val_str(&tmp); // may change tmp's charset
|
||||||
|
}
|
||||||
|
|
||||||
|
dbug_tmp_restore_column_maps(&table->read_set, &table->write_set, old_sets);
|
||||||
|
|
||||||
|
if (add_quotes)
|
||||||
|
{
|
||||||
|
out->append('\'');
|
||||||
|
// Worst case: Every character is escaped.
|
||||||
|
const size_t buffer_size= tmp.length() * 2 + 1;
|
||||||
|
char *quoted_string= (char *) thd_alloc(current_thd, buffer_size);
|
||||||
|
|
||||||
|
my_bool overflow;
|
||||||
|
const size_t quoted_length=
|
||||||
|
escape_string_for_mysql(tmp.charset(), quoted_string, buffer_size,
|
||||||
|
tmp.ptr(), tmp.length(), &overflow);
|
||||||
|
if (overflow)
|
||||||
|
{
|
||||||
|
// Overflow. Our worst case estimate for the buffer size was too low.
|
||||||
|
assert(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
out->append(quoted_string, quoted_length, tmp.charset());
|
||||||
|
out->append('\'');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out->append(tmp.ptr(), tmp.length(), tmp.charset());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Convert range read function to a concise symbolic operator string.
|
||||||
|
*/
|
||||||
|
|
||||||
|
std::string haRKeyFunctionToSymbol(ha_rkey_function function)
|
||||||
|
{
|
||||||
|
switch (function)
|
||||||
|
{
|
||||||
|
case HA_READ_KEY_EXACT:
|
||||||
|
return "=";
|
||||||
|
case HA_READ_KEY_OR_NEXT:
|
||||||
|
return ">=";
|
||||||
|
case HA_READ_KEY_OR_PREV:
|
||||||
|
return "<=";
|
||||||
|
case HA_READ_AFTER_KEY:
|
||||||
|
return ">";
|
||||||
|
case HA_READ_BEFORE_KEY:
|
||||||
|
return "<";
|
||||||
|
case HA_READ_PREFIX:
|
||||||
|
return "=x%";
|
||||||
|
case HA_READ_PREFIX_LAST:
|
||||||
|
return "last_x%";
|
||||||
|
case HA_READ_PREFIX_LAST_OR_PREV:
|
||||||
|
return "<=last_x%";
|
||||||
|
case HA_READ_MBR_CONTAIN:
|
||||||
|
return "HA_READ_MBR_CONTAIN";
|
||||||
|
case HA_READ_MBR_INTERSECT:
|
||||||
|
return "HA_READ_MBR_INTERSECT";
|
||||||
|
case HA_READ_MBR_WITHIN:
|
||||||
|
return "HA_READ_MBR_WITHIN";
|
||||||
|
case HA_READ_MBR_DISJOINT:
|
||||||
|
return "HA_READ_MBR_DISJOINT";
|
||||||
|
case HA_READ_MBR_EQUAL:
|
||||||
|
return "HA_READ_MBR_EQUAL";
|
||||||
|
default:
|
||||||
|
return "Unknown ha_rkey_function";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Append one column bound to output and JSON; used by key-range serialization.
|
||||||
|
*/
|
||||||
|
|
||||||
|
inline void subha_append_range(String *out, const KEY_PART_INFO *key_part,
|
||||||
|
const uchar *uchar_key, const uint,
|
||||||
|
VidexJsonItem *range_json)
|
||||||
|
{
|
||||||
|
if (out->length() > 0)
|
||||||
|
out->append(STRING_WITH_LEN(" "));
|
||||||
|
String tmp_str;
|
||||||
|
tmp_str.set_charset(system_charset_info);
|
||||||
|
tmp_str.length(0);
|
||||||
|
std::stringstream ss;
|
||||||
|
|
||||||
|
const char *field_or_expr=get_field_name_or_expression(key_part->field);
|
||||||
|
out->append(field_or_expr, strlen(field_or_expr));
|
||||||
|
range_json->add_property("column", field_or_expr);
|
||||||
|
|
||||||
|
out->append(STRING_WITH_LEN("("));
|
||||||
|
videx_print_key_value(&tmp_str, key_part, uchar_key);
|
||||||
|
out->append(tmp_str);
|
||||||
|
out->append(STRING_WITH_LEN("), "));
|
||||||
|
ss.write(tmp_str.ptr(), tmp_str.length());
|
||||||
|
range_json->add_property("value", ss.str());
|
||||||
|
tmp_str.length(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Return indices of set bits (0..63) in the given bitmap.
|
||||||
|
*/
|
||||||
|
|
||||||
|
std::vector<int> BitsSetIn(ulong bitmap)
|
||||||
|
{
|
||||||
|
std::vector<int> result;
|
||||||
|
for (int i= 0; i < 64; ++i)
|
||||||
|
{
|
||||||
|
if (bitmap & (1UL << i))
|
||||||
|
result.push_back(i);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Serialize a `key_range` into text and JSON; mirrors range optimizer output.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void subha_parse_key_range(const key_range *key_range, const KEY *index,
|
||||||
|
String *out, VidexJsonItem *req_json)
|
||||||
|
{
|
||||||
|
const uint QUICK_RANGE_flag= -1;
|
||||||
|
if (key_range == nullptr)
|
||||||
|
{
|
||||||
|
out->append(STRING_WITH_LEN("<NO_KEY_RANGE>"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
KEY_PART_INFO *first_key_part= index->key_part;
|
||||||
|
out->append(STRING_WITH_LEN(" "));
|
||||||
|
std::string key_range_flag_str= haRKeyFunctionToSymbol(key_range->flag);
|
||||||
|
out->append(key_range_flag_str.c_str(), key_range_flag_str.length());
|
||||||
|
|
||||||
|
req_json->add_property("operator", key_range_flag_str);
|
||||||
|
req_json->add_property_nonan("length", key_range->length);
|
||||||
|
req_json->add_property("index_name", index->name.str);
|
||||||
|
|
||||||
|
const uchar *uchar_key= key_range->key;
|
||||||
|
for (int keypart_idx : BitsSetIn(key_range->keypart_map))
|
||||||
|
{
|
||||||
|
VidexJsonItem *range_json= req_json->create("column_and_bound");
|
||||||
|
subha_append_range(out, &first_key_part[keypart_idx], uchar_key,
|
||||||
|
QUICK_RANGE_flag, range_json);
|
||||||
|
|
||||||
|
uchar_key+= first_key_part[keypart_idx].store_length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void serializeKeyRangeToJson(const key_range *min_key,
|
||||||
|
const key_range *max_key, KEY *key,
|
||||||
|
VidexJsonItem *req_json)
|
||||||
|
{
|
||||||
|
String range_info;
|
||||||
|
range_info.set_charset(system_charset_info);
|
||||||
|
|
||||||
|
VidexJsonItem *min_json= req_json->create("min_key");
|
||||||
|
subha_parse_key_range(min_key, key, &range_info, min_json);
|
||||||
|
std::string std_info_min(range_info.ptr(), range_info.length());
|
||||||
|
range_info.length(0);
|
||||||
|
|
||||||
|
VidexJsonItem *max_json= req_json->create("max_key");
|
||||||
|
subha_parse_key_range(max_key, key, &range_info, max_json);
|
||||||
|
std::string std_info_max(range_info.ptr(), range_info.length());
|
||||||
|
range_info.length(0);
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "KEY: " << key->name.str << " MIN_KEY: {" << std_info_min
|
||||||
|
<< "}, MAX_KEY: {" << std_info_max << "}";
|
||||||
|
DBUG_PRINT("info", ("%s", ss.str().c_str()));
|
||||||
|
DBUG_PRINT("info", ("req_json = %s", req_json->to_json().c_str()));
|
||||||
|
}
|
||||||
153
storage/videx/videx_utils.h
Normal file
153
storage/videx/videx_utils.h
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/* Copyright (c) 2024 Bytedance Ltd. 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 also distributed 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 included with MySQL.
|
||||||
|
|
||||||
|
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 */
|
||||||
|
|
||||||
|
/** @file videx_utils.h
|
||||||
|
*
|
||||||
|
* @brief
|
||||||
|
* Lightweight JSON utilities for MariaDB storage engine development:
|
||||||
|
* - VidexJsonItem class: Hierarchical JSON builder.
|
||||||
|
* - Simple parser: Extracts code/message/data from flat JSON structures, at most 2-level nested.
|
||||||
|
* @note
|
||||||
|
* 1. Parser supports max 2-level nested JSON structures
|
||||||
|
* 2. Builder automatically escapes special characters (\"\\)
|
||||||
|
* 3. Use construct_request() for standard API request templates
|
||||||
|
* 4. Cross-platform: Avoids rapid_json segmentation faults on macOS
|
||||||
|
* 5. Serializes key_range to JSON object
|
||||||
|
*
|
||||||
|
* @see
|
||||||
|
* - Implementation: videx_utils.cc
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef VIDEX_UTILS
|
||||||
|
#define VIDEX_UTILS
|
||||||
|
|
||||||
|
#include <my_global.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <my_base.h>
|
||||||
|
#include <key.h>
|
||||||
|
#include <structs.h>
|
||||||
|
#include <field.h>
|
||||||
|
#include <table.h>
|
||||||
|
#include <mysqld.h>
|
||||||
|
#include <map>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <list>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <memory>
|
||||||
|
#include <sql_string.h>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
typedef std::map<std::string, std::string> VidexStringMap;
|
||||||
|
|
||||||
|
static inline bool videx_contains_key(const VidexStringMap &myMap,
|
||||||
|
const std::string &key)
|
||||||
|
{
|
||||||
|
return myMap.find(key) != myMap.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
int videx_parse_simple_json(const std::string &json, int &code,
|
||||||
|
std::string &message,
|
||||||
|
std::map<std::string, std::string> &data_dict);
|
||||||
|
|
||||||
|
std::string videx_escape_double_quotes(const std::string &input,
|
||||||
|
size_t len= std::string::npos);
|
||||||
|
|
||||||
|
class VidexJsonItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string item_type;
|
||||||
|
std::map<std::string, std::string> properties;
|
||||||
|
std::list<VidexJsonItem> data;
|
||||||
|
int depth;
|
||||||
|
|
||||||
|
VidexJsonItem() : item_type("empty"), depth(0) {}
|
||||||
|
|
||||||
|
VidexJsonItem(const std::string &item_type, int depth)
|
||||||
|
: item_type(item_type), depth(depth) {}
|
||||||
|
|
||||||
|
VidexJsonItem *create(const std::string &new_item_type);
|
||||||
|
|
||||||
|
VidexJsonItem *create(const std::string &item_type, const char *prompt);
|
||||||
|
|
||||||
|
void add_property(const std::string &key, const std::string &value);
|
||||||
|
|
||||||
|
void add_property(const std::string &key, const char *value);
|
||||||
|
|
||||||
|
void add_property(const std::string &key, const String &value);
|
||||||
|
|
||||||
|
void add_property(const std::string &key, const String *value);
|
||||||
|
|
||||||
|
// Except for string which might be empty and needs to be converted to NULL
|
||||||
|
// separately, all other values can be handled using this function.
|
||||||
|
template <typename V>
|
||||||
|
void add_property_nonan(const std::string &key, V value)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << value;
|
||||||
|
properties[key]= ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string to_json() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
construct a basic request, and other parameters can be conveniently added
|
||||||
|
externally. */
|
||||||
|
|
||||||
|
static inline VidexJsonItem
|
||||||
|
construct_request(const std::string &db_name, const std::string &table_name,
|
||||||
|
const std::string &function,
|
||||||
|
const std::string &target_storage_engine= "INNODB")
|
||||||
|
{
|
||||||
|
VidexJsonItem req("videx_request", 0);
|
||||||
|
req.add_property("dbname", db_name);
|
||||||
|
req.add_property("table_name", table_name);
|
||||||
|
req.add_property("function", function);
|
||||||
|
req.add_property("target_storage_engine", target_storage_engine);
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Serializes min/max key bounds for a given index into `req_json`.
|
||||||
|
Also prints a concise human-readable summary for debugging.
|
||||||
|
|
||||||
|
@param min_key Minimum key range.
|
||||||
|
@param max_key Maximum key range.
|
||||||
|
@param key Index key information.
|
||||||
|
@param req_json JSON object to store serialized key range.
|
||||||
|
*/
|
||||||
|
void serializeKeyRangeToJson(const key_range *min_key,
|
||||||
|
const key_range *max_key, KEY *key,
|
||||||
|
VidexJsonItem *req_json);
|
||||||
|
|
||||||
|
#endif // VIDEX_UTILS
|
||||||
Loading…
Reference in New Issue
Block a user