1
0
mirror of https://github.com/MariaDB/server.git synced 2025-08-01 03:47:19 +03:00
Files
mariadb/sql/opt_trace_ddl_info.cc
bsrikanth-mariadb 67b320b413 MDEV-36483: store ddls in the optimizer trace
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.
2025-06-28 07:35:07 -04:00

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;
}