in sources/cg_test_helpers.c [639:843]
static void cg_test_helpers_dummy_test(ast_node *stmt) {
Contract(is_ast_create_proc_stmt(stmt));
EXTRACT_STRING(proc_name, stmt->left);
CHARBUF_OPEN(create_triggers);
CHARBUF_OPEN(drop_triggers);
gen_create_triggers = &create_triggers;
gen_drop_triggers = &drop_triggers;
dummy_test_info info = {
.table_current = NULL,
.found_tables = NULL,
.found_views = NULL
};
// First thing we have to do is gather all the tables that are used transitively by the procedure
// that needs dummy_test helpers.
find_all_table_nodes(&info, stmt);
// If the create proc statement does not reference any tables, there is nothing to emit
if (info.found_tables == NULL) {
CHARBUF_CLOSE(drop_triggers);
CHARBUF_CLOSE(create_triggers);
return;
}
// There are some tables, so we've work to do, there are several types of functions emitted
// by this helper type, we're going to need them all.
int32_t value_seed = 123;
gen_sql_callbacks callbacks;
init_gen_sql_callbacks(&callbacks);
callbacks.if_not_exists_callback = cg_test_helpers_force_if_not_exists;
callbacks.mode = gen_mode_no_annotations;
CHARBUF_OPEN(gen_create_tables);
CHARBUF_OPEN(gen_drop_tables);
CHARBUF_OPEN(gen_populate_tables);
CHARBUF_OPEN(gen_read_tables);
CHARBUF_OPEN(gen_declare_funcs);
CHARBUF_OPEN(gen_drop_indexes);
// Here we record that we actually emitted some dummy test stuff for this proc, this helps us
// decide if we need the test markers in test mode.
helper_flags |= DUMMY_TEST;
// The found tables list begins in an order that is correct for dropping (i.e. the "leaf" tables/views are first)
// do that now...
for (list_item *item = info.found_tables; item; item = item->next) {
EXTRACT_ANY_NOTNULL(table_or_view, item->ast);
Invariant(is_ast_create_table_stmt(table_or_view) || is_ast_create_view_stmt(table_or_view));
CSTR table_name = get_table_or_view_name(table_or_view);
bprintf(&gen_drop_tables, "DROP %s IF EXISTS %s;\n", is_ast_create_table_stmt(table_or_view) ? "TABLE" : "VIEW", table_name);
}
// Reverse the list to get the tables back into a safe-to-declare order that we can loop over
// to emit table creation of parent tables before child tables.
reverse_list(&info.found_tables);
// For each found table we're going to do some table specific things
for (list_item *item = info.found_tables; item; item = item->next) {
EXTRACT_ANY_NOTNULL(table_or_view, item->ast);
ast_node *ast_to_emit = table_or_view;
// the virtual table ast in the symbol table points to the table decl part of the virtual table create
// we want the whole statement so we have to back up one notch up the tree.
bool_t is_virtual_table = table_or_view->parent && is_ast_create_virtual_table_stmt(table_or_view->parent);
if (is_virtual_table) {
ast_to_emit = table_or_view->parent;
}
// First thing we need is the CREATE DDL for the item in question, make that now
gen_set_output_buffer(&gen_create_tables);
gen_statement_with_callbacks(ast_to_emit, &callbacks);
bprintf(&gen_create_tables, ";\n");
// Next we need the DDL for any indices that may be on the table, we'll generate
// the CREATE for those indices and a DROP for the indices. The CREATE goes with
// the table creates. The indices may be dropped seperately so the DROP goes
// in its own buffer
CSTR table_name = get_table_or_view_name(table_or_view);
cg_emit_index_stmt(table_name, &gen_create_tables, &gen_drop_indexes, &callbacks);
// Next we generate a fragment to populate data for this table using the current seed value
// We don't do this for views or virtual tables
if (is_ast_create_table_stmt(table_or_view) && !is_virtual_table) {
cg_dummy_test_populate(&gen_populate_tables, table_or_view, &value_seed);
}
// Finally, there is a helper procedure for each table or view that just reads all that
// data out of it. Most tests don't use all of them but it's only test code so size doesn't
// matter so much and it's super easy to have them all handy so we aren't picky.
bprintf(&gen_read_tables, "\n");
bprintf(&gen_read_tables, "CREATE PROC test_%s_read_%s()\n", proc_name, table_name);
bprintf(&gen_read_tables, "BEGIN\n");
bprintf(&gen_read_tables, " SELECT * FROM %s;\n", table_name);
bprintf(&gen_read_tables, "END;\n");
}
// At this point we're done with all the tables, we're ready to generate the main methods
// plus do the rest of the housekeeping
// Emit declare functions because they may be needed for schema and query validation
// We don't try to guess which functions were used, we just emit the correct declarations for them all.
// We could in principle do this one time for the entire translation unit but duplicates don't hurt anyway.
gen_set_output_buffer(&gen_declare_funcs);
bprintf(&gen_declare_funcs, "\n");
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_one_stmt(any_func);
bprintf(&gen_declare_funcs, ";\n");
}
}
// declare functions
bprintf(cg_th_procs, "%s", gen_declare_funcs.ptr);
// create tables proc
bprintf(cg_th_procs, "\n");
bprintf(cg_th_procs, "CREATE PROC test_%s_create_tables()\n", proc_name);
bprintf(cg_th_procs, "BEGIN\n");
bindent(cg_th_procs, &gen_create_tables, 2);
bprintf(cg_th_procs, "END;\n");
// Create the triggers proc.
//
// We emit the trigger creation code in its own proc for two reasons:
//
// 1. If the code is part of the create table proc it might have unwanted
// effects on the dummy data populated later. Some dummy data in the table
// will likely be altered because of the triggers and the DB will end up in
// an unexpected state. Generally the dummy data is considered authoritative
// of the desire end state, it isn't transactions to be applied.
//
// 2. We want to give the engineer control of if/when the triggers are applied.
//
// We create the create/drop triggers helpers even for procs that don't use any
// tables with triggers. Otherwise callsites might have to change when triggers
// are added/removed from the schema.
if (gen_drop_triggers->used <= 1) {
// Similarly, to avoid the procs signature changing based on triggers being
// added/removed we use the below snippet to force the procedure to use the
// db-using signature, even if no triggers are actually created.
bprintf(gen_create_triggers, "IF @rc THEN END IF;\n");
}
bprintf(cg_th_procs, "\n");
bprintf(cg_th_procs, "CREATE PROC test_%s_create_triggers()\n", proc_name);
bprintf(cg_th_procs, "BEGIN\n");
bindent(cg_th_procs, gen_create_triggers, 2);
bprintf(cg_th_procs, "END;\n");
// populate tables proc
if (gen_populate_tables.used > 1) {
bprintf(cg_th_procs, "\n");
bprintf(cg_th_procs, "CREATE PROC test_%s_populate_tables()\n", proc_name);
bprintf(cg_th_procs, "BEGIN\n");
bindent(cg_th_procs, &gen_populate_tables, 2);
bprintf(cg_th_procs, "END;\n");
}
// drop tables proc
bprintf(cg_th_procs, "\n");
bprintf(cg_th_procs, "CREATE PROC test_%s_drop_tables()\n", proc_name);
bprintf(cg_th_procs, "BEGIN\n");
bindent(cg_th_procs, &gen_drop_tables, 2);
bprintf(cg_th_procs, "END;\n");
// drop trigger proc
if (gen_drop_triggers->used <= 1) {
bprintf(gen_drop_triggers, "IF @rc THEN END IF;\n");
}
bprintf(cg_th_procs, "\n");
bprintf(cg_th_procs, "CREATE PROC test_%s_drop_triggers()\n", proc_name);
bprintf(cg_th_procs, "BEGIN\n");
bindent(cg_th_procs, gen_drop_triggers, 2);
bprintf(cg_th_procs, "END;\n");
// read tables procedures
bprintf(cg_th_procs, "%s", gen_read_tables.ptr);
// drop indexes proc
if (gen_drop_indexes.used > 1) {
bprintf(cg_th_procs, "\n");
bprintf(cg_th_procs, "CREATE PROC test_%s_drop_indexes()\n", proc_name);
bprintf(cg_th_procs, "BEGIN\n");
bindent(cg_th_procs, &gen_drop_indexes, 2);
bprintf(cg_th_procs, "END;\n");
}
CHARBUF_CLOSE(gen_drop_indexes);
CHARBUF_CLOSE(gen_declare_funcs);
CHARBUF_CLOSE(gen_read_tables);
CHARBUF_CLOSE(gen_populate_tables);
CHARBUF_CLOSE(gen_drop_tables);
CHARBUF_CLOSE(gen_create_tables);
CHARBUF_CLOSE(drop_triggers);
CHARBUF_CLOSE(create_triggers);
}