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