sources/cg_query_plan.c (534 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if defined(CQL_AMALGAM_LEAN) && !defined(CQL_AMALGAM_QUERY_PLAN)
// stubs to avoid link errors
cql_noexport void cg_query_plan_main(ast_node *head) {}
#else
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "ast.h"
#include "cg_query_plan.h"
#include "charbuf.h"
#include "cql.h"
#include "encoders.h"
#include "gen_sql.h"
#include "cg_common.h"
#include "sem.h"
static void cg_qp_one_stmt(ast_node *stmt);
static charbuf *schema_stmts;
static charbuf *query_plans;
static CSTR current_procedure_name;
static charbuf *current_ok_table_scan;
// Count sql statement found in ast
static uint32_t sql_stmt_count = 0;
static gen_sql_callbacks *cg_qp_callbacks = NULL;
// When generating the query plan report there will be no actual
// variable values to use in the query. To get around this
// we replace all variable references with a type-correct version
// of the constant "1". This gives us a pretty good query plan
// even if there are parameters in the real query.
// Note: if the SQLite query processor were much more fancy this
// wouldn't work at all. Constants matter.
static bool_t variables_callback(
struct ast_node *_Nonnull ast,
void *_Nullable context,
charbuf *_Nonnull output)
{
sem_t sem_type = ast->sem->sem_type;
if (is_nullable(sem_type)) {
bprintf(output, "nullable(");
}
if (is_numeric(sem_type)) {
bprintf(output, "1");
}
else if (is_text(sem_type)) {
bprintf(output, "'1'");
}
else if (is_object(sem_type)) {
bprintf(output, "cast('1' as object)");
}
else {
Contract(is_blob(sem_type));
bprintf(output, "cast('1' as blob)");
}
if (is_nullable(sem_type)) {
bprintf(output, ")");
}
return true;
}
static void cg_qp_explain_query_stmt(ast_node *stmt) {
sql_stmt_count++;
CHARBUF_OPEN(proc);
CHARBUF_OPEN(body);
CHARBUF_OPEN(sql);
CHARBUF_OPEN(cstr_sql);
CHARBUF_OPEN(cstr_sql2);
gen_set_output_buffer(&sql);
gen_statement_with_callbacks(stmt, cg_qp_callbacks);
cg_encode_json_string_literal(sql.ptr, &cstr_sql);
// Encode the encoded special character to be able retain the special characteres in the
// json string output
bprintf(&cstr_sql2, "\"");
for (int32_t i = 1; i < cstr_sql.used - 2; i++) {
cg_encode_char_as_json_string_literal(cstr_sql.ptr[i], &cstr_sql2);
}
bprintf(&cstr_sql2, "\"");
bprintf(&body, "DECLARE stmt TEXT NOT NULL;\n");
// Storing the string statement in a variable prevents codegen from stripping the '\n'
// We strip '\n' when formatting the one line string sql statement to a multiline c
// string sql statement. It gave a better look to the code gen but that does not work
// for query plan use case because we want to keep '\n' in the string statement because
// we re-use that statement later to print it in Diff or the terminal.
bprintf(&body, "SET stmt := %s;\n", cstr_sql2.ptr);
bprintf(&body, "INSERT INTO sql_temp(id, sql) VALUES(%d, stmt);\n", sql_stmt_count);
if (current_procedure_name && current_ok_table_scan && current_ok_table_scan->used > 1) {
bprintf(
&body,
"INSERT INTO ok_table_scan(sql_id, proc_name, table_names) VALUES(%d, \"%s\", \"%s\");\n",
sql_stmt_count,
current_procedure_name,
current_ok_table_scan->ptr);
}
bprintf(&body, "DECLARE C CURSOR FOR EXPLAIN QUERY PLAN\n");
bprintf(&body, "%s;\n", sql.ptr);
bprintf(&body, "LOOP FETCH C\n");
bprintf(&body, "BEGIN\n");
bprintf(&body, " INSERT INTO plan_temp(sql_id, iselectid, iorder, ifrom, zdetail) VALUES(%d, C.iselectid, C.iorder, C.ifrom, C.zdetail);\n", sql_stmt_count);
bprintf(&body, "END;\n");
bprintf(&proc, "CREATE PROC populate_query_plan_%d()\n", sql_stmt_count);
bprintf(&proc, "BEGIN\n");
bindent(&proc, &body, 2);
bprintf(&proc, "END;\n\n");
bprintf(query_plans, "%s", proc.ptr);
CHARBUF_CLOSE(cstr_sql2);
CHARBUF_CLOSE(cstr_sql);
CHARBUF_CLOSE(sql);
CHARBUF_CLOSE(body);
CHARBUF_CLOSE(proc);
}
static void cg_qp_sql_stmt(ast_node *ast) {
if (ast->sem->delete_version <= 0) {
gen_set_output_buffer(schema_stmts);
gen_statement_with_callbacks(ast, cg_qp_callbacks);
bprintf(schema_stmts, ";\n");
}
}
static void cg_qp_ok_table_scan_callback(
CSTR _Nonnull name,
ast_node* _Nonnull misc_attr_value,
void* _Nullable context) {
Contract(context && is_ast_str(misc_attr_value));
charbuf *ok_table_scan_buf = (charbuf *)context;
EXTRACT_STRING(table_name, misc_attr_value);
if (ok_table_scan_buf->used > 1) {
bprintf(ok_table_scan_buf, ",");
}
// the "#" around the table_name are used as delimiter of the
// table_name's word to later find tables that are ok to scan.
bprintf(ok_table_scan_buf, "#%s#", table_name);
}
// There are now extract work to be done in create_proc_stmt substree.
// We need to associate the proc name and ok_table_scan's tables to all
// sql statement found in the proc.
// It's used later to establish the relationship between a statement and
// a proc name (but all "ok_table_scan" attribution). But also to detect
// whether or not scan table alert should be silent because of "ok_table_scan".
//
// The code in this function collect all the tables of "ok_table_scan" attr
// and associate them with the proc name and the statement ids in the proc.
//
// e.g: With the info below now available we can now figure out wheter or
// an alert of scan table can be made on a particular statement id.
// stmt(id) <-> proc_name <-> table_name (ok_table_scan)
static void cg_qp_create_proc_stmt(ast_node *ast) {
Contract(is_ast_create_proc_stmt(ast));
Contract(current_procedure_name == NULL);
Contract(current_ok_table_scan == NULL);
uint32_t frag_type = find_proc_frag_type(ast);
if (frag_type == FRAG_TYPE_SHARED) {
bprintf(query_plans, "@attribute(cql:shared_fragment)\n");
gen_set_output_buffer(query_plans);
gen_statement_with_callbacks(ast, cg_qp_callbacks);
bprintf(query_plans, ";\n\n");
return;
}
CHARBUF_OPEN(ok_table_scan_buf);
current_ok_table_scan = &ok_table_scan_buf;
// The statement has attributions therefore we should collect the values
// of "ok_table_scan" attribution if applicable. Otherwise we have nothing
// record on this proc related to "ok_table_scan".
if (is_ast_stmt_and_attr(ast->parent)) {
EXTRACT_NOTNULL(stmt_and_attr, ast->parent);
EXTRACT_NOTNULL(misc_attrs, stmt_and_attr->left);
EXTRACT_STRING(table_name, ast->left);
current_procedure_name = table_name;
find_ok_table_scan(misc_attrs, cg_qp_ok_table_scan_callback, (void *) &ok_table_scan_buf);
}
cg_qp_one_stmt(ast->left);
cg_qp_one_stmt(ast->right);
current_procedure_name = NULL;
current_ok_table_scan = NULL;
CHARBUF_CLOSE(ok_table_scan_buf);
}
static void cg_qp_one_stmt(ast_node *stmt) {
if (!stmt || is_primitive(stmt)) {
return;
}
symtab_entry *entry = symtab_find(cg_stmts, stmt->type);
if (entry) {
((void (*)(ast_node*))entry->val)(stmt);
} else {
cg_qp_one_stmt(stmt->left);
cg_qp_one_stmt(stmt->right);
}
}
static void cg_qp_stmt_list(ast_node *head) {
Contract(is_ast_stmt_list(head));
for (ast_node *stmt = head; stmt; stmt = stmt->right) {
cg_qp_one_stmt(stmt->left);
}
}
static void emit_populate_no_table_scan_proc(charbuf *output) {
CHARBUF_OPEN(no_scan_tables_buf);
for (list_item *item = all_tables_list; item; item = item->next) {
if (is_ast_create_table_stmt(item->ast)) {
EXTRACT_MISC_ATTRS(item->ast, misc_attrs);
if (misc_attrs != NULL) {
EXTRACT_NOTNULL(create_table_name_flags, item->ast->left);
EXTRACT_NOTNULL(table_flags_attrs, create_table_name_flags->left);
EXTRACT_ANY_NOTNULL(name_ast, create_table_name_flags->right);
EXTRACT_STRING(name, name_ast);
if (exists_attribute_str(misc_attrs, "no_table_scan")) {
if (no_scan_tables_buf.used > 1) {
bprintf(&no_scan_tables_buf, "\n UNION ");
}
bprintf(&no_scan_tables_buf, "SELECT \"%s\"", name);
}
}
}
}
bprintf(output, "CREATE PROC populate_no_table_scan()\n"
"BEGIN\n");
if (no_scan_tables_buf.used > 1) {
bprintf(output, " INSERT OR IGNORE INTO no_table_scan(table_name) %s;\n", no_scan_tables_buf.ptr);
}
bprintf(output, "END;\n");
CHARBUF_CLOSE(no_scan_tables_buf);
}
static void cg_qp_emit_declare_func(charbuf *output) {
// Emit declare functions because it may be needed for schema and query validation
gen_set_output_buffer(output);
for (list_item *item = all_functions_list; item; item = item->next) {
EXTRACT_ANY_NOTNULL(any_func, item->ast);
Contract(is_ast_declare_func_stmt(any_func) || is_ast_declare_select_func_stmt(any_func));
if (is_ast_declare_select_func_stmt(any_func)) {
gen_statement_with_callbacks(any_func, cg_qp_callbacks);
bprintf(output, ";\n");
}
}
}
static void cg_qp_emit_create_schema_proc(charbuf *output) {
bprintf(output, "CREATE PROC create_schema()\n"
"BEGIN\n");
bindent(output, schema_stmts, 2);
bprintf(output, " CREATE TABLE sql_temp(\n"
" id INT NOT NULL PRIMARY KEY,\n"
" sql TEXT NOT NULL\n"
" ) WITHOUT ROWID;\n"
" CREATE TABLE plan_temp(\n"
" iselectid INT NOT NULL,\n"
" iorder INT NOT NULL,\n"
" ifrom INT NOT NULL,\n"
" zdetail TEXT NOT NULL,\n"
" sql_id INT NOT NULL,\n"
" FOREIGN KEY (sql_id) REFERENCES sql_temp(id)\n"
" );\n"
" CREATE TABLE no_table_scan(\n"
" table_name TEXT NOT NULL PRIMARY KEY\n"
" );\n"
" CREATE TABLE table_scan_alert(\n"
" info TEXT NOT NULL\n"
" );\n"
" CREATE TABLE b_tree_alert(\n"
" info TEXT NOT NULL\n"
" );\n"
" CREATE TABLE ok_table_scan(\n"
" sql_id INT NOT NULL PRIMARY KEY,\n"
" proc_name TEXT NOT NULL,\n"
" table_names TEXT NOT NULL\n"
" ) WITHOUT ROWID;\n"
"END;\n");
}
static void emit_populate_tables_proc(charbuf *output) {
bprintf(output, "%s", query_plans->ptr);
}
static void emit_print_sql_statement_proc(charbuf *output) {
bprintf(output,
"%s",
"CREATE PROC print_sql_statement(sql_id integer not null)\n"
"BEGIN\n"
" DECLARE C CURSOR FOR SELECT * FROM sql_temp WHERE id = sql_id LIMIT 1;\n"
" FETCH C;\n"
" CALL printf(\" \\\"%s\\\",\\n\", C.sql);\n"
"END;\n"
);
}
static void emit_populate_table_scan_alert_table_proc(charbuf *output) {
bprintf(output, "CREATE PROC populate_table_scan_alert_table(table_ text not null)\n");
bprintf(output, "BEGIN\n");
bprintf(output, " INSERT OR IGNORE INTO table_scan_alert\n");
bprintf(output, " SELECT upper(table_) || '(' || count(*) || ')' as info FROM plan_temp\n");
bprintf(output, " WHERE ( zdetail GLOB ('*[Ss][Cc][Aa][Nn]* ' || table_) OR \n");
bprintf(output, " zdetail GLOB ('*[Ss][Cc][Aa][Nn]* ' || table_ || ' *')\n");
bprintf(output, " )\n");
bprintf(output, " AND sql_id NOT IN (\n");
bprintf(output, " SELECT sql_id from ok_table_scan\n");
bprintf(output, " WHERE table_names GLOB ('*#' || table_ || '#*')\n");
bprintf(output, " ) GROUP BY table_;\n");
bprintf(output, "END;\n");
}
static void emit_populate_b_tree_alert_table_proc(charbuf *output) {
bprintf(output,
"%s",
"CREATE PROC populate_b_tree_alert_table()\n"
"BEGIN\n"
" INSERT OR IGNORE INTO b_tree_alert\n"
" SELECT '#' || sql_id || '(' || count(*) || ')' as info FROM plan_temp\n"
" WHERE zdetail LIKE '%temp b-tree%'\n"
" GROUP BY sql_id;\n"
"END;\n");
}
static void emit_print_query_violation_proc(charbuf *output) {
bprintf(output,
"%s",
"CREATE PROC print_query_violation()\n"
"BEGIN\n"
" CALL populate_b_tree_alert_table();\n"
" DECLARE C CURSOR FOR SELECT table_name FROM no_table_scan;\n"
" LOOP FETCH C\n"
" BEGIN\n"
" CALL populate_table_scan_alert_table(C.table_name);\n"
" END;\n\n"
" LET count := (SELECT COUNT(*) FROM table_scan_alert UNION ALL SELECT COUNT(*) FROM b_tree_alert);\n"
" IF count > 0 THEN \n"
" CALL printf(\"[\\\"Alert\\\"],\\n\");\n"
" DECLARE C2 CURSOR FOR\n"
" SELECT 'TABLE SCAN VIOLATION: ' AS key, group_concat(info, ', ') AS info_list FROM table_scan_alert\n"
" UNION ALL\n"
" SELECT 'TEMP B-TREE VIOLATION: ' AS key, group_concat(info, ', ') AS info_list FROM b_tree_alert;\n"
" LOOP FETCH C2\n"
" BEGIN\n"
" CALL printf(\"[{\\\"value\\\": \\\"%s\", C2.key);\n"
" CALL printf(\"%s\", C2.info_list);\n"
" CALL printf(\"\\\", \\\"style\\\": {\\\"fontSize\\\": 14, \\\"color\\\": \\\"red\\\", \\\"fontWeight\\\": \\\"bold\\\"}}],\\n\");\n"
" END;\n"
" END IF;\n"
"END;\n"
);
}
static void emit_print_query_plan_stat_proc(charbuf *output) {
bprintf(output,
"%s",
"CREATE PROC print_query_plan_stat(id_ integer not null)\n"
"BEGIN\n"
" CALL printf(\" [\\n\");\n"
" DECLARE Ca CURSOR FOR\n"
" WITH\n"
" scan(name, count, priority) AS (\n"
" SELECT 'SCAN', COUNT(*), 0 \n"
" FROM plan_temp \n"
" WHERE zdetail LIKE '%scan%' AND sql_id = id_\n"
" ),\n"
" search(name, count, priority) AS (\n"
" SELECT 'SEARCH', COUNT(*), 4 \n"
" FROM plan_temp \n"
" WHERE zdetail LIKE '%search%' AND iselectid NOT IN (\n"
" SELECT iselectid \n"
" FROM plan_temp \n"
" WHERE zdetail LIKE '%search%using%covering%'\n"
" ) AND sql_id = id_\n"
" ),\n"
" search_fast(name, count, priority) AS (\n"
" SELECT 'SEARCH USING COVERING', COUNT(*), 5 \n"
" FROM plan_temp \n"
" WHERE zdetail LIKE '%search%using%covering%' AND sql_id = id_\n"
" ),\n"
" b_tree(name, count, priority) AS (\n"
" SELECT 'TEMP B-TREE', COUNT(*), 1 \n"
" FROM plan_temp \n"
" WHERE zdetail LIKE '%temp b-tree%' AND sql_id = id_\n"
" ),\n"
" compound_subqueries(name, count, priority) AS (\n"
" SELECT 'COMPOUND SUBQUERIES', COUNT(*), 2 \n"
" FROM plan_temp \n"
" WHERE zdetail LIKE '%compound subqueries%' AND sql_id = id_\n"
" ),\n"
" execute_scalar(name, count, priority) AS (\n"
" SELECT 'EXECUTE SCALAR', COUNT(*), 3 \n"
" FROM plan_temp \n"
" WHERE zdetail LIKE '%execute scalar%' AND sql_id = id_\n"
" )\n"
" SELECT \n"
" CASE name\n"
" WHEN 'SCAN' THEN '{\"value\": \"' || name || '\", \"style\": {\"fontSize\": 14, \"color\": \"red\", \"fontWeight\": \"bold\"}}'\n"
" WHEN 'TEMP B-TREE' THEN '{\"value\": \"' || name || '\", \"style\": {\"fontSize\": 14, \"color\": \"red\", \"fontWeight\": \"bold\"}}'\n"
" ELSE '\"' || name || '\"'\n"
" END name,\n"
" CASE name\n"
" WHEN 'SCAN' THEN '{\"value\": ' || count || ', \"style\": {\"fontSize\": 14, \"color\": \"red\", \"fontWeight\": \"bold\"}}'\n"
" WHEN 'TEMP B-TREE' THEN '{\"value\": ' || count || ', \"style\": {\"fontSize\": 14, \"color\": \"red\", \"fontWeight\": \"bold\"}}'\n"
" ELSE '' || count\n"
" END value\n"
" FROM (\n"
" SELECT * FROM scan\n"
" UNION ALL\n"
" SELECT * FROM search\n"
" UNION ALL\n"
" SELECT * FROM search_fast\n"
" UNION ALL\n"
" SELECT * FROM b_tree\n"
" UNION ALL\n"
" SELECT * FROM compound_subqueries\n"
" UNION ALL\n"
" SELECT * FROM execute_scalar\n"
" )\n"
" WHERE count > 0 ORDER BY priority ASC, count DESC;\n"
" CALL printf(\" [],\\n\");\n"
" LOOP FETCH Ca\n"
" BEGIN\n"
" CALL printf(\" [%s, %s],\\n\", Ca.name, Ca.value);\n"
" END;\n"
" CALL printf(\" []\\n\");\n"
" CALL printf(\" ],\\n\");\n"
"END;\n"
);
}
static void emit_print_query_plan_graph_proc(charbuf *output) {
bprintf(output,
"%s",
"CREATE PROC print_query_plan_graph(id_ integer not null)\n"
"BEGIN\n"
" CALL printf(\" \\\"\");\n"
" DECLARE C CURSOR FOR\n"
" WITH RECURSIVE\n"
" plan_chain(iselectid, zdetail, level) AS (\n"
" SELECT 0 as iselectid, '?' as zdetail, 0 as level\n"
" UNION ALL\n"
" SELECT plan_temp.iselectid, plan_temp.zdetail, plan_chain.level+1 as level\n"
" FROM plan_temp JOIN plan_chain ON plan_temp.iorder=plan_chain.iselectid WHERE plan_temp.sql_id = id_\n"
" ORDER BY 3 DESC\n"
" )\n"
" SELECT\n"
" substr(' ', 1, max(level - 1, 0)*4) ||\n"
" substr('|.............................', 1, min(level, 1)*4) ||\n"
" zdetail as graph_line FROM plan_chain;\n"
"\n"
" LOOP FETCH C\n"
" BEGIN\n"
" CALL printf(\"\\\\n%s\", C.graph_line);\n"
" END;\n"
" CALL printf(\"\\\"\\n\");\n"
"END;\n"
);
}
static void emit_print_query_plan(charbuf *output) {
bprintf(output,
"CREATE PROC print_query_plan(sql_id integer not null)\n"
"BEGIN\n"
" CALL printf(\"[\\n\");\n"
" CALL print_sql_statement(sql_id);\n"
" CALL print_query_plan_stat(sql_id);\n"
" CALL print_query_plan_graph(sql_id);\n"
" CALL printf(\"],\\n\");\n"
"END;\n"
);
}
#undef STMT_INIT
#define STMT_INIT(x) symtab_add(cg_stmts, k_ast_ ## x, (void *)cg_qp_ ## x)
#undef STMT_INIT_EXPL
#define STMT_INIT_EXPL(x) symtab_add(cg_stmts, k_ast_ ## x, (void *)cg_qp_explain_query_stmt)
#undef STMT_INIT_DDL
#define STMT_INIT_DDL(x) symtab_add(cg_stmts, k_ast_ ## x, (void *)cg_qp_sql_stmt)
cql_noexport void cg_query_plan_main(ast_node *head) {
sql_stmt_count = 0; // reset statics
Contract(options.file_names_count == 1);
cql_exit_on_semantic_errors(head);
exit_on_validating_schema();
cg_stmts = symtab_new();
STMT_INIT(create_proc_stmt);
// schema
// * note probably need to add declare select function to this list
// that can be needed to correctly parse the body of triggers or views (which might use the function)
STMT_INIT_DDL(create_table_stmt);
STMT_INIT_DDL(create_index_stmt);
STMT_INIT_DDL(create_view_stmt);
STMT_INIT_DDL(create_trigger_stmt);
// dml
STMT_INIT_EXPL(select_stmt);
STMT_INIT_EXPL(with_select_stmt);
STMT_INIT_EXPL(with_insert_stmt);
STMT_INIT_EXPL(update_stmt);
STMT_INIT_EXPL(delete_stmt);
STMT_INIT_EXPL(with_delete_stmt);
STMT_INIT_EXPL(insert_stmt);
STMT_INIT_EXPL(upsert_stmt);
STMT_INIT_EXPL(drop_table_stmt);
STMT_INIT_EXPL(drop_view_stmt);
STMT_INIT_EXPL(drop_index_stmt);
STMT_INIT_EXPL(begin_trans_stmt);
STMT_INIT_EXPL(commit_trans_stmt);
CHARBUF_OPEN(query_plans_buf);
query_plans = &query_plans_buf;
CHARBUF_OPEN(schema_stmts_buf);
schema_stmts = &schema_stmts_buf;
CHARBUF_OPEN(output_buf);
bprintf(&output_buf, "DECLARE PROC printf NO CHECK;\n");
gen_sql_callbacks callbacks;
init_gen_sql_callbacks(&callbacks);
callbacks.mode = gen_mode_no_annotations;
callbacks.variables_callback = &variables_callback;
cg_qp_callbacks = &callbacks;
cg_qp_stmt_list(head);
if (sql_stmt_count) {
bprintf(&output_buf, rt->source_prefix);
if (options.test) {
while (head->right) {
head = head->right;
}
bprintf(&output_buf, "-- The statement ending at line %d\n\n", head->left->lineno);
}
cg_qp_emit_declare_func(&output_buf);
bprintf(&output_buf, "\n");
cg_qp_emit_create_schema_proc(&output_buf);
bprintf(&output_buf, "\n");
emit_populate_no_table_scan_proc(&output_buf);
bprintf(&output_buf, "\n");
emit_populate_tables_proc(&output_buf);
bprintf(&output_buf, "\n");
emit_populate_table_scan_alert_table_proc(&output_buf);
bprintf(&output_buf, "\n");
emit_populate_b_tree_alert_table_proc(&output_buf);
bprintf(&output_buf, "\n");
emit_print_query_violation_proc(&output_buf);
bprintf(&output_buf, "\n");
emit_print_sql_statement_proc(&output_buf);
bprintf(&output_buf, "\n");
emit_print_query_plan_stat_proc(&output_buf);
bprintf(&output_buf, "\n");
emit_print_query_plan_graph_proc(&output_buf);
bprintf(&output_buf, "\n");
emit_print_query_plan(&output_buf);
bprintf(&output_buf, "\n");
}
// create an empty query_plan method even if there are no statements
// (the helpers above won't be needed)
bprintf(&output_buf, "CREATE PROC query_plan()\n");
bprintf(&output_buf, "BEGIN\n");
if (sql_stmt_count) {
bprintf(&output_buf, " CALL create_schema();\n");
bprintf(&output_buf, " BEGIN TRY\n");
bprintf(&output_buf, " CALL populate_no_table_scan();\n");
bprintf(&output_buf, " END TRY;\n");
bprintf(&output_buf, " BEGIN CATCH\n");
bprintf(&output_buf, " CALL printf(\"failed populating no_table_scan table\\n\");\n");
bprintf(&output_buf, " THROW;\n");
bprintf(&output_buf, " END CATCH;\n");
}
for (int32_t i = 1; i <= sql_stmt_count; i++) {
bprintf(&output_buf, " BEGIN TRY\n");
bprintf(&output_buf, " CALL populate_query_plan_%d();\n", i);
bprintf(&output_buf, " END TRY;\n");
bprintf(&output_buf, " BEGIN CATCH\n");
bprintf(&output_buf, " CALL printf(\"failed populating query %d\\n\");\n", i);
bprintf(&output_buf, " THROW;\n");
bprintf(&output_buf, " END CATCH;\n");
}
bprintf(&output_buf, " CALL printf(\"[\\n\");\n");
if (sql_stmt_count) {
bprintf(&output_buf, " CALL print_query_violation();\n");
}
bprintf(&output_buf, " CALL printf(\"[\\n\");\n");
bprintf(&output_buf, " CALL printf(\"[\\n\");\n");
bprintf(&output_buf, " CALL printf(\"[\\\"Query\\\", \\\"Stat\\\", \\\"Graph\\\"],\\n\");\n");
for (int32_t i = 1; i <= sql_stmt_count; i++) {
bprintf(&output_buf, " CALL print_query_plan(%d);\n", i);
}
bprintf(&output_buf, " CALL printf(\"[]\\n\");\n");
bprintf(&output_buf, " CALL printf(\"]\\n\");\n");
bprintf(&output_buf, " CALL printf(\"],\\n\");\n");
bprintf(&output_buf, " CALL printf(\"[]\\n\");\n");
bprintf(&output_buf, " CALL printf(\"]\");\n");
bprintf(&output_buf, "END;\n");
cql_write_file(options.file_names[0], output_buf.ptr);
CHARBUF_CLOSE(output_buf);
CHARBUF_CLOSE(schema_stmts_buf);
CHARBUF_CLOSE(query_plans_buf);
// Force the globals to null state so that they do not look like roots to LeakSanitizer
// all of these should have been freed already. This is the final safety net to prevent
// non-reporting of leaks.
schema_stmts = NULL;
query_plans = NULL;
cg_qp_callbacks = NULL;
}
#endif