static void cg_create_proc_stmt()

in sources/cg_c.c [3529:3834]


static void cg_create_proc_stmt(ast_node *ast) {
  Contract(is_ast_create_proc_stmt(ast));
  EXTRACT_STRING(name, ast->left);
  EXTRACT_NOTNULL(proc_params_stmts, ast->right);
  EXTRACT(params, proc_params_stmts->left);
  EXTRACT(stmt_list, proc_params_stmts->right);
  EXTRACT_MISC_ATTRS(ast, misc_attrs);

  bool_t private_proc = misc_attrs && exists_attribute_str(misc_attrs, "private");
  bool_t dml_proc = is_dml_proc(ast->sem->sem_type);
  bool_t result_set_proc = has_result_set(ast);
  bool_t out_stmt_proc = has_out_stmt_result(ast);
  bool_t out_union_proc = has_out_union_stmt_result(ast);
  bool_t calls_out_union = has_out_union_call(ast);
  proc_cte_index = 0;
  cur_bound_statement = 0;

  // sets base_fragment_name as well for the current fragment
  uint32_t frag_type = find_fragment_attr_type(misc_attrs, &base_fragment_name);

  if (frag_type == FRAG_TYPE_SHARED || frag_type == FRAG_TYPE_EXTENSION) {
    // shared fragments produce no code at all, no header, nothing
    // extension fragments, same story
    // put a line marker in the header file in case we want a test suite that verifies that

    if (options.test) {
      bprintf(cg_header_output, "\n// The statement ending at line %d\n//\n", ast->lineno);
    }

    return;
  }

  if (frag_type == FRAG_TYPE_BASE) {
    Contract(has_result_set(ast));
    cg_proc_result_set(ast);

    // Emit assembly_fetch_results, it has the same signature as the base only with a result set
    // instead of a statement.

    // Note we can do this prototype on an easy plan.  We know everything about the signature already
    // from the base_fragment_name.  We also know that it is not an out union proc or an out proc
    // because it's a fragment and we know it's a DML proc for the same reason.
    // So we're on the easiest plan for sure.

    CHARBUF_OPEN(temp);
      cg_emit_fetch_results_prototype(EMIT_DML_PROC, params, base_fragment_name, base_fragment_name, &temp);
      bprintf(cg_header_output, "%s%s);\n", rt->symbol_visibility, temp.ptr);
    CHARBUF_CLOSE(temp);

    return;
  }

  CHARBUF_OPEN(proc_fwd_ref);
  CHARBUF_OPEN(proc_contracts);
  CHARBUF_OPEN(proc_body);
  CHARBUF_OPEN(proc_locals);
  CHARBUF_OPEN(proc_cleanup);

  bool_t saved_error_target_used = error_target_used;
  error_target_used = false;
  return_used = false;

  int32_t saved_rcthrown_index = rcthrown_index;
  rcthrown_index = 0;

  bool_t saved_rcthrown_used = rcthrown_used;
  rcthrown_used = 0;

  bool_t saved_temp_emitted = temp_statement_emitted;
  bool_t saved_seed_declared = seed_declared;
  charbuf *saved_main = cg_main_output;
  charbuf *saved_decls = cg_declarations_output;
  charbuf *saved_scratch = cg_scratch_vars_output;
  charbuf *saved_cleanup = cg_cleanup_output;
  charbuf *saved_fwd_ref = cg_fwd_ref_output;
  cg_scratch_masks *saved_masks = cg_current_masks;

  Invariant(!use_encode);
  Invariant(!encode_context_column);
  Invariant(!encode_columns);
  encode_columns = symtab_new();

  cg_scratch_masks masks;
  cg_current_masks = &masks;
  cg_zero_masks(cg_current_masks);
  temp_statement_emitted = false;
  in_proc = true;
  current_proc = ast;
  seed_declared = false;

  init_encode_info(misc_attrs, &use_encode, &encode_context_column, encode_columns);

  bprintf(cg_declarations_output, "\n");

  // if you're doing out_union then the row fetcher is all there is
  CSTR suffix = out_union_proc ? "_fetch_results" : "";

  CG_CHARBUF_OPEN_SYM(proc_name_base, name);
  CG_CHARBUF_OPEN_SYM(proc_sym, name, suffix);

  bprintf(cg_declarations_output, "#define _PROC_ \"%s\"\n", proc_sym.ptr);

  if (out_stmt_proc || out_union_proc) {
    cg_c_struct_for_sptr(cg_fwd_ref_output, ast->sem->sptr, NULL);
    cg_struct_teardown_info(cg_declarations_output, ast->sem->sptr, NULL);
  }

  if (out_stmt_proc || out_union_proc || result_set_proc) {
    cg_proc_result_set(ast);
  }

  CHARBUF_OPEN(proc_decl);
  cg_emit_proc_prototype(ast, &proc_decl);

  if (out_union_proc) {
    CG_CHARBUF_OPEN_SYM(result_set_ref, name, "_result_set_ref");

    if (!calls_out_union) {
      CSTR rc = dml_proc ? "_rc_" : "SQLITE_OK";
      if (dml_proc) {
        bprintf(&proc_cleanup, "  %s_info.db = _db_;\n", proc_name_base.ptr);
      }
      bprintf(&proc_cleanup, "  cql_results_from_data(%s, &_rows_, &%s_info, (cql_result_set_ref *)_result_set_);\n",
        rc,
        proc_name_base.ptr);
      if (dml_proc) {
        bprintf(&proc_cleanup, "  %s_info.db = NULL;\n", proc_name_base.ptr);
      }

      CG_CHARBUF_OPEN_SYM(perf_index, name, "_perf_index");
        // emit profiling start signal
        bprintf(&proc_body, "  cql_profile_start(CRC_%s, &%s);\n", proc_name_base.ptr, perf_index.ptr);
      CHARBUF_CLOSE(perf_index);
    }
    else {
      // If this is a forwarding procedure then we have to ensure that we produce at least an empty
      // result even if the procedure didn't actually make the call to the forwarder.

      if (dml_proc) {
        bprintf(&proc_cleanup, "  if (_rc_ == SQLITE_OK && !*_result_set_) ");
      }
      else {
        bprintf(&proc_cleanup, "  if (!*_result_set_) ");
      }

      bprintf(&proc_cleanup, "*_result_set_ = (%s)cql_no_rows_result_set();\n", result_set_ref.ptr);
    }
    CHARBUF_CLOSE(result_set_ref);
  }

  // CREATE PROC [name] ( [params] )
  if (params) {
    cg_params_init(params, &proc_body);
    if (!private_proc) {
      cg_emit_contracts(params, &proc_contracts);
    }
  }

  if (out_stmt_proc) {
    bprintf(&proc_locals, "memset(_result_, 0, sizeof(*_result_));\n");
  }

  // If the proc has a result, don't expose a function with the proc name.
  // Consumers should use the _fetch_results function to execute the proc.
  // We don't make it "static" so that CQL-based tests can access it.  However
  // you would have to import it yourself to get access to the symbol (--generate_exports)
  charbuf *decl = (result_set_proc || out_stmt_proc) ? cg_fwd_ref_output : cg_header_output;

  if (private_proc)  {
    bprintf(decl, "// %s);\n", proc_decl.ptr);
  }
  else {
    bprintf(decl, "%s%s);\n", rt->symbol_visibility, proc_decl.ptr);
  }

  if (options.generate_exports && !private_proc) {
    gen_set_output_buffer(exports_output);
    gen_declare_proc_from_create_proc(ast);
    bprintf(exports_output, ";\n");
  }

  if (options.test) {
    // echo the export where it can be sanity checked
    if (private_proc) {
      bprintf(cg_declarations_output, "// private: ");
    }
    else {
      bprintf(cg_declarations_output, "// export: ");
    }

    gen_set_output_buffer(cg_declarations_output);
    gen_declare_proc_from_create_proc(ast);
    bprintf(cg_declarations_output, ";\n");
  }

  if (out_union_proc) {
    // clobber the out arg, it is assumed to be trash by convention
    bprintf(&proc_locals, "*_result_set_ = NULL;\n");
  }

  cg_fwd_ref_output = &proc_fwd_ref;
  cg_main_output = &proc_body;
  cg_declarations_output = &proc_locals;
  cg_scratch_vars_output = &proc_locals;
  cg_cleanup_output = &proc_cleanup;

  // BEGIN [stmt_list] END
  cg_stmt_list(stmt_list);

  cg_fwd_ref_output = saved_fwd_ref;
  cg_main_output = saved_main;
  cg_declarations_output = saved_decls;
  cg_scratch_vars_output = saved_scratch;
  cg_cleanup_output = saved_cleanup;
  cg_current_masks = saved_masks;
  temp_statement_emitted = saved_temp_emitted;
  seed_declared = saved_seed_declared;

  bprintf(cg_declarations_output, proc_fwd_ref.ptr);
  bprintf(cg_declarations_output, "%s) {\n", proc_decl.ptr);
  bprintf(cg_declarations_output, proc_contracts.ptr);

  if (dml_proc) {
    cg_emit_rc_vars(cg_declarations_output);
    if (result_set_proc) {
      bprintf(cg_declarations_output, "  *_result_stmt = NULL;\n");
    }
  }

  if (out_union_proc && !calls_out_union) {
    bprintf(cg_declarations_output, "  cql_bytebuf _rows_;\n");
    bprintf(cg_declarations_output, "  cql_bytebuf_open(&_rows_);\n");
  }

  bindent(cg_declarations_output, &proc_locals, 2);
  if (proc_locals.used > 1) {
    bprintf(cg_declarations_output, "\n");
  }

  bprintf(cg_declarations_output, "%s", proc_body.ptr);

  cg_line_directive_max(ast, cg_declarations_output);

  if (dml_proc) {
    bprintf(cg_declarations_output, "  _rc_ = SQLITE_OK;\n");
  }

  bool_t empty_statement_needed = false;

  if (error_target_used || return_used) {
    bprintf(cg_declarations_output, "\n%s:", error_target);
    empty_statement_needed = true;
  }

  bprintf(cg_declarations_output, "\n");

  if (proc_cleanup.used > 1) {
    bprintf(cg_declarations_output, proc_cleanup.ptr);
    empty_statement_needed = false;
  }

  if (result_set_proc) {
    // Because of control flow it's possible that we never actually ran a select statement
    // even if there were no errors.  Or maybe we caught the error.  In any case if we
    // are not producing an error then we have to produce an empty result set to go with it.
    bprintf(cg_declarations_output, "  if (_rc_ == SQLITE_OK && !*_result_stmt) _rc_ = cql_no_rows_stmt(_db_, _result_stmt);\n");
    empty_statement_needed = false;
  }

  if (dml_proc) {
    bprintf(cg_declarations_output, "  return _rc_;\n");
    empty_statement_needed = false;
  }

  if (empty_statement_needed) {
    bprintf(cg_declarations_output, "  ; // label requires some statement\n");
  }

  bprintf(cg_declarations_output, "}\n");
  bprintf(cg_declarations_output, "#undef _PROC_\n");

  CHARBUF_CLOSE(proc_decl);
  CHARBUF_CLOSE(proc_sym);
  CHARBUF_CLOSE(proc_name_base);
  CHARBUF_CLOSE(proc_cleanup);
  CHARBUF_CLOSE(proc_locals);
  CHARBUF_CLOSE(proc_body);
  CHARBUF_CLOSE(proc_contracts);
  CHARBUF_CLOSE(proc_fwd_ref);

  in_var_group_decl = false;
  in_var_group_emit = false;
  in_proc = false;
  use_encode = false;
  current_proc = NULL;
  base_fragment_name = NULL;

  symtab_delete(encode_columns);
  encode_context_column = NULL;
  encode_columns = NULL;
  error_target_used = saved_error_target_used;
  rcthrown_index = saved_rcthrown_index;
  rcthrown_used = saved_rcthrown_used;
  Invariant(!strcmp(error_target, CQL_CLEANUP_DEFAULT_LABEL));
  Invariant(!strcmp(rcthrown_current, CQL_RCTHROWN_DEFAULT));
}