static void cg_test_helpers_dummy_test()

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