mirror of
https://github.com/MariaDB/server.git
synced 2025-08-01 03:47:19 +03:00
This feature stores the ddls of the tables/views that are used in a query, to the optimizer trace. It is currently controlled by a system variable store_ddls_in_optimizer_trace, and is not enabled by default. All the ddls will be stored in a single json array, with each element having table/view name, and the associated create definition of the table/view. The approach taken is to read global query_tables from the thd->lex, and read them in reverse. Create a record with table_name, ddl of the table and add the table_name to the hash, along with dumping the information to the trace. dbName_plus_tableName is used as a key, and the duplicate entries are not added to the hash. The main suite tests are also run with the feature enabled, and they all succeed.
230 lines
6.7 KiB
C++
230 lines
6.7 KiB
C++
/*
|
|
Copyright (c) 2025, MariaDB
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; version 2 of the License.
|
|
|
|
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 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 Street, Fifth Floor, Boston, MA 02110-1335
|
|
USA */
|
|
|
|
#include "opt_trace_ddl_info.h"
|
|
#include "sql_show.h"
|
|
#include "my_json_writer.h"
|
|
#include "sql_list.h"
|
|
#include "sql_table.h"
|
|
#include "mysql.h"
|
|
#include "hash.h"
|
|
|
|
/**
|
|
@file
|
|
|
|
@brief
|
|
Stores the ddls of the tables, and views that are used
|
|
in either SELECT, INSERT, DELETE, and UPDATE queries,
|
|
into the optimizer trace. All the ddls are stored together
|
|
at one place as a JSON array object with name "list_ddls"
|
|
*/
|
|
|
|
struct DDL_Key
|
|
{
|
|
char *name; //full name of the table or view
|
|
size_t name_len;
|
|
};
|
|
|
|
/*
|
|
helper function to know the key portion of the record
|
|
that is stored in hash.
|
|
*/
|
|
static const uchar *get_rec_key(const void *entry_, size_t *length,
|
|
my_bool flags)
|
|
{
|
|
auto entry= static_cast<const DDL_Key *>(entry_);
|
|
*length= entry->name_len;
|
|
return reinterpret_cast<const uchar *>(entry->name);
|
|
}
|
|
|
|
/*
|
|
@brief
|
|
Check whether a table is a regular base table (for which we should
|
|
dump the ddl) or not.
|
|
|
|
@detail
|
|
Besides base tables, the query may have:
|
|
- Table functions (Currently it's only JSON_TABLE)
|
|
- INFORMATION_SCHEMA tables
|
|
- Tables in PERFORMANCE_SCHEMA and mysql database
|
|
- Internal temporary ("work") tables
|
|
*/
|
|
static bool is_base_table(TABLE_LIST *tbl)
|
|
{
|
|
return
|
|
(tbl->table &&
|
|
tbl->table->s &&
|
|
!tbl->table_function &&
|
|
!tbl->schema_table &&
|
|
get_table_category(tbl->get_db_name(), tbl->get_table_name()) ==
|
|
TABLE_CATEGORY_USER &&
|
|
tbl->table->s->tmp_table != INTERNAL_TMP_TABLE &&
|
|
tbl->table->s->tmp_table != SYSTEM_TMP_TABLE);
|
|
}
|
|
|
|
static bool dump_record_to_trace(THD *thd, DDL_Key *ddl_key, String *stmt)
|
|
{
|
|
Json_writer_object ddl_wrapper(thd);
|
|
ddl_wrapper.add("name", ddl_key->name);
|
|
size_t non_esc_stmt_len= stmt->length();
|
|
/*
|
|
making escape_stmt size to be 4 times the non_esc_stmt
|
|
4 is chosen as a worst case although 3 should suffice.
|
|
"'" would be escaped to \"\'\"
|
|
*/
|
|
size_t len_multiplier= sizeof(uint32_t);
|
|
size_t escape_stmt_len= len_multiplier * non_esc_stmt_len;
|
|
char *escaped_stmt= (char *) thd->alloc(escape_stmt_len + 1);
|
|
|
|
if (!escaped_stmt)
|
|
return true;
|
|
|
|
int act_escape_stmt_len=
|
|
json_escape_string(stmt->c_ptr(), stmt->c_ptr() + non_esc_stmt_len,
|
|
escaped_stmt, escaped_stmt + escape_stmt_len);
|
|
|
|
if (act_escape_stmt_len < 0)
|
|
return true;
|
|
|
|
escaped_stmt[act_escape_stmt_len]= 0;
|
|
ddl_wrapper.add("ddl", escaped_stmt);
|
|
return false;
|
|
}
|
|
|
|
static void create_view_def(THD *thd, TABLE_LIST *table, String *name,
|
|
String *buf)
|
|
{
|
|
buf->append(STRING_WITH_LEN("CREATE "));
|
|
view_store_options(thd, table, buf);
|
|
buf->append(STRING_WITH_LEN("VIEW "));
|
|
buf->append(*name);
|
|
buf->append(STRING_WITH_LEN(" AS "));
|
|
buf->append(table->select_stmt.str, table->select_stmt.length);
|
|
}
|
|
|
|
/*
|
|
@brief
|
|
Dump definitions of all tables and view used by the statement into
|
|
the optimizer trace. The goal is to eventually save everything that
|
|
is needed to reproduce the query execution
|
|
|
|
@detail
|
|
Stores the ddls of the tables, and views that are used
|
|
in either SELECT, INSERT, DELETE, and UPDATE queries,
|
|
into the optimizer trace.
|
|
Global query_tables are read in reverse order from the thd->lex,
|
|
and a record with table_name, and ddl of the table are created.
|
|
Hash is used to store the records, where in no duplicates
|
|
are stored. db_name.table_name is used as a key to discard any
|
|
duplicates. If a new record that is created is not in the hash,
|
|
then that is dumped into the trace.
|
|
|
|
@return
|
|
false when no error occurred during the computation
|
|
*/
|
|
bool store_table_definitions_in_trace(THD *thd)
|
|
{
|
|
LEX *lex= thd->lex;
|
|
if (!(thd->variables.optimizer_trace &&
|
|
thd->variables.optimizer_record_context &&
|
|
(lex->sql_command == SQLCOM_SELECT ||
|
|
lex->sql_command == SQLCOM_INSERT_SELECT ||
|
|
lex->sql_command == SQLCOM_DELETE ||
|
|
lex->sql_command == SQLCOM_UPDATE ||
|
|
lex->sql_command == SQLCOM_DELETE_MULTI ||
|
|
lex->sql_command == SQLCOM_UPDATE_MULTI)))
|
|
return false;
|
|
|
|
if (lex->query_tables == *(lex->query_tables_last))
|
|
return false;
|
|
|
|
Json_writer_object ddls_wrapper(thd);
|
|
ddls_wrapper.add("current_database", thd->get_db());
|
|
Json_writer_array ddl_list(thd, "list_ddls");
|
|
HASH hash;
|
|
List<TABLE_LIST> tables_list;
|
|
|
|
/*
|
|
lex->query_tables lists the VIEWs before their underlying tables.
|
|
Create a list in the reverse order.
|
|
*/
|
|
for (TABLE_LIST *tbl= lex->query_tables; tbl; tbl= tbl->next_global)
|
|
{
|
|
if (!tbl->is_view() && !is_base_table(tbl))
|
|
continue;
|
|
if (tables_list.push_front(tbl))
|
|
return true;
|
|
}
|
|
|
|
if (tables_list.is_empty())
|
|
return false;
|
|
|
|
List_iterator li(tables_list);
|
|
my_hash_init(key_memory_trace_ddl_info, &hash, system_charset_info, 16, 0, 0,
|
|
get_rec_key, NULL, HASH_UNIQUE);
|
|
bool res= false;
|
|
for (TABLE_LIST *tbl= li++; tbl; li.remove(), tbl= li++)
|
|
{
|
|
String ddl;
|
|
String name;
|
|
DDL_Key *ddl_key;
|
|
char *name_copy;
|
|
|
|
/*
|
|
A query can use the same table multiple times. Do not dump the DDL
|
|
multiple times.
|
|
*/
|
|
name.append(tbl->get_db_name().str, tbl->get_db_name().length);
|
|
name.append(STRING_WITH_LEN("."));
|
|
name.append(tbl->get_table_name().str, tbl->get_table_name().length);
|
|
|
|
if (my_hash_search(&hash, (uchar *) name.c_ptr(), name.length()))
|
|
continue;
|
|
|
|
if (!(ddl_key= (DDL_Key *) thd->alloc(sizeof(DDL_Key))))
|
|
{
|
|
res= true;
|
|
break;
|
|
}
|
|
|
|
if (tbl->is_view())
|
|
create_view_def(thd, tbl, &name, &ddl);
|
|
else
|
|
{
|
|
if (show_create_table(thd, tbl, &ddl, NULL, WITH_DB_NAME))
|
|
{
|
|
res= true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
name_copy= (char *) thd->alloc(name.length() + 1);
|
|
strcpy(name_copy, name.c_ptr());
|
|
ddl_key->name= name_copy;
|
|
ddl_key->name_len= name.length();
|
|
my_hash_insert(&hash, (uchar *) ddl_key);
|
|
|
|
if (dump_record_to_trace(thd, ddl_key, &ddl))
|
|
{
|
|
res= true;
|
|
break;
|
|
}
|
|
}
|
|
my_hash_free(&hash);
|
|
return res;
|
|
}
|