sources/cg_c.c (5,269 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. */ // Perform codegen of the various nodes to "C". #if defined(CQL_AMALGAM_LEAN) && !defined(CQL_AMALGAM_CG_C) // stubs to avoid link errors. cql_noexport void cg_c_main(ast_node *head) {} cql_noexport void cg_c_init(void) {} cql_noexport void cg_c_cleanup() {} #else #include "cg_c.h" #include "ast.h" #include "bytebuf.h" #include "cg_common.h" #include "charbuf.h" #include "compat.h" #include "cql.h" #include "gen_sql.h" #include "list.h" #include "sem.h" #include "eval.h" #include "symtab.h" #include "encoders.h" static void cg_expr(ast_node *node, charbuf *is_null, charbuf *value, int32_t pri); static void cg_stmt_list(ast_node *node); static void cg_get_column(sem_t sem_type, CSTR cursor, int32_t index, CSTR var, charbuf *output); static void cg_binary(ast_node *ast, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new); static void cg_store_same_type(charbuf *output, CSTR var, sem_t sem_type, CSTR is_null, CSTR value); static void cg_store(charbuf *output, CSTR var, sem_t sem_type_var, sem_t sem_type_expr, CSTR is_null, CSTR value); static void cg_call_stmt_with_cursor(ast_node *ast, CSTR cursor_name); static void cg_proc_result_set(ast_node *ast); static void cg_var_decl(charbuf *output, sem_t sem_type, CSTR base_name, bool_t is_local); static void cg_emit_external_arglist(ast_node *expr_list, charbuf *prep, charbuf *invocation, charbuf *cleanup); static void cg_call_named_external(CSTR name, ast_node *expr_list); static void cg_user_func(ast_node *ast, charbuf *is_null, charbuf *value); static void cg_copy(charbuf *output, CSTR var, sem_t sem_type_var, CSTR value); static void cg_insert_dummy_spec(ast_node *ast); static void cg_release_out_arg_before_call(sem_t sem_type_arg, sem_t sem_type_param, CSTR name); static void cg_refs_offset(charbuf *output, sem_struct *sptr, CSTR offset_sym_name, CSTR struct_name); static void cg_col_offsets(charbuf *output, sem_struct *sptr, CSTR sym_name, CSTR struct_name); static void cg_declare_simple_var(sem_t sem_type, CSTR name); static void cg_data_type(charbuf *buffer, bool_t encode, sem_t sem_type); cql_noexport void cg_c_init(void); // Emits a sql statement with bound args. static void cg_bound_sql_statement(CSTR stmt_name, ast_node *stmt, int32_t cg_exec); // These globals represent the major state of the code-generator // True if we are presently emitting a stored proc static bool_t in_proc = false; // True if we presently declaring a variable group static bool_t in_var_group_decl = false; // True if we presently emitting a variable group static bool_t in_var_group_emit = false; // True if we are in a loop (hence the statement might run again) static bool_t cg_in_loop = false; // exports file if we are outputing exports static charbuf *exports_output = NULL; // The stack level, which facilitates safe re-use of scratch variables. static int32_t stack_level = 0; // Every string literal in a compiland gets a unique number. This is it. static int32_t string_literals_count = 0; // Every output string piece gets unique number, the offset of the string in the frag buffer // this tracks the biggest number we've seen so far. static int32_t piece_last_offset = 0; // Case statements might need to generate a unique label for their "else" code // We count the statements to make an easy label static int32_t case_statement_count = 0; // We need a local to hold the (const char *) conversion of a string reference // when calling out to external code. This gives each such temporary a unique name. static int32_t temp_cstr_count = 0; // This tells us if we needed a temporary statement to do an exec or prepare // with no visible statement result. If we emitted the temporary we have to // clean it up. Examples of this set x := (select 1); or DELETE from foo; static bool_t temp_statement_emitted = 0; // This tells us if we have already emitted the declaration for the dummy data // seed variable holder _seed_ in the current context. static bool_t seed_declared; // Each catch block needs a unique pair of lables, they are numbered. static int32_t catch_block_count = 0; // Used to give us a clue about when it might be smart to emit diagnostic // output but otherwise uninteresting. We increment this on ever nested block. cql_data_defn( int32_t stmt_nesting_level ); #define CQL_CLEANUP_DEFAULT_LABEL "cql_cleanup" // In the event of a failure of a sql block or a throw we need to emit // a goto to the current cleanup target. This is it. Try/catch manipulate this. static CSTR error_target = CQL_CLEANUP_DEFAULT_LABEL; #define CQL_RCTHROWN_DEFAULT "SQLITE_OK" // no variable at the root level, it's just "ok" // When we need the most recent caught error code we have to use the variable that is // holding the right value. Each catch scope has its own corresponding to the error // that it caught. static CSTR rcthrown_current = CQL_RCTHROWN_DEFAULT; static int32_t rcthrown_index = 0; static bool_t rcthrown_used = false; // We set this to true when we have used the error target in the current context // The current context is either the current procedure or the current try/catch block // If this is true we need to emit the cleanup label. static bool_t error_target_used = false; // We set this to true if a "return" statement happened in a proc. This also // forces the top level "cql_cleanup" to be emitted. We need a different flag for this // because no matter how deeply nested we are "return" goes to the outermost error target. // If this is set we will emit that top level target even if there were no other uses. static bool_t return_used = false; // String literals are frequently duplicated, we want a unique constant for each piece of text static symtab *string_literals; // Statement text pieces are frequently duplicated, we want a unique constant for each chunk of DML/DDL // To avoid confusion with shared fragments and/or extension fragments we call the bits of text // used to create SQL with the --compress option "pieces" static symtab *text_pieces; // The current shared fragment number in the current procdure static int32_t proc_cte_index; // This is the mapping between the original parameter name and the aliased name // for a particular parameter of a particular shared CTE fragment static symtab *proc_arg_aliases; // This tells us if we are currently processing an inline fragment in which case // we know there are no local variables only parameters and those have been // remapped as part of the inlining process static bool_t in_inline_function_fragment; // This is the mapping between the original CTE and the aliased name // for a particular parameter of a particular shared CTE fragment static symtab *proc_cte_aliases; // Shared fragment management state // These are the important fragment classifications, we can use simpler codegen if // some of these are false. static bool_t has_conditional_fragments; static bool_t has_shared_fragments; static bool_t has_variables; // Each bound statement in a proc gets a unique index static int32_t cur_bound_statement; // this holds the text of the generated SQL broken at fragment boundaries static bytebuf shared_fragment_strings = {NULL, 0, 0}; // these track the current and max predicate number, these // correspond 1:1 with a fragment string in the shared_fragment_strings buffer static int32_t max_fragment_predicate = 0; static int32_t cur_fragment_predicate = 0; // these track the current variable count, we snapshot the previous count // before generating each fragment string so we know how many variables were in there // we use these to emit the appropriate booleans for each bound variable static int32_t prev_variable_count; static int32_t cur_variable_count; // See cg_find_best_line for more details on why this is what it is. // All that's going on here is we recursively visit the tree and find the smallest // line number that matches the given file in that branch. static int32_t cg_find_first_line_recursive(ast_node *ast, CSTR filename) { int32_t line = INT32_MAX; int32_t lleft = INT32_MAX; int32_t lright = INT32_MAX; // file name is usually the same actual string but not always if (ast->filename == filename || !strcmp(filename, ast->filename)) { line = ast->lineno; } if (ast_has_left(ast)) { lleft = cg_find_first_line_recursive(ast->left, filename); if (lleft < line) line = lleft; } if (ast_has_right(ast)) { lright = cg_find_first_line_recursive(ast->right, filename); if (lright < line) line = lright; } return line; } // What's going on here is that the AST is generated on REDUCE operations. // that means the line number at the time any AST node was generated is // the largest line number anywhere in that AST. But if we're looking for // the line number for a statement we want the line number where it started. // The way to get that is to recurse through the tree and choose the smallest // line number anywhere in the tree. But, we must only use line numbers // from the same file as the one we ended on. If (e.g.) a procedure spans files // this will cause jumping around but that's not really avoidable. static int32_t cg_find_first_line(ast_node *ast) { return cg_find_first_line_recursive(ast, ast->filename); } // emit the line directive, escape the file name using the C convention static void cg_line_directive(CSTR filename, int32_t lineno, charbuf *output) { if (options.test || options.nolines) { return; } CHARBUF_OPEN(tmp); cg_encode_c_string_literal(filename, &tmp); bprintf(output, "#line %d %s\n", lineno, tmp.ptr); CHARBUF_CLOSE(tmp); } // use the recursive search to emit the smallest line number in this subtree static void cg_line_directive_min(ast_node *ast, charbuf *output) { int32_t lineno = cg_find_first_line(ast); cg_line_directive(ast->filename, lineno, output); } // The line number in the node is the last line number in the subtree because // that is when the REDUCE operation happened when building the AST. static void cg_line_directive_max(ast_node *ast, charbuf *output) { cg_line_directive(ast->filename, ast->lineno, output); } // The situation in CQL is that most statements, even single line statements, // end up generating many lines of C. So the normal situation is that you need // to emit additional # directives to stay on the same line you were already on. // In fact basically the situation is that you want to stay on your current line // until the next time you see an explicit switch. So what we do here is this: // // * when we see a # line directive (e.g. # 32 "foo") we remember that line // * if we are in a proc, emit the last such directive (just the line number) before each line // * when we find an actual #line directive, we don't also emit the last known directive too // * no additional outputs outside of procedures // * we use the #define _PROC_ and #undef _PROC_ markers to know if we are in a proc // // Typical output: // // # 1 "foo.sql" // // /* // CREATE PROC foo () // BEGIN // IF 1 THEN // CALL printf("one"); // ELSE // CALL printf("two"); // END IF; // END; // */ // // #define _PROC_ "foo" // # 1 // void foo(void) { // # 3 "x" // if (1) { // # 4 "x" // printf("one"); // # 4 // } // # 4 // else { // # 6 "x" // printf("two"); // # 6 // } // # 6 // // # 6 // } // #undef _PROC_ // static void cg_insert_line_directives(CSTR input, charbuf *output) { CHARBUF_OPEN(last_line_directive); CHARBUF_OPEN(line); CSTR start_proc = "#define _PROC_ "; // this marks the start of a proc size_t start_proc_len = strlen(start_proc); CSTR line_directive = "\x23line "; // this marks the end of a proc size_t line_directive_len = strlen(line_directive); CSTR end_proc = "#undef _PROC_"; // this marks the end of a proc size_t end_proc_len = strlen(end_proc); // true between the markers above (i.e. in the text of a procedure) bool_t now_in_proc = false; // true immediately after we see something like # 32 "foo.sql" in the input bool_t suppress_because_new_directive = false; while (breadline(&line, &input)) { CSTR trim = line.ptr; while (*trim == ' ') trim++; if (!strncmp(trim, start_proc, start_proc_len)) { // entering a procedure, we will start to emit additional line directives to stay on the same line bprintf(output, "%s\n", line.ptr); now_in_proc = true; continue; } else if (!strncmp(trim, end_proc, end_proc_len)) { // leaving a procedure, we will no longer emit additional line directives to stay on the same line bprintf(output, "%s\n", line.ptr); now_in_proc = false; continue; } else if ((trim[0] == '#' && trim[1] == ' ') || !strncmp(trim, line_directive, line_directive_len)) { bclear(&last_line_directive); bprintf(&last_line_directive, "%s", trim); bprintf(output, "%s\n", last_line_directive.ptr); char* line_start = strchr(last_line_directive.ptr, ' '); char* next_space = strchr(line_start + 1, ' '); if (next_space) *next_space = '\0'; // this prevents us from emitting the sequence // #line 32 "foo.sql" // #line 32 // the second # 32 would be a waste... suppress_because_new_directive = true; continue; } if (last_line_directive.ptr[0] && !suppress_because_new_directive && now_in_proc) { // this forces us to stay on the current line until we explicitly switch lines // every line becomes // #line 32 // [whatever] bprintf(output, "%s\n", last_line_directive.ptr); } suppress_because_new_directive = false; bprintf(output, "%s\n", line.ptr); } CHARBUF_CLOSE(line); CHARBUF_CLOSE(last_line_directive); } // return the symbol name for the string literal if there is one static CSTR find_literal(CSTR str) { symtab_entry *entry = symtab_find(string_literals, str); return entry ? entry->val : NULL; } // the current proc name or null static CSTR current_proc_name() { if (current_proc) { ast_node *proc_name_ast = get_proc_name(current_proc); EXTRACT_STRING(proc_name, proc_name_ast); return proc_name; } return NULL; } // generate an error if the given expression is true (note this drives tracing) static void cg_error_on_expr(CSTR expr) { bprintf(cg_main_output, "if (%s) { cql_error_trace(); goto %s; }\n", expr, error_target); error_target_used = true; } // generate an error if the return code is not the required value (helper for common case) static void cg_error_on_rc_notequal(CSTR required) { CHARBUF_OPEN(tmp); bprintf(&tmp, "_rc_ != %s", required); cg_error_on_expr(tmp.ptr); CHARBUF_CLOSE(tmp); } // generate an error if the return code is not SQLITE_OK (helper for common case) static void cg_error_on_not_sqlite_ok() { cg_error_on_expr("_rc_ != SQLITE_OK"); } // This tells us if a subtree should be wrapped in () // Basically we know the binding strength of the context (pri) and the current element (pri_new) // Weaker contexts get parens. Equal contexts get parens on the right side because all ops // are left to right associtive in SQL. Stronger child contexts never need parens because // the operator already binds tighter than its parent in the tree. static bool_t needs_paren(ast_node *ast, int32_t pri_new, int32_t pri) { // if the priorities are different then parens are needed // if and only if the new priority (this node) is weaker than the // containing priority (the parent node) if (pri_new != pri) { return pri_new < pri; } // If equal binding strength, put parens on the right of the expression // because our entire world is left associative. // // so e.g. *(a, /(b,c)) becomes a*(b/c); return ast->parent->right == ast; } // We have a series of masks to remember if we have emitted any given scratch variable. // We might need several temporaries at the same level if different types appear // at the same level but in practice we tend not to run into such things. Mostly // this works very well at arranging for the same scratch nullable int (or whatever) // to be re-used in every statement. The stack depth is limited to bundles of 64bits // with thisrepresentation. One bit for each stack level tracks if the temp has been // generated. This could be extended if needed... typedef struct cg_type_masks { uint64_t reals[CQL_MAX_STACK/64]; uint64_t bools[CQL_MAX_STACK/64]; uint64_t ints[CQL_MAX_STACK/64]; uint64_t longs[CQL_MAX_STACK/64]; uint64_t strings[CQL_MAX_STACK/64]; uint64_t objects[CQL_MAX_STACK/64]; uint64_t blobs[CQL_MAX_STACK/64]; } cg_type_masks; // There is one set of masks for nullables and another for not-nullables. // The later doesn't get used very frequently... typedef struct cg_scratch_masks { cg_type_masks nullables; cg_type_masks notnullables; } cg_scratch_masks; // Any new name context might need new temporaries, this points to the current // context. In practice it is set when we start processing a proc and it // is cleared when we exit that proc. static cg_scratch_masks *_Nullable cg_current_masks; // just like it sounds static void cg_zero_masks(cg_scratch_masks *_Nonnull masks) { memset(masks, 0, sizeof(*masks)); } // emit the type decl for the reference and the typeid if needed (msys only) static void cg_result_set_type_decl(charbuf *output, CSTR sym, CSTR ref) { // The result type might be defined several times if a base fragment is included in many outputs // they are all equivalent so we guard them here. For normal queries this doesn't happen // because you only include the proc once. The other parts of the base fragment are // idempotent to the compiler so no need to guard those prototypes. Indeed if they are // different because of a build problem, best to let the compiler complain. If you // generated all the base fragments from the same proc they MUST be the same result so // errors in those parts are for sure build issues. bprintf(output, "#ifndef result_set_type_decl_%s\n", sym); bprintf(output, "#define result_set_type_decl_%s 1\n", sym); bprintf(output, "cql_result_set_type_decl(%s, %s);\n", sym, ref); // if the result type needs extra type info, let it do so. rt->result_set_type_decl_extra && rt->result_set_type_decl_extra(output, sym, ref); bprintf(output, "#endif\n"); } // When emitting a variable reference, you might want the full local variable // defintion or just the declaration parts as they would appear in a prototype. #define CG_VAR_DECL_PROTO 0 #define CG_VAR_DECL_LOCAL 1 // Reference types and non-null locals begin at a zero value. References are especially // crucial because if they started at something other than null then we would try to // release that pointer on exit which would be bad. Note that this means that even // a non-null text variable (for instance) begins at null when it is initialized. This is // much like the _Nonnull clang option which can't prevent a global variable from starting // at null. It's a bit weird but there isn't really a viable alternative short of some // non-null BS value which seems worse. static void cg_emit_local_init(charbuf *output, sem_t sem_type) { sem_t core_type = core_type_of(sem_type); bool_t notnull = is_not_nullable(sem_type); switch (core_type) { case SEM_TYPE_INTEGER: if (notnull) { bprintf(output, " = 0"); } break; case SEM_TYPE_TEXT: bprintf(output, " = NULL"); break; case SEM_TYPE_BLOB: bprintf(output, " = NULL"); break; case SEM_TYPE_OBJECT: bprintf(output, " = NULL"); break; case SEM_TYPE_LONG_INTEGER: if (notnull) { bprintf(output, " = 0"); } break; case SEM_TYPE_REAL: if (notnull) { bprintf(output, " = 0"); } break; case SEM_TYPE_BOOL: if (notnull) { bprintf(output, " = 0"); } break; } } // The nullable primtives get the cql_set_null macro treatment instead of an assigment. // So all nullables begin at null. static void cg_emit_local_nullable_init(charbuf *output, CSTR name, sem_t sem_type) { sem_t core_type = core_type_of(sem_type); switch (core_type) { case SEM_TYPE_INTEGER: case SEM_TYPE_LONG_INTEGER: case SEM_TYPE_REAL: case SEM_TYPE_BOOL: if (in_proc) { bprintf(output, "cql_set_null(%s);\n", name); } break; case SEM_TYPE_TEXT: case SEM_TYPE_OBJECT: case SEM_TYPE_BLOB: break; } } // Emit the nullability annotation for a BLOB, OBJECT, or TEXT variable. static void cg_var_nullability_annotation(charbuf *output, sem_t sem_type) { sem_t core_type = core_type_of(sem_type); Contract(core_type == SEM_TYPE_BLOB || core_type == SEM_TYPE_OBJECT || core_type == SEM_TYPE_TEXT); if (is_out_parameter(sem_type) && !is_in_parameter(sem_type)) { // In the case of an OUT NOT NULL variable (but *not* an INOUT NOT NULL // variable), we annotate with _Nullable despite the NOT NULL because // purpose of calling the procedure is to initialize the location pointed to // with a value. Accordingly, the value at the location pointed to should be // NULL before the call. For example, `TEXT t OUT NOT NULL` should become // `cql_string_ref _Nullable *_Nonnull t`, whereas `TEXT t INOUT NOT NULL` // should become `cql_string_ref _Nonnull *_Nonnull t`. bprintf(output, "_Nullable "); return; } else if (is_not_nullable(sem_type)) { bprintf(output, "_Nonnull "); } else { bprintf(output, "_Nullable "); } } // Emit a declaration for a local whose name is base_name and whose type // is given by sem_type. Is_local really only decides if we add ";\n" to // the end of the output. This lets us use the same helper for list of // arg-prototypes as a list of declarations. // The real "trick" here is: // * flags might say it's an output parameter in which case we declare a pointer // * flags might indicate nullable, in which case we need the struct version // * text is always a reference, nullable or no. But if you make a text local // then we also gotta clean it up. static void cg_var_decl(charbuf *output, sem_t sem_type, CSTR base_name, bool_t is_local) { Contract(is_unitary(sem_type)); Contract(!is_null_type(sem_type)); Contract(cg_main_output); sem_t core_type = core_type_of(sem_type); bool_t notnull = is_not_nullable(sem_type); CHARBUF_OPEN(name); if (is_out_parameter(sem_type)) { bprintf(&name, "*_Nonnull "); } bprintf(&name, "%s", base_name); switch (core_type) { case SEM_TYPE_INTEGER: if (notnull) { bprintf(output, "%s %s", rt->cql_int32, name.ptr); } else { bprintf(output, "cql_nullable_int32 %s", name.ptr); } break; case SEM_TYPE_TEXT: bprintf(output, "%s ", rt->cql_string_ref); if (!is_local) { cg_var_nullability_annotation(output, sem_type); } bprintf(output, "%s", name.ptr); if (is_local) { bprintf(cg_cleanup_output, " %s(%s);\n", rt->cql_string_release, name.ptr); } break; case SEM_TYPE_BLOB: bprintf(output, "%s ", rt->cql_blob_ref); if (!is_local) { cg_var_nullability_annotation(output, sem_type); } bprintf(output, "%s", name.ptr); if (is_local) { bprintf(cg_cleanup_output, " %s(%s);\n", rt->cql_blob_release, name.ptr); } break; case SEM_TYPE_OBJECT: bprintf(output, "%s ", rt->cql_object_ref); if (!is_local) { cg_var_nullability_annotation(output, sem_type); } bprintf(output, "%s", name.ptr); if (is_local) { bprintf(cg_cleanup_output, " %s(%s);\n", rt->cql_object_release, name.ptr); } break; case SEM_TYPE_LONG_INTEGER: if (notnull) { bprintf(output, "%s %s", rt->cql_int64, name.ptr); } else { bprintf(output, "cql_nullable_int64 %s", name.ptr); } break; case SEM_TYPE_REAL: if (notnull) { bprintf(output, "%s %s", rt->cql_double, name.ptr); } else { bprintf(output, "cql_nullable_double %s", name.ptr); } break; case SEM_TYPE_BOOL: if (notnull) { bprintf(output, "%s %s", rt->cql_bool, name.ptr); } else { bprintf(output, "cql_nullable_bool %s", name.ptr); } break; } if (is_local) { cg_emit_local_init(output, sem_type); bprintf(output, ";\n"); if (!notnull) { cg_emit_local_nullable_init(output, name.ptr, sem_type); } } CHARBUF_CLOSE(name); } // Sometimes when we need a scratch variable to store an intermediate result // we can avoid the scratch variable entirely and use the target of the assignment // in flight for the storage. For instance: // declare x, y integer; // set y := 1; // set x := y + 3; // generates: // cql_set_notnull(y, 1); // cql_set_nullable(x, y.is_null, y.value + 3); // // A scratch variable is not used to hold the result of the RHS of the set because // the target of the assignment is known and compatible. // The target must match the exact type including nullability. Note bogus // sensitive assignments or incompatible assignments were already ruled out // in semantic analysis. static bool_t is_assignment_target_reusable(ast_node *ast, sem_t sem_type) { if (ast && ast->parent && (is_ast_assign(ast->parent) || is_ast_let_stmt(ast->parent))) { EXTRACT_ANY_NOTNULL(name_ast, ast->parent->left); sem_t sem_type_target = name_ast->sem->sem_type; sem_type_target &= (SEM_TYPE_CORE | SEM_TYPE_NOTNULL); return sem_type_target == sem_type; } return false; } // The scratch variable helper uses the given sem_type and the current // stack level to create a temporary variable name for that type at that level. // If the variable does not already have a declaration (as determined by the masks) // then a declaration is added to the scratch_vars section. This is one of the root // ways of getting an .is_null and .value back. Note that not null variables always // have a .is_null of "0" which becomes important when deciding how to assign // one result to another. Everything stays uniform. static void cg_scratch_var(ast_node *ast, sem_t sem_type, charbuf *var, charbuf *is_null, charbuf *value) { Contract(is_unitary(sem_type)); Contract(!is_null_type(sem_type)); sem_t core_type = core_type_of(sem_type); sem_type &= (SEM_TYPE_CORE | SEM_TYPE_NOTNULL); Contract(stack_level < CQL_MAX_STACK); // try to avoid creating a scratch variable if we can use the target of an assignment in flight. if (is_assignment_target_reusable(ast, sem_type)) { Invariant(ast && ast->parent && ast->parent->left); EXTRACT_ANY_NOTNULL(name_ast, ast->parent->left); EXTRACT_STRING(name, name_ast); if (is_out_parameter(name_ast->sem->sem_type)) { bprintf(var, "*%s", name); } else { bprintf(var, "%s", name); } } else { // Generate a scratch variable name of the correct type. We don't generate // the declaration of any given scratch variable more than once. We use the // current stack level to make the name. This means that have to burn a stack level // if you want more than one scratch. Stacklevel is normally increased by // the CG_PUSH_EVAL macro which does the recursion but it can also be manually // increased if temporaries are needed for some other reason. Any level of // recursion is expected to fix all that. CSTR prefix; cg_type_masks *pmask; if (is_nullable(sem_type)) { pmask = &cg_current_masks->nullables; prefix = "_tmp_n"; } else { pmask = &cg_current_masks->notnullables; prefix = "_tmp"; } uint64_t *usedmask; switch (core_type) { case SEM_TYPE_INTEGER: bprintf(var, "%s_int_%d", prefix, stack_level); usedmask = pmask->ints; break; case SEM_TYPE_BLOB: bprintf(var, "%s_blob_%d", prefix, stack_level); usedmask = pmask->blobs; break; case SEM_TYPE_OBJECT: bprintf(var, "%s_object_%d", prefix, stack_level); usedmask = pmask->objects; break; case SEM_TYPE_TEXT: bprintf(var, "%s_text_%d", prefix, stack_level); usedmask = pmask->strings; break; case SEM_TYPE_LONG_INTEGER: bprintf(var, "%s_int64_%d", prefix, stack_level); usedmask = pmask->longs; break; case SEM_TYPE_REAL: bprintf(var, "%s_double_%d", prefix, stack_level); usedmask = pmask->reals; break; case SEM_TYPE_BOOL: bprintf(var, "%s_bool_%d", prefix, stack_level); usedmask = pmask->bools; break; } int32_t index = stack_level/64; uint64_t mask = ((uint64_t)1) << (stack_level % 64); // Emit scratch if needed. if (!(usedmask[index] & mask)) { cg_var_decl(cg_scratch_vars_output, sem_type, var->ptr, CG_VAR_DECL_LOCAL); usedmask[index] |= mask; } } // If the is_null and value expressions are desired, generate them here. if (is_null && value) { if (is_ref_type(sem_type)) { // note that because reference types begin initialized to null we have to check their // value even though they are "non-null" so the is_null expression can't be 0 for these ever. bprintf(is_null, "!%s", var->ptr); bprintf(value, "%s", var->ptr); } else if (is_not_nullable(sem_type)) { bprintf(is_null, "0"); bprintf(value, "%s", var->ptr); } else { bprintf(is_null, "%s.is_null", var->ptr); bprintf(value, "%s.value", var->ptr); } } } // This helper deals with one of the most common situations we have a nullable // result that has to go into the result variable "var" and it's coming from one // or two nullable sources. The standard combine rules are that if eiter is null // the result must be null. However, some might be known to be not-null at compile // time. This function generates a call to the best runtime helper. By doing it here // all of the callers do not have to know all of the cases. Here "val" is some // arithmetic or logical combination of the values. static void cg_combine_nullables(charbuf *out, CSTR var, CSTR l_is_null, CSTR r_is_null, CSTR val) { // generate the optimal set expression for the target nullable if (!strcmp(l_is_null, "1") || !strcmp(r_is_null, "1")) { // either known to be null, result is null bprintf(out, "cql_set_null(%s);\n", var); } else if (!strcmp(l_is_null, "0") && !strcmp(r_is_null, "0")) { // both known to be not null bprintf(out, "cql_set_notnull(%s, %s);\n", var, val); } else if (!strcmp(l_is_null, "0")) { // left known to be not null, null if right is null // Note: the target of the assignment is only compatible with the source, not identical // So the macro here generates the assignment field by field which gives free conversions. bprintf(out, "cql_set_nullable(%s, %s, %s);\n", var, r_is_null, val); } else if (!strcmp(r_is_null, "0")) { // right known to be not null, null if left is null // Note: the target of the assignment is only compatible with the source, not identical // So the macro here generates the assignment field by field which gives free conversions. bprintf(out, "cql_set_nullable(%s, %s, %s);\n", var, l_is_null, val); } else { // either could be null bprintf(out, "cql_combine_nullables(%s, %s, %s, %s);\n", var, l_is_null, r_is_null, val); } } // We pass this on to the general combine handler, letting it think the second operand was not null. // There is of course no second operand. We do this because one of the args might be a null literal // or known not null for some other reason in which case we can make vastly simpler code. That logic // is already done in the above in the general case. This call probably ends up in // cg_set_nullable_nontrivial after the nullchecks are done. static void cg_set_nullable(charbuf *out, CSTR var, CSTR is_null, CSTR value) { cg_combine_nullables(out, var, is_null, "0", value); } // Set nullable output type to null. The only trick here is that reference types // need the ref counting stuff. // NOTE: there are lots of cases where even not-nullable ref types have to be set to null. // These correspond to cases where the value is known to be uninitialized and we need // to get something sane in there so that there isn't junk. Or likewise where the value // must become uninitialized because it's logically not readable anymore (e.g. in a cursor with no data). // We have to free the data... so we have to set it to null... This is a bit weird but the alternative // is some not-null sentinel constant string like the empty string which seems worse... static void cg_set_null(charbuf *output, CSTR name, sem_t sem_type) { if (is_blob(sem_type)) { bprintf(output, "cql_set_blob_ref(&%s, NULL);\n", name); } else if (is_object(sem_type)) { bprintf(output, "cql_set_object_ref(&%s, NULL);\n", name); } else if (is_text(sem_type)) { bprintf(output, "cql_set_string_ref(&%s, NULL);\n", name); } else if (is_nullable(sem_type)) { bprintf(output, "cql_set_null(%s);\n", name); } } // Once we've done any type conversions for the basic types we can do pretty simple assignments // The nullable non-reference types typically need of the helper macros unless it's an exact-type copy // operation. This function is used by cg_store near the finish line. static void cg_copy(charbuf *output, CSTR var, sem_t sem_type_var, CSTR value) { if (is_text(sem_type_var)) { bprintf(output, "cql_set_string_ref(&%s, %s);\n", var, value); } else if (is_blob(sem_type_var)) { bprintf(output, "cql_set_blob_ref(&%s, %s);\n", var, value); } else if (is_object(sem_type_var)) { if (var[0] == '*') { // this is just to avoid weird looking &*foo in the output which happens // when the target is an output variable bprintf(output, "cql_set_object_ref(%s, %s);\n", var+1, value); } else { bprintf(output, "cql_set_object_ref(&%s, %s);\n", var, value); } } else { bprintf(output, "%s = %s;\n", var, value); } } // Functions are a little special in that they can return reference types that come // with a +1 reference. To handle those you do not want to upcount the target. // We release whatever we're holding and then hammer it with the new value // with no upcount using the +1 we were given. static void cg_copy_for_create(charbuf *output, CSTR var, sem_t sem_type_var, CSTR value) { if (is_text(sem_type_var)) { bprintf(cg_main_output, "%s(%s);\n", rt->cql_string_release, var); } else if (is_blob(sem_type_var)) { bprintf(output, "%s(%s);\n", rt->cql_blob_release, var); } else if (is_object(sem_type_var)) { bprintf(output, "%s(%s);\n", rt->cql_object_release, var); } bprintf(output, "%s = %s;\n", var, value); } // This is most general store function. Given the type of the destination and the type of the source // plus the is_null and value of the source it generates the correct operation to set it. // * if storing to a boolean from non-boolean first normalize the result to a 0 or 1 // * for text, emit cql_set_string_ref to do the job // * for nullables use cg_set_nullable (see above) to do the job // * for not-nullables x = y is all you need. static void cg_store(charbuf *output, CSTR var, sem_t sem_type_var, sem_t sem_type_expr, CSTR is_null, CSTR value) { CHARBUF_OPEN(adjusted_value); CG_BEGIN_ADJUST_FOR_OUTARG(var, sem_type_var); // Most types convert correctly with no help, the C compiler will convert. This is not true for bool. if (is_bool(sem_type_var) && !is_bool(sem_type_expr)) { // exclude some things that are already normalized if (strcmp("0", value) && strcmp("1", value) && value[0] != '!') { bprintf(&adjusted_value, "!!(%s)", value); value = adjusted_value.ptr; } } bool_t handled = 0; // Check to see if we are trying to store a variable back on itself. // This happens when is_assignment_target_reusable let us use the target // of the assignment as the scratch variable. When it comes time to // do the assignment we find there is now nothing to do. if (!is_nullable(sem_type_var) || is_ref_type(sem_type_var)) { // dead store -- source = target handled = !strcmp(var, value); } else { // In the nullable case the comparison is a bit trickier, we have to be // storing from var.value and var.is_null right back into var.value // and var.isnull. CHARBUF_OPEN(val); CHARBUF_OPEN(nul); bprintf(&val, "%s.value", var); bprintf(&nul, "%s.is_null", var); handled = !strcmp(val.ptr, value) && !strcmp(nul.ptr, is_null); CHARBUF_CLOSE(nul); CHARBUF_CLOSE(val); } if (handled) { // nothing left to do, assignment already happened } else if (is_ref_type(sem_type_var) || !is_nullable(sem_type_var)) { cg_copy(output, var, sem_type_var, value); } else { cg_set_nullable(output, var, is_null, value); } CG_END_ADJUST_FOR_OUTARG(); CHARBUF_CLOSE(adjusted_value); } // This is a simple helper for store where we know that the type of the thing being stored // is exactly the same as the type of the thing we are storing. This is used when we // just made a temporary of exactly the correct type to hold an expression. cg_store // handles this all but this helper lets you specify only one type. static void cg_store_same_type(charbuf *output, CSTR var, sem_t sem_type, CSTR is_null, CSTR value) { cg_store(output, var, sem_type, sem_type, is_null, value); } // This is the general helper for some kind of comparison. In fact comparison for // the numeric types is exactly like all the other binary operators so really this is // here only to special case the code gen for text comparison. The passed in "op" is // the operator between the left and right, so "<=", "<", etc. String comparisons // use the cql_string_compare helper to do the compare and the comparison is changed to be // relative to zero. So x <= y turns into cql_string_compare(x, y) <= 0. If the arguments // are not nullable, the comparison expression goes directly into value. If the // the result is nullable, it goes into a scratch var using the "combine" rules, see above. static void cg_binary_compare(ast_node *ast, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { sem_t sem_type_result = ast->sem->sem_type; sem_t sem_type_left = ast->left->sem->sem_type; sem_t sem_type_right = ast->right->sem->sem_type; bool_t is_text_op = is_text(sem_type_left) && is_text(sem_type_right); bool_t is_blob_op = is_blob(sem_type_left) && is_blob(sem_type_right); if (is_blob_op) { // we want to make sure blob is only supported for certain operation // even though we already have the semantic analysis for this. Invariant(is_ast_eq(ast) || is_ast_ne(ast)); } if (!is_text_op && !is_blob_op) { // for numeric, the usual binary processing works cg_binary(ast, op, is_null, value, pri, pri_new); return; } // Both sides are text/blob or null; already verified compatible in semantic phase. CHARBUF_OPEN(comparison); if (needs_paren(ast, pri_new, pri)) { bprintf(&comparison, "("); } ast_node *l = ast->left; ast_node *r = ast->right; CG_RESERVE_RESULT_VAR(ast, sem_type_result); CG_PUSH_EVAL(l, pri_new); CG_PUSH_EVAL(r, pri_new); if (is_ast_like(ast)) { // like not allowed semantically for blob type Invariant(!is_blob_op); bprintf(&comparison, "%s(%s, %s) == 0", rt->cql_string_like, l_value.ptr, r_value.ptr); } else if (is_ast_not_like(ast)) { // like not allowed semantically for blob type Invariant(!is_blob_op); bprintf(&comparison, "%s(%s, %s) != 0", rt->cql_string_like, l_value.ptr, r_value.ptr); } else if (is_blob_op) { bool_t logical_not = is_ast_ne(ast) || is_ast_is_not(ast); if (logical_not) { bprintf(&comparison, "%s", "!"); } bprintf(&comparison, "%s(%s, %s)", rt->cql_blob_equal, l_value.ptr, r_value.ptr); } else { // otherwise other string comparisons bprintf(&comparison, "%s(%s, %s) %s 0", rt->cql_string_compare, l_value.ptr, r_value.ptr, op); } if (needs_paren(ast, pri_new, pri)) { bprintf(&comparison, ")"); } if (is_not_nullable(sem_type_left) && is_not_nullable(sem_type_right)) { bprintf(value, "%s", comparison.ptr); bprintf(is_null, "0"); } else { CG_USE_RESULT_VAR(); cg_combine_nullables(cg_main_output, result_var.ptr, l_is_null.ptr, r_is_null.ptr, comparison.ptr); } CG_POP_EVAL(r); CG_POP_EVAL(l); CG_CLEANUP_RESULT_VAR(); CHARBUF_CLOSE(comparison); } // Other than string comparison, all the normal (no short-circuit) binary operators // can be handled the same way. // * op is the operator text // * is_null and value are the usual outputs // * pri is the strength of the caller // * pri_new is the strength of "op" // The helper needs_paren() tells us if we should wrap this subtree in parens (see above) // If the inputs are not nullable then we can make the easy case of returning the // result in the value string (and 0 for is null). Otherwise, cg_combine_nullables // does the job. static void cg_binary(ast_node *ast, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { // left op right ast_node *l = ast->left; ast_node *r = ast->right; sem_t sem_type_result = ast->sem->sem_type; if (sem_type_result == SEM_TYPE_NULL) { bprintf(value, "0"); bprintf(is_null, "1"); return; } sem_t sem_type_left = l->sem->sem_type; sem_t sem_type_right = r->sem->sem_type; // this hold the formula for the answer CHARBUF_OPEN(result); CG_RESERVE_RESULT_VAR(ast, sem_type_result); CG_PUSH_EVAL(l, pri_new); CG_PUSH_EVAL(r, pri_new); bprintf(&result, "%s %s %s", l_value.ptr, op, r_value.ptr); if (is_not_nullable(sem_type_left) && is_not_nullable(sem_type_right)) { if (needs_paren(ast, pri_new, pri)) { bprintf(value, "(%s)", result.ptr); } else { bprintf(value, "%s", result.ptr); } bprintf(is_null, "0"); } else { // put result into result_var CG_USE_RESULT_VAR(); cg_combine_nullables(cg_main_output, result_var.ptr, l_is_null.ptr, r_is_null.ptr, result.ptr); } CG_POP_EVAL(r); CG_POP_EVAL(l); CG_CLEANUP_RESULT_VAR(); CHARBUF_CLOSE(result); } // code gen for expr IS FALSE // operands already known to be of the correct type so all we have to do is // check for nullable or not nullable and generate the appropriate code using // either the helper or just looking at the value static void cg_expr_is_false(ast_node *ast, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { Contract(is_ast_is_false(ast)); EXTRACT_ANY_NOTNULL(expr, ast->left); sem_t sem_type_is_expr = expr->sem->sem_type; // expr IS FALSE bprintf(is_null, "0"); // the result of is false is never null // we always put parens because ! is the highest binding, so we can use ROOT, the callee never needs parens CG_PUSH_EVAL(expr, C_EXPR_PRI_ROOT); if (is_nullable(sem_type_is_expr)) { bprintf(value, "cql_is_nullable_false(%s, %s)", expr_is_null.ptr, expr_value.ptr); } else { bprintf(value, "!(%s)", expr_value.ptr); } CG_POP_EVAL(expr); } // code gen for expr IS NOT FALSE // operands already known to be of the correct type so all we have to do is // check for nullable or not nullable and generate the appropriate code using // either the helper or just looking at the value static void cg_expr_is_not_false(ast_node *ast, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { Contract(is_ast_is_not_false(ast)); EXTRACT_ANY_NOTNULL(expr, ast->left); sem_t sem_type_is_expr = expr->sem->sem_type; // expr IS NOT FALSE bprintf(is_null, "0"); // the result of is false is never null // we always put parens because ! is the highest binding, so we can use ROOT, the callee never needs parens CG_PUSH_EVAL(expr, C_EXPR_PRI_ROOT); if (is_nullable(sem_type_is_expr)) { bprintf(value, "!cql_is_nullable_false(%s, %s)", expr_is_null.ptr, expr_value.ptr); } else { bprintf(value, "!!(%s)", expr_value.ptr); } CG_POP_EVAL(expr); } // code gen for expr IS TRUE // operands already known to be of the correct type so all we have to do is // check for nullable or not nullable and generate the appropriate code using // either the helper or just looking at the value static void cg_expr_is_true(ast_node *ast, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { Contract(is_ast_is_true(ast)); EXTRACT_ANY_NOTNULL(expr, ast->left); sem_t sem_type_is_expr = expr->sem->sem_type; // expr IS TRUE bprintf(is_null, "0"); // the result of is true is never null // we always put parens because ! is the highest binding, so we can use ROOT, the callee never needs parens CG_PUSH_EVAL(expr, C_EXPR_PRI_ROOT); if (is_nullable(sem_type_is_expr)) { bprintf(value, "cql_is_nullable_true(%s, %s)", expr_is_null.ptr, expr_value.ptr); } else { bprintf(value, "!!(%s)", expr_value.ptr); } CG_POP_EVAL(expr); } // code gen for expr IS NOT TRUE // operands already known to be of the correct type so all we have to do is // check for nullable or not nullable and generate the appropriate code using // either the helper or just looking at the value static void cg_expr_is_not_true(ast_node *ast, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { Contract(is_ast_is_not_true(ast)); EXTRACT_ANY_NOTNULL(expr, ast->left); sem_t sem_type_is_expr = expr->sem->sem_type; // expr IS NOT TRUE bprintf(is_null, "0"); // the result of is not true is never null // we always put parens because ! is the highest binding, so we can use ROOT, the callee never needs parens CG_PUSH_EVAL(expr, C_EXPR_PRI_ROOT); if (is_nullable(sem_type_is_expr)) { bprintf(value, "!cql_is_nullable_true(%s, %s)", expr_is_null.ptr, expr_value.ptr); } else { bprintf(value, "!(%s)", expr_value.ptr); } CG_POP_EVAL(expr); } // The code-gen for is_null is one of the easiest. The recursive call // produces is_null as one of the outputs. Use that. Our is_null result // is always zero because IS NULL is never, itself, null. static void cg_expr_is_null(ast_node *expr, charbuf *is_null, charbuf *value) { sem_t sem_type_expr = expr->sem->sem_type; // expr IS NULL bprintf(is_null, "0"); // the result of is null is never null // The fact that this is not constant not null for not null reference types reflects // the weird state of affairs with uninitialized reference variables which // must be null even if they are typed not null. if (is_not_nullable(sem_type_expr) && !is_ref_type(sem_type_expr)) { // Note, sql has no side-effects so we can fold this away. bprintf(value, "0"); } else { CG_PUSH_EVAL(expr, C_EXPR_PRI_ROOT); bprintf(value, "%s", expr_is_null.ptr); CG_POP_EVAL(expr); } } // The code-gen for is_not_null is one of the easiest. The recursive call // produces is_null as one of the outputs. Invert that. Our is_null result // is always zero because IS NOT NULL is never, itself, null. static void cg_expr_is_not_null(ast_node *expr, charbuf *is_null, charbuf *value) { sem_t sem_type_expr = expr->sem->sem_type; // expr IS NOT NULL bprintf(is_null, "0"); // the result of is not null is never null // The fact that this is not constant not null for not null reference types reflects // the weird state of affairs with uninitialized reference variables which // must be null even if they are typed not null. if (is_not_nullable(sem_type_expr) && !is_ref_type(sem_type_expr)) { // Note, sql has no side-effects so we can fold this away. bprintf(value, "1"); } else { CG_PUSH_EVAL(expr, C_EXPR_PRI_ROOT); bprintf(value, "!%s", expr_is_null.ptr); CG_POP_EVAL(expr); } } // This is the general IS pattern, there are several case: // * if the right hand side is NULL then use the special helper for IS NULL // * that helper makes better code for this special case (the 99% case) // * if the args are text, use the text comparor runtime helper // * if the args are non-nullable or reference types (not text) equality works // * otherwise use the messy formula // * both are either null or both not null AND // * either they are null or their values are equal static void cg_expr_is(ast_node *ast, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { Contract(is_ast_is(ast)); EXTRACT_ANY_NOTNULL(l, ast->left); EXTRACT_ANY_NOTNULL(r, ast->right); // left IS right if (is_ast_null(l)) { cg_expr_is_null(r, is_null, value); return; } else if (is_ast_null(r)) { cg_expr_is_null(l, is_null, value); return; } sem_t sem_type_left = l->sem->sem_type; sem_t sem_type_right = r->sem->sem_type; // the result of IS, will not be null, no cases. bprintf(is_null, "0"); bool_t is_text_op = is_text(sem_type_left) || is_text(sem_type_right); bool_t is_blob_op = is_blob(sem_type_left) || is_blob(sem_type_right); if (is_text_op || is_blob_op) { // Both sides are text already verified compatible in semantic phase. CG_PUSH_EVAL(l, pri_new); CG_PUSH_EVAL(r, pri_new); CSTR equal_func = is_text_op ? rt->cql_string_equal : rt->cql_blob_equal; bprintf(value, "%s(%s, %s)", equal_func, l_value.ptr, r_value.ptr, op); CG_POP_EVAL(r); CG_POP_EVAL(l); return; } CG_PUSH_EVAL(l, pri_new); CG_PUSH_EVAL(r, pri_new); bool_t refs = is_ref_type(sem_type_left) || is_ref_type(sem_type_right); bool_t notnull = is_not_nullable(sem_type_left) && is_not_nullable(sem_type_right); if (notnull || refs) { if (needs_paren(ast, pri_new, pri)) { bprintf(value, "(%s == %s)", l_value.ptr, r_value.ptr); } else { bprintf(value, "%s == %s", l_value.ptr, r_value.ptr); } } else { bprintf(value, "((%s == %s) && (%s || %s == %s))", l_is_null.ptr, r_is_null.ptr, r_is_null.ptr, l_value.ptr, r_value.ptr); } CG_POP_EVAL(r); CG_POP_EVAL(l); } // This is the general IS NOT pattern, there are several case: // * if the right hand side is NULL then use the special helper for IS NOT NULL // * that helper makes better code for this special case (the 99% case) // * if the args are text, use the text comparor runtime helper (with !) // * if the args are non-nullable or reference types (not text) inequality works // * otherwise use the messy IS formula (qv), but invert the result static void cg_expr_is_not(ast_node *ast, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { Contract(is_ast_is_not(ast)); EXTRACT_ANY_NOTNULL(l, ast->left); EXTRACT_ANY_NOTNULL(r, ast->right); // left IS NOT right if (is_ast_null(r)) { cg_expr_is_not_null(l, is_null, value); return; } else if (is_ast_null(l)) { cg_expr_is_not_null(r, is_null, value); return; } sem_t sem_type_left = l->sem->sem_type; sem_t sem_type_right = r->sem->sem_type; // the resut of IS NOT, will not be null, no cases. bprintf(is_null, "0"); bool_t is_text_exp = is_text(sem_type_left) || is_text(sem_type_right); bool_t is_blob_exp = is_blob(sem_type_left) || is_blob(sem_type_right); if (is_text_exp || is_blob_exp) { // Both sides are text already verified compatible in semantic phase. CG_PUSH_EVAL(l, pri_new); CG_PUSH_EVAL(r, pri_new); CSTR equal_func = is_text_exp ? rt->cql_string_equal : rt->cql_blob_equal; bprintf(value, "!%s(%s, %s)", equal_func, l_value.ptr, r_value.ptr, op); CG_POP_EVAL(r); CG_POP_EVAL(l); return; } CG_PUSH_EVAL(l, pri_new); CG_PUSH_EVAL(r, pri_new); bprintf(is_null, "0"); bool_t refs = is_ref_type(sem_type_left) || is_ref_type(sem_type_right); bool_t notnull = is_not_nullable(sem_type_left) && is_not_nullable(sem_type_right); if (notnull || refs) { if (needs_paren(ast, pri_new, pri)) { bprintf(value, "(%s != %s)", l_value.ptr, r_value.ptr); } else { bprintf(value, "%s != %s", l_value.ptr, r_value.ptr); } } else { bprintf(value, "!((%s == %s) && (%s || %s == %s))", l_is_null.ptr, r_is_null.ptr, r_is_null.ptr, l_value.ptr, r_value.ptr); } CG_POP_EVAL(r); CG_POP_EVAL(l); } // Helper to emit an "if false" condition // There are lots of cases where the object is not nullable and we'd like nicer code // for those cases, hence this helper. static void cg_if_false(charbuf *output, CSTR is_null, CSTR value) { if (!strcmp(is_null, "0")) { bprintf(output, "if (!(%s)) {\n", value); } else if (!strcmp(is_null, "1")) { // null is not false bprintf(output, "if (0) {\n", value); } else { bprintf(output, "if (cql_is_nullable_false(%s, %s)) {\n", is_null, value); } } // Helper to emit an "if true" condition // There are lots of cases where the object is not nullable and we'd like nicer code // for those cases, hence this helper. static void cg_if_true(charbuf *output, CSTR is_null, CSTR value) { if (!strcmp(is_null, "0")) { bprintf(output, "if (%s) {\n", value); } else if (!strcmp(is_null, "1")) { // null is not true bprintf(output, "if (0) {\n", value); } else { bprintf(output, "if (cql_is_nullable_true(%s, %s)) {\n", is_null, value); } } // The logical operations are fairly tricky, the code generators for // each of them are very similar. Basically x OR y has to be this: // * if x is true the answer is true and don't evaluate y (null is not true) // * if x is not true and y is true the answer is true (and both were evaluated) // * if neither is true and either is null the answer is null // * if both are false (only) the answer is false. See the truth table. // To get this code generation we need some if statements... This is another // of the cases where an expression-looking thing actually has statements in the C. // There is easy case code if both are known to to be nullable, where the result // is directly computed with ||. static void cg_expr_or(ast_node *ast, CSTR str, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { Contract(is_ast_or(ast)); Contract(pri_new == C_EXPR_PRI_LOR); EXTRACT_ANY_NOTNULL(l, ast->left); EXTRACT_ANY_NOTNULL(r, ast->right); // [left] OR [right] // Logical OR truth table and short circuit rules: // // left right result evalaute //----- ----- ------ -------- // null null null both // null 0 null both // null 1 1 both // 0 null null both // 0 0 0 both // 0 1 1 both // 1 null 1 left only // 1 0 1 left only // 1 1 1 left only sem_t sem_type_result = ast->sem->sem_type; sem_t sem_type_right = r->sem->sem_type; CG_RESERVE_RESULT_VAR(ast, sem_type_result); CG_PUSH_EVAL(l, pri_new); CHARBUF_OPEN(right_eval); charbuf *saved_main = cg_main_output; cg_main_output = &right_eval; CG_PUSH_EVAL(r, pri_new); cg_main_output = saved_main; // Easiest case of all, we can use the logical || operator // we can only do this if everything is non-null and no-statement generation is needed for the right expression // it's ok if statement generation is needed for the left because that never needs to short circuit (left // is always evaluated). if (!is_nullable(sem_type_result) && right_eval.used == 1) { if (needs_paren(ast, pri_new, pri)) { bprintf(value, "("); } bprintf(is_null, "0"); bprintf(value, "%s || %s", l_value.ptr, r_value.ptr); if (needs_paren(ast, pri_new, pri)) { bprintf(value, ")"); } } else { // Possibly nullable result... CG_USE_RESULT_VAR(); // More special cases, both null is just null. if (is_ast_null(l) && is_ast_null(r)) { bprintf(cg_main_output, "cql_set_null(%s);\n", result_var.ptr); } else { cg_if_true(cg_main_output, l_is_null.ptr, l_value.ptr); // if (left...) { // if left is true the result is true and don't evaluate the right bprintf(cg_main_output, " "); cg_store_same_type(cg_main_output, result_var.ptr, sem_type_result, "0", "1"); bprintf(cg_main_output, "}\n"); bprintf(cg_main_output, "else {\n"); // Left is not true, it's null or false. We need the right. // We already stored the statements right needs (if any). Spit those out now. CG_PUSH_MAIN_INDENT(r, 2); bprintf(cg_main_output, "%s", right_eval.ptr); if (!is_nullable(sem_type_result)) { // If the result is not null then neither of the inputs are null // In this branch the left was not true, so it must have been false. // Therefore the result is whatever is on the right. And it's not null. cg_store(cg_main_output, result_var.ptr, sem_type_result, sem_type_right, "0", r_value.ptr); } else { // One was nullable so we have to do the nullable logic cg_if_true(cg_main_output, r_is_null.ptr, r_value.ptr); // if (right..) { bprintf(cg_main_output, " "); // The right was true, the result is therefore true. cg_store_same_type(cg_main_output, result_var.ptr, sem_type_result, "0", "1"); bprintf(cg_main_output, "}\n"); bprintf(cg_main_output, "else {\n "); // Neither was true, so the result is null or false. Null if either are null. cg_combine_nullables(cg_main_output, result_var.ptr, l_is_null.ptr, r_is_null.ptr, "0"); bprintf(cg_main_output, "}\n"); } CG_POP_MAIN_INDENT(r); bprintf(cg_main_output, "}\n"); } } CG_POP_EVAL(r); CHARBUF_CLOSE(right_eval); CG_POP_EVAL(l); CG_CLEANUP_RESULT_VAR(); } // The logical operations are fairly tricky, the code generators for // each of them are very similar. Basically x AND y has to be this: // * if x is false the answer is false and don't evaluate y (null is not false) // * if x is not false and y is false the answer is false (and both were evaluated) // * if neither is false and either is null the answer is null // * if both are true (only) the answer is true. See the truth table. // To get this code generation we need some if statements... This is another // of the cases where an expression-looking thing actually has statements in the C. // There is easy case code if both are known to to be nullable, where the result // is directly computed with &&. static void cg_expr_and(ast_node *ast, CSTR str, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { Contract(is_ast_and(ast)); Contract(pri_new == C_EXPR_PRI_LAND); EXTRACT_ANY_NOTNULL(l, ast->left); EXTRACT_ANY_NOTNULL(r, ast->right); // [left] AND [right] // Logical AND truth table and short circuit rules: // // left right result evalaute //----- ----- ------ -------- // null null null both // null 0 0 both // null 1 null both // 0 null 0 left only // 0 0 0 left only // 0 1 0 left only // 1 null null both // 1 0 0 both // 1 1 1 both sem_t sem_type_result = ast->sem->sem_type; sem_t sem_type_right = r->sem->sem_type; CG_RESERVE_RESULT_VAR(ast, sem_type_result); CG_PUSH_EVAL(l, pri_new); CHARBUF_OPEN(right_eval); charbuf *saved_main = cg_main_output; cg_main_output = &right_eval; CG_PUSH_EVAL(r, pri_new); cg_main_output = saved_main; // Easiest case of all, we can use the logical && operator // we can only do this if everything is non-null and no-statement generation is needed for the right expression // it's ok if statement generation is needed for the left because that never needs to short circuit (left // is always evaluated). if (!is_nullable(sem_type_result) && right_eval.used == 1) { if (needs_paren(ast, pri_new, pri)) { bprintf(value, "("); } bprintf(is_null, "0"); bprintf(value, "%s && %s", l_value.ptr, r_value.ptr); if (needs_paren(ast, pri_new, pri)) { bprintf(value, ")"); } } else { // We're doing the longish form, so there is a result variable for the answer. CG_USE_RESULT_VAR(); // More special cases, both null is just null. if (is_ast_null(l) && is_ast_null(r)) { bprintf(cg_main_output, "cql_set_null(%s);\n", result_var.ptr); } else { cg_if_false(cg_main_output, l_is_null.ptr, l_value.ptr); // if (!left...) { bprintf(cg_main_output, " "); cg_store_same_type(cg_main_output, result_var.ptr, sem_type_result, "0", "0"); bprintf(cg_main_output, "}\n"); bprintf(cg_main_output, "else {\n"); // Left is not false, it's null or true. We need the right. // We already stored the statements right needs (if any). Spit those out now. CG_PUSH_MAIN_INDENT(r, 2); bprintf(cg_main_output, "%s", right_eval.ptr); if (!is_nullable(sem_type_result)) { // If the result is not null then neither of the inputs are null // In this branch the left was not false, so it must have been true. // Therefore the result is whatever is on the right. And it's not null. cg_store(cg_main_output, result_var.ptr, sem_type_result, sem_type_right, "0", r_value.ptr); } else { // One was nullable so we have to do the nullable logic cg_if_false(cg_main_output, r_is_null.ptr, r_value.ptr); // if (!right..) { bprintf(cg_main_output, " "); // The right is false, the result is therefore false. cg_store_same_type(cg_main_output, result_var.ptr, sem_type_result, "0", "0"); bprintf(cg_main_output, "}\n"); bprintf(cg_main_output, "else {\n "); // Neither was false, so the result is null or true. Null if either are null. cg_combine_nullables(cg_main_output, result_var.ptr, l_is_null.ptr, r_is_null.ptr, "1"); bprintf(cg_main_output, "}\n"); } CG_POP_MAIN_INDENT(r); bprintf(cg_main_output, "}\n"); } } CG_POP_EVAL(r); CHARBUF_CLOSE(right_eval); CG_POP_EVAL(l); CG_CLEANUP_RESULT_VAR(); } // The unary operators are handled just like the binary operators. All of the // C outputs have the form (op arg). We just have to decide if we need parens. // We use the same rules for parens here as in other places. "pri" tells us // the context of the caller, if it is stronger than our operator then we need parens. // As usual, there is the easy case for not-nullables and the "use a temporary" case // for nullables. static void cg_unary(ast_node *ast, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { // op [left] EXTRACT_ANY_NOTNULL(expr, ast->left); sem_t sem_type_result = ast->sem->sem_type; sem_t sem_type_expr = expr->sem->sem_type; if (!strcmp(op, "-") && is_ast_num(expr)) { // we have to do special code gen for -9223372036854775808 // to avoid compiler warnings... This is how the literal // gets handled in limits.h as well... EXTRACT_NUM_TYPE(num_type, expr); EXTRACT_NUM_VALUE(lit, expr); if (num_type == NUM_LONG && !strcmp("9223372036854775808", lit)) { // add long suffix if needed bprintf(value, "(_64(9223372036854775807)-1)"); bprintf(is_null, "0"); return; } } CHARBUF_OPEN(result); CG_RESERVE_RESULT_VAR(ast, sem_type_result); CG_PUSH_EVAL(expr, pri_new) if (needs_paren(ast, pri_new, pri)) { bprintf(&result, "(%s%s)", op, expr_value.ptr); } else { // We always add a space to avoid creating "--" or "++" // expr_value might be -1 or -x or some such. This way we're // always safe at the cost of a space. bprintf(&result, "%s %s", op, expr_value.ptr); } if (is_not_nullable(sem_type_expr)) { bprintf(is_null, "0"); bprintf(value, "%s", result.ptr); } else { CG_USE_RESULT_VAR(); cg_set_nullable(cg_main_output, result_var.ptr, expr_is_null.ptr, result.ptr); } CG_POP_EVAL(expr); CG_CLEANUP_RESULT_VAR(); CHARBUF_CLOSE(result); } static void cg_func_sign(ast_node *call_ast, charbuf *is_null, charbuf *value) { Contract(is_ast_call(call_ast)); EXTRACT_ANY_NOTNULL(name_ast, call_ast->left); EXTRACT_STRING(name, name_ast); EXTRACT_NOTNULL(call_arg_list, call_ast->right); EXTRACT(arg_list, call_arg_list->right); EXTRACT_ANY_NOTNULL(expr, arg_list->left); sem_t sem_type_result = call_ast->sem->sem_type; sem_t sem_type_expr = expr->sem->sem_type; CHARBUF_OPEN(sign_value); CG_SETUP_RESULT_VAR(call_ast, sem_type_result); CG_PUSH_EVAL(expr, C_EXPR_PRI_ROOT); CG_PUSH_TEMP(temp, sem_type_expr); cg_store_same_type(cg_main_output, temp.ptr, sem_type_result, expr_is_null.ptr, expr_value.ptr); bprintf(&sign_value, "((%s > 0) - (%s < 0))", temp_value.ptr, temp_value.ptr); cg_store_same_type(cg_main_output, result_var.ptr, sem_type_result, temp_is_null.ptr, sign_value.ptr); CG_POP_TEMP(temp); CG_POP_EVAL(expr); CG_CLEANUP_RESULT_VAR(); CHARBUF_CLOSE(sign_value); } // To do `abs` we have to evaluate the argument and store it somewhere // we use the result variable for this. We don't want to evaluate that // expression more than once, hence the temporary storage. Once we have // the value in a result variable, we can test it against zero safely // and alter it as needed. static void cg_func_abs(ast_node *call_ast, charbuf *is_null, charbuf *value) { Contract(is_ast_call(call_ast)); EXTRACT_ANY_NOTNULL(name_ast, call_ast->left); EXTRACT_STRING(name, name_ast); EXTRACT_NOTNULL(call_arg_list, call_ast->right); EXTRACT(arg_list, call_arg_list->right); EXTRACT_ANY_NOTNULL(expr, arg_list->left); // first arg // abs ( expr ) sem_t sem_type_result = call_ast->sem->sem_type; sem_t core_type_result = core_type_of(sem_type_result); if (core_type_result == SEM_TYPE_NULL) { bprintf(value, "0"); bprintf(is_null, "1"); return; } CHARBUF_OPEN(abs_value); CG_SETUP_RESULT_VAR(call_ast, sem_type_result); // Evaluate the expression and stow it in a temporary. CG_PUSH_EVAL(expr, C_EXPR_PRI_ROOT); CG_PUSH_TEMP(temp, sem_type_result); // Copy the expression, we can't evaluate it more than once, so stow it. cg_store_same_type(cg_main_output, temp.ptr, sem_type_result, expr_is_null.ptr, expr_value.ptr); switch (core_type_result) { case SEM_TYPE_INTEGER: bprintf(&abs_value, "abs(%s)", temp_value.ptr); break; case SEM_TYPE_LONG_INTEGER: bprintf(&abs_value, "labs(%s)", temp_value.ptr); break; case SEM_TYPE_REAL: bprintf(&abs_value, "fabs(%s)", temp_value.ptr); break; case SEM_TYPE_BOOL: bprintf(&abs_value, "!!%s", temp_value.ptr); break; } cg_store_same_type(cg_main_output, result_var.ptr, sem_type_result, temp_is_null.ptr, abs_value.ptr); CG_POP_TEMP(temp); CG_POP_EVAL(expr); CG_CLEANUP_RESULT_VAR(); CHARBUF_CLOSE(abs_value); } // This helper generates the tests for each entry in the IN list. // we generate the appropriate equality test -- one for strings // one for nullables and one for not nullables. Note expr is already known // to be not null here. There was previous codegen for that case. The result // is either bool or nullable bool. static void cg_in_or_not_in_expr_list(ast_node *head, CSTR expr, CSTR result, sem_t sem_type_result, bool_t is_not_in) { Contract(is_bool(sem_type_result)); CSTR found_value = is_not_in ? "0" : "1"; CSTR not_found_value = is_not_in ? "1" : "0"; cg_store_same_type(cg_main_output, result, sem_type_result, "0", found_value); for (ast_node *ast = head; ast; ast = ast->right) { EXTRACT_ANY_NOTNULL(in_expr, ast->left) // null can't ever match anything, waste of time. if (is_ast_null(in_expr)) { continue; } cg_line_directive_min(in_expr, cg_main_output); int32_t stack_level_saved = stack_level; CG_PUSH_EVAL(in_expr, C_EXPR_PRI_EQ_NE); sem_t sem_type_in_expr = in_expr->sem->sem_type; if (is_text(sem_type_in_expr)) { bprintf(cg_main_output, "if (%s(%s, %s) == 0)", rt->cql_string_compare, expr, in_expr_value.ptr); } else if (is_blob(sem_type_in_expr)) { bprintf(cg_main_output, "if (%s(%s, %s))", rt->cql_blob_equal, expr, in_expr_value.ptr); } else if (is_nullable(sem_type_in_expr)) { bprintf(cg_main_output, "if (cql_is_nullable_true(%s, %s == %s))", in_expr_is_null.ptr, expr, in_expr_value.ptr); } else { bprintf(cg_main_output, "if (%s == %s)", expr, in_expr_value.ptr); } bprintf(cg_main_output, " break;\n"); CG_POP_EVAL(in_expr); // This comparison clause fully used any temporaries associated with expr // this is kind of like the result variable case, except we didn't store the result // we used it in the "if" test, but we're done with it. stack_level = stack_level_saved; } cg_store_same_type(cg_main_output, result, sem_type_result, "0", not_found_value); } static void cg_null_result(ast_node *ast, charbuf *is_null, charbuf *value) { sem_t sem_type_result = ast->sem->sem_type; CG_SETUP_RESULT_VAR(ast, sem_type_result); cg_set_null(cg_main_output, result_var.ptr, sem_type_result); CG_CLEANUP_RESULT_VAR(); } // The [NOT] IN structure is the simplest of the multi-test forms. // It's actually a special case of case/when if you like. // Each item in the [NOT] IN needs to be evaluated because there is no rule // that says they are constants. // NOT IN is just a similar reversed check compare IN starting with opposite result value. // The general pattern for X IN (U, V) looks like this // // int result; // do { // prep statements for X; // temp = X; // if (temp is null) { result = null; break; } [only needed if X is nullable] // // result = 1; /* cg_in_or_not_in_expr_list generates the alternatives */ // (result = 0; if NOT IN case) // // prep statements for U; // compute U; // if (temp == U) break; // // prep statements for V; // compute V; // if (temp == V) break; // // result = 0; // (result = 1; if NOT IN case) // } while (0); // // The result ends up in the is_null and value fields as usual. static void cg_expr_in_pred_or_not_in( ast_node *ast, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { Contract(is_ast_in_pred(ast) || is_ast_not_in(ast)); EXTRACT_ANY_NOTNULL(expr, ast->left) EXTRACT_NOTNULL(expr_list, ast->right); // [expr] [NOT] IN ( [expr_list] ) sem_t sem_type_result = ast->sem->sem_type; sem_t sem_type_expr = expr->sem->sem_type; if (is_null_type(sem_type_expr)) { cg_null_result(ast, is_null, value); return; } // The answer will be stored in this scratch variable. // note: we do not allow the assignment variable to be used because it might be // in the candidate list. Since we write to it before we're done the early // "result = 1" would kill something like r := x in (r, b); CG_SETUP_RESULT_VAR(NULL, sem_type_result); bprintf(cg_main_output, "do {\n"); CG_PUSH_MAIN_INDENT(do, 2); cg_line_directive_min(expr, cg_main_output); // Evaluate the expression and stow it in a temporary. CG_PUSH_EVAL(expr, C_EXPR_PRI_ROOT); CG_PUSH_TEMP(temp, sem_type_expr); // Copy the expression, we can't evaluate it more than once, so stow it. cg_store_same_type(cg_main_output, temp.ptr, sem_type_expr, expr_is_null.ptr, expr_value.ptr); // If the expression is null the result is null if (is_nullable(sem_type_expr)) { bprintf(cg_main_output, "if (%s) { \n", temp_is_null.ptr); bprintf(cg_main_output, " "); cg_set_null(cg_main_output, result_var.ptr, sem_type_result); bprintf(cg_main_output, " break;\n"); bprintf(cg_main_output, "}\n"); } // Now generate the list cg_in_or_not_in_expr_list(expr_list, temp_value.ptr, result_var.ptr, sem_type_result, is_ast_not_in(ast)); CG_POP_TEMP(temp); CG_POP_EVAL(expr); CG_POP_MAIN_INDENT(do); CG_CLEANUP_RESULT_VAR(); cg_line_directive_max(ast, cg_main_output); bprintf(cg_main_output, "} while (0);\n"); } // This helper method emits the alternatives for the case. If there was an // expression the temporary holding the expression is in expr. Expr has // already been tested for null if that was a possibility so we only need its // value at this point. static void cg_case_list(ast_node *head, CSTR expr, CSTR result, sem_t sem_type_result) { Contract(is_ast_case_list(head)); for (ast_node *ast = head; ast; ast = ast->right) { EXTRACT_NOTNULL(when, ast->left); EXTRACT_ANY_NOTNULL(case_expr, when->left); EXTRACT_ANY_NOTNULL(then_expr, when->right); // null can't ever match anything, waste of time. if (is_ast_null(case_expr)) { continue; } // WHEN [case_expr] THEN [then_expr] sem_t sem_type_case_expr = case_expr->sem->sem_type; sem_t sem_type_then_expr = then_expr->sem->sem_type; cg_line_directive_min(case_expr, cg_main_output); int32_t stack_level_saved = stack_level; CG_PUSH_EVAL(case_expr, C_EXPR_PRI_EQ_NE); if (expr) { // Generate a comparison for the appropriate data type (expr known to be not null) if (is_text(sem_type_case_expr)) { bprintf(cg_main_output, "if (%s(%s, %s) == 0) {\n", rt->cql_string_compare, expr, case_expr_value.ptr); } else if (is_nullable(sem_type_case_expr)) { bprintf(cg_main_output, "if (cql_is_nullable_true(%s, %s == %s)) {\n", case_expr_is_null.ptr, expr, case_expr_value.ptr); } else { bprintf(cg_main_output, "if (%s == %s) {\n", expr, case_expr_value.ptr); } } else { // No temporary, generate a test for a boolean expression (which may or may not be null) if (is_nullable(sem_type_case_expr)) { bprintf(cg_main_output, "if (cql_is_nullable_true(%s, %s)) {\n", case_expr_is_null.ptr, case_expr_value.ptr); } else { bprintf(cg_main_output, "if (%s) {\n", case_expr_value.ptr); } } cg_line_directive_min(then_expr, cg_main_output); CG_POP_EVAL(case_expr); // The comparison above clause fully used any temporaries associated with expr stack_level = stack_level_saved; CG_PUSH_MAIN_INDENT(then, 2); CG_PUSH_EVAL(then_expr, C_EXPR_PRI_ROOT); cg_store(cg_main_output, result, sem_type_result, sem_type_then_expr, then_expr_is_null.ptr, then_expr_value.ptr); bprintf(cg_main_output, "break;\n"); CG_POP_EVAL(then_expr); CG_POP_MAIN_INDENT(then); bprintf(cg_main_output, "}\n"); // This 'then' clause stored its result, temporaries no longer needed // This is just like the result variable case stack_level = stack_level_saved; } } // Case looks a lot like IN except the net result is computed at each step // and the test is different at each step. It's a straight generalization. // // Case X when U then R1 when V then R2 else R3 end; // // declare result (whatever type holds R1, R2, and R3) // // do { // statements to evaluate X; // temp = X; // [ if temp is null goto case_else; ] optional if temp is nullable // // statements to evaluate U // if (temp == U) { // statements to evaluate R1; // result = R1; // break; // } // // statements to evaluate V // if (temp == V) { // statements to evaluate R2; // result = R2; // break; // } // case_else: // statements to evaluate R3; // result = R3; // } while (0); // // If the X is omitted then U and V are normal boolean expressions and // the code becomes if (U) etc if (V) etc. with no temp. static void cg_expr_case(ast_node *case_expr, CSTR str, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { Contract(is_ast_case_expr(case_expr)); EXTRACT_ANY(expr, case_expr->left); EXTRACT_NOTNULL(connector, case_expr->right); EXTRACT_NOTNULL(case_list, connector->left); EXTRACT_ANY(else_expr, connector->right); // if we need an else label, this will hold the value. int32_t else_label_number = -1; sem_t sem_type_result = case_expr->sem->sem_type; // CASE [expr]? [case_list] ELSE [else_expr] END // The answer will be stored in this scratch variable, any type is possible CG_SETUP_RESULT_VAR(case_expr, sem_type_result); cg_line_directive_min(case_expr, cg_main_output); bprintf(cg_main_output, "do {\n"); CG_PUSH_MAIN_INDENT(do, 2); // if the form is case expr when ... then save the expr in a temporary if (expr) { cg_line_directive_min(expr, cg_main_output); sem_t sem_type_expr = expr->sem->sem_type; CG_PUSH_TEMP(temp, sem_type_expr); int32_t stack_level_saved = stack_level; // Compute the value of the expression. CG_PUSH_EVAL(expr, C_EXPR_PRI_EQ_NE); // Store it in the temporary we just made, which has the exact correct type (we just made it) bprintf(cg_main_output, " "); cg_store_same_type(cg_main_output, temp.ptr, sem_type_expr, expr_is_null.ptr, expr_value.ptr); // here "temp" is like a mini-result variable... anything from expr can be released // we only need temp now, so restore to that level. stack_level = stack_level_saved; // If the expression is null, then we go to the else logic. Note: there is always else logic // either the user provides it or we do (to use null as the default). if (is_nullable(sem_type_expr)) { else_label_number = ++case_statement_count; bprintf(cg_main_output, " if (%s) ", temp_is_null.ptr); bprintf(cg_main_output, "goto case_else_%d;\n", else_label_number); } cg_case_list(case_list, temp_value.ptr, result_var.ptr, sem_type_result); CG_POP_EVAL(expr); CG_POP_TEMP(temp); } else { // Otherwise do the case list with no expression... cg_case_list(case_list, NULL, result_var.ptr, sem_type_result); } if (else_label_number >= 0) { bprintf(cg_main_output, "case_else_%d:\n", else_label_number); } // If there is an else clause, spit out the result for that now. // Note that lack of an else is by-construction a nullable outcome because // the semantics of case say that if you miss all the cases you get null. if (else_expr) { cg_line_directive_min(else_expr, cg_main_output); sem_t sem_type_else = else_expr->sem->sem_type; CG_PUSH_EVAL(else_expr, C_EXPR_PRI_ROOT); cg_line_directive_max(case_expr, cg_main_output); cg_store(cg_main_output, result_var.ptr, sem_type_result, sem_type_else, else_expr_is_null.ptr, else_expr_value.ptr); CG_POP_EVAL(else_expr); } else { // No else, result must be nullable. (enforced by cg_set_null) cg_line_directive_max(case_expr, cg_main_output); cg_set_null(cg_main_output, result_var.ptr, sem_type_result); } CG_POP_MAIN_INDENT(do); CG_CLEANUP_RESULT_VAR(); bprintf(cg_main_output, "} while (0);\n"); } static void cg_expr_cast(ast_node *cast_expr, CSTR str, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { Contract(is_ast_cast_expr(cast_expr)); sem_t sem_type_result = cast_expr->sem->sem_type; sem_t core_type_result = core_type_of(sem_type_result); ast_node *expr = cast_expr->left; sem_t core_type_expr = core_type_of(expr->sem->sem_type); CSTR type_text = NULL; CSTR bool_norm = ""; if (core_type_expr == SEM_TYPE_BOOL) { // convert bool to 0/1 bool_norm = "!!"; } switch (core_type_result) { case SEM_TYPE_INTEGER: type_text = rt->cql_int32; break; case SEM_TYPE_LONG_INTEGER: type_text = rt->cql_int64; break; case SEM_TYPE_REAL: type_text = rt->cql_double; break; case SEM_TYPE_BOOL: // convert to 0/1 as part of conversion bool_norm = "!!"; type_text = rt->cql_bool; break; } Invariant(type_text); // all other types forbidden by semantic analysis CG_RESERVE_RESULT_VAR(cast_expr, sem_type_result); CG_PUSH_EVAL(expr, pri_new); CHARBUF_OPEN(result); bprintf(&result, "((%s)%s(%s))", type_text, bool_norm, expr_value.ptr); if (core_type_expr == core_type_result) { // no-op cast, just pass through bprintf(is_null, "%s", expr_is_null.ptr); bprintf(value, "%s", expr_value.ptr); } else if (is_not_nullable(sem_type_result)) { // simple cast, use the result with no temporary bprintf(value, "%s", result.ptr); bprintf(is_null, "0"); } else { // nullable form, make a result variable and store CG_USE_RESULT_VAR(); cg_set_nullable(cg_main_output, result_var.ptr, expr_is_null.ptr, result.ptr); } CHARBUF_CLOSE(result); CG_POP_EVAL(expr); CG_CLEANUP_RESULT_VAR(); } // A CQL string literal needs to be stored somewhere so it looks like a string_ref. // Here is a helper method for creating the name of the literal. We use // some letters from the text of the literal in the variable name to make it // easier to find and recognize. static bool_t cg_make_nice_literal_name(CSTR str, charbuf *output) { // empty buffer (just the null terminator) Contract(output->used == 1); CSTR existing_name = find_literal(str); if (existing_name) { bprintf(output, "%s", existing_name); return false; } bprintf(output, "_literal_%d", ++string_literals_count); bool_t underscore = 0; for (int32_t i = 0; str[i] && i < CQL_NICE_LITERAL_NAME_LIMIT; i++) { char ch = str[i]; if (Isalpha(ch)) { bputc(output, ch); underscore = 0; } else if (ch == '\\') { if (str[i+1]) i++; // don't fall off the end } else if (!underscore) { bputc(output, '_'); underscore = 1; } } if (current_proc) { EXTRACT_STRING(name, current_proc->left); bprintf(output, "%s", name); } symtab_add(string_literals, str, Strdup(output->ptr)); return true; } // This converts from SQL string literal format to C literal format. // * the single quotes around the string become double quotes // * escaped single quote becomes just single quote // * backslash escapes are preserved static void cg_requote_literal(CSTR str, charbuf *output) { CHARBUF_OPEN(plaintext); cg_decode_string_literal(str, &plaintext); cg_encode_c_string_literal(plaintext.ptr, output); CHARBUF_CLOSE(plaintext); } // Here we use the helper above to create a variable name for the literal // then we declare that variable and emit the initializer. The macro // cql_string_literal does the job for us while allowing the different // string implementations. These go into the constants section. static void cg_string_literal(CSTR str, charbuf *output) { Contract(str); Contract(str[0] == '\''); CHARBUF_OPEN(name); bool_t is_new = cg_make_nice_literal_name(str, &name); // Emit reference to a new shared string. bprintf(output, name.ptr); if (is_new) { // The shared string itself must live forever so it goes in global constants. bprintf(cg_constants_output, "%s(%s, ", rt->cql_string_literal, name.ptr); cg_requote_literal(str, cg_constants_output); bprintf(cg_constants_output, ");\n"); } CHARBUF_CLOSE(name); } // The rewritten between expression is designed to be super easy to code gen. // The semantic analyzer has already turned the between or not beween into a normal // combination of and/or so all we have to do is load up the temporary with the test // value and then evaluate the test expression. Between and not between look the same // to the codgen (they will have different expressions). This lets us get all that // weird short circuit behavior super easy. It's literally the AND/OR code running. static void cg_expr_between_rewrite( ast_node *ast, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { Contract(is_ast_between_rewrite(ast)); EXTRACT_NOTNULL(range, ast->right); EXTRACT_ANY_NOTNULL(expr, ast->left); EXTRACT_STRING(var, range->left); EXTRACT_ANY_NOTNULL(test, range->right); // BETWEEN REWRITE [var := expr] CHECK [test] sem_t sem_type_var = expr->sem->sem_type; if (is_ast_null(expr)) { bprintf(is_null, "1"); bprintf(value, "0"); return; } cg_var_decl(cg_declarations_output, sem_type_var, var, CG_VAR_DECL_LOCAL); CG_PUSH_EVAL(expr, C_EXPR_PRI_ASSIGN); cg_store_same_type(cg_main_output, var, sem_type_var, expr_is_null.ptr, expr_value.ptr); CG_POP_EVAL(expr); cg_expr(test, is_null, value, pri); } // This is the first of the key primitives in codegen -- it generates the // output buffers for an identifier. There are a few interesting cases. // * if it's an out variable we refer to it as *foo // * nullable strings use "id" for .value and "!id" for .is_null // * nullable other use the variables .is_null and .value // * non-nullables use the variable for the value and "0" for is_null // // Note: It's important to use the semantic name sem->name rather than the text // of the ast because the user might refer case insensitively to the variable FoO // and we need to emit the canonical name (e.g. foo, or Foo, or whatever it was). static void cg_id(ast_node *expr, charbuf *is_null, charbuf *value) { sem_t sem_type = expr->sem->sem_type; Invariant(is_variable(sem_type)); // Crucial, we want the canonical version of the name, not any MixED case version // the user might have typed. CSTR name = expr->sem->name; // map the logical @rc variable to the correct saved version if (!strcmp(name, "@rc")) { bprintf(value, "%s", rcthrown_current); bprintf(is_null, "0"); rcthrown_used = 1; return; } // while generating expressions for the CTE assignments we might have to // rename the proc args to the name in the outermost context if (proc_arg_aliases) { symtab_entry *entry = symtab_find(proc_arg_aliases, name); if (entry) { EXTRACT_ANY_NOTNULL(var, entry->val); name = var->sem->name; } } CHARBUF_OPEN(name_buff); if (is_out_parameter(sem_type)) { bprintf(&name_buff, "(*%s)", name); name = name_buff.ptr; } if (is_ref_type(sem_type)) { // Note that reference type identifiers can't be assumed to be not null // even if declared so, because they begin uninitialized. Yes this is weird. // C has the same problem... bprintf(value, "%s", name); bprintf(is_null, "!%s", name); } else { if (is_nullable(sem_type)) { bprintf(value, "%s.value", name); bprintf(is_null, "%s.is_null", name); } else { bprintf(value, "%s", name); bprintf(is_null, "0", name); } } CHARBUF_CLOSE(name_buff); } // Recall that coalesce returns the first non-null arg from the list of arguments. // The arguments must be type compatible, this was previously verified. To do // the codgen for coalesce(X,Y) we use a pattern like this: // declare result of the appropriate type; // do { // evaluate X; // if (x is not null) { // result = X; // we can use the form where X is known to be not null // break; // we're done... // } // ... other cases just like the above... // ... the final case has no test, use it even if null // evaluate Y; // result = Y; // } while (0); static void cg_func_coalesce(ast_node *call_ast, charbuf *is_null, charbuf *value) { Contract(is_ast_call(call_ast)); EXTRACT_ANY_NOTNULL(name_ast, call_ast->left); EXTRACT_STRING(name, name_ast); EXTRACT_NOTNULL(call_arg_list, call_ast->right); EXTRACT(arg_list, call_arg_list->right); // ifnull ( [arg_list] ) // coalesce ( [arg_list] ) sem_t sem_type_result = call_ast->sem->sem_type; // the answer will be stored in this scratch variable CG_SETUP_RESULT_VAR(call_ast, sem_type_result); cg_line_directive_min(call_ast, cg_main_output); bprintf(cg_main_output, "do {\n"); CG_PUSH_MAIN_INDENT(do, 2); for (ast_node *ast = arg_list; ast; ast = ast->right) { EXTRACT_ANY_NOTNULL(expr, ast->left); sem_t sem_type_expr = expr->sem->sem_type; cg_line_directive_max(expr, cg_main_output); CG_PUSH_EVAL(expr, C_EXPR_PRI_ROOT); // Generate the test for all but the last choice. if (ast->right) { bprintf(cg_main_output, "if (!%s) {\n ", expr_is_null.ptr); // We can generate the store for a known not null value // because we just tested for not null, cg_store will pick the best // assignment macro for that case based on type. bclear(&expr_is_null); bputc(&expr_is_null, '0'); sem_type_expr |= SEM_TYPE_NOTNULL; } cg_store(cg_main_output, result_var.ptr, sem_type_result, sem_type_expr, expr_is_null.ptr, expr_value.ptr); if (ast->right) { bprintf(cg_main_output, " break;\n"); bprintf(cg_main_output, "}\n"); } CG_POP_EVAL(expr); } CG_POP_MAIN_INDENT(do); bprintf(cg_main_output, "} while (0);\n"); CG_CLEANUP_RESULT_VAR(); } // Ifnull is an alias for coalesce, with only two args. static void cg_func_ifnull(ast_node *call_ast, charbuf *is_null, charbuf *value) { cg_func_coalesce(call_ast, is_null, value); } static void cg_func_sensitive(ast_node *call_ast, charbuf *is_null, charbuf *value) { Contract(is_ast_call(call_ast)); EXTRACT_ANY_NOTNULL(name_ast, call_ast->left); EXTRACT_STRING(name, name_ast); EXTRACT_NOTNULL(call_arg_list, call_ast->right); EXTRACT(arg_list, call_arg_list->right); // sensitive ( any expression ) -- at run time this function is a no-op EXTRACT_ANY_NOTNULL(expr, arg_list->left); // we just evaluate the inner expression // we have to fake a high binding strength so that it will for sure emit parens // as the nullable() construct looks like has parens and we don't know our context // oh well, extra parens is better than the temporaries of doing this with PUSH_EVAL etc. cg_expr(expr, is_null, value, C_EXPR_PRI_HIGHEST); } static void cg_func_nullable(ast_node *call_ast, charbuf *is_null, charbuf *value) { Contract(is_ast_call(call_ast)); EXTRACT_ANY_NOTNULL(name_ast, call_ast->left); EXTRACT_STRING(name, name_ast); EXTRACT_NOTNULL(call_arg_list, call_ast->right); EXTRACT(arg_list, call_arg_list->right); // nullable ( any expression ) -- at run time this function is a no-op EXTRACT_ANY_NOTNULL(expr, arg_list->left); // we just evaluate the inner expression // we have to fake a high binding strength so that it will for sure emit parens // as the nullable() construct looks like has parens and we don't know our context // oh well, extra parens is better than the temporaries of doing this with PUSH_EVAL etc. cg_expr(expr, is_null, value, C_EXPR_PRI_HIGHEST); } typedef enum { ATTEST_NOTNULL_VARIANT_CRASH, ATTEST_NOTNULL_VARIANT_INFERRED, ATTEST_NOTNULL_VARIANT_THROW, } attest_notnull_variant; // Generates code for all functions of the attest_notnull family. static void cg_func_attest_notnull(ast_node *call_ast, charbuf *is_null, charbuf *value, attest_notnull_variant variant) { Contract(is_ast_call(call_ast)); EXTRACT_ANY_NOTNULL(name_ast, call_ast->left); EXTRACT_STRING(name, name_ast); EXTRACT_NOTNULL(call_arg_list, call_ast->right); EXTRACT(arg_list, call_arg_list->right); // notnull ( a_nullable_expression ) EXTRACT_ANY_NOTNULL(expr, arg_list->left); // result known to be not null so easy codegen sem_t sem_type_expr = expr->sem->sem_type; Invariant(is_nullable(sem_type_expr)); // expression must already be in a temp CG_PUSH_EVAL(expr, C_EXPR_PRI_ROOT); switch (variant) { case ATTEST_NOTNULL_VARIANT_CRASH: bprintf(cg_main_output, "cql_invariant(!%s);\n", expr_is_null.ptr); break; case ATTEST_NOTNULL_VARIANT_INFERRED: // Semantic analysis has guaranteed that the input is not going to be // NULL so we don't need to check anything here. break; case ATTEST_NOTNULL_VARIANT_THROW: bprintf(cg_main_output, "if (%s) {\n", expr_is_null.ptr); bprintf(cg_main_output, " _rc_ = SQLITE_ERROR;\n"); bprintf(cg_main_output, " goto %s;\n", error_target); bprintf(cg_main_output, "}\n"); error_target_used = 1; break; } bprintf(is_null, "0"); bprintf(value, "%s", expr_value.ptr); CG_POP_EVAL(expr); } static void cg_func_ifnull_throw(ast_node *call_ast, charbuf *is_null, charbuf *value) { cg_func_attest_notnull(call_ast, is_null, value, ATTEST_NOTNULL_VARIANT_THROW); } static void cg_func_ifnull_crash(ast_node *call_ast, charbuf *is_null, charbuf *value) { cg_func_attest_notnull(call_ast, is_null, value, ATTEST_NOTNULL_VARIANT_CRASH); } // The `cql_inferred_notnull` function is not used by the programmer directly, // but rather inserted via a rewrite during semantic analysis to coerce a value // of a nullable type to be nonnull. The reason for this approach, as opposed to // just changing the type directly, is that there are also representational // differences between values of nullable and nonnull types; some conversion is // required. static void cg_func_cql_inferred_notnull(ast_node *call_ast, charbuf *is_null, charbuf *value) { cg_func_attest_notnull(call_ast, is_null, value, ATTEST_NOTNULL_VARIANT_INFERRED); } // There's a helper for this method, just call it. Super easy. static void cg_func_changes(ast_node *ast, charbuf *is_null, charbuf *value) { bprintf(is_null, "0"); bprintf(value, "sqlite3_changes(_db_)"); } // There's a helper for this method, just call it. Super easy. static void cg_func_last_insert_rowid(ast_node *ast, charbuf *is_null, charbuf *value) { bprintf(is_null, "0"); bprintf(value, "sqlite3_last_insert_rowid(_db_)"); } // Printf also has a helper, we just call it. There are other helpers to emit // a call to an external (not stored proc) function. Use that. static void cg_func_printf(ast_node *call_ast, charbuf *is_null, charbuf *value) { Contract(is_ast_call(call_ast)); EXTRACT_ANY_NOTNULL(name_ast, call_ast->left); EXTRACT_STRING(name, name_ast); EXTRACT_NOTNULL(call_arg_list, call_ast->right); EXTRACT(arg_list, call_arg_list->right); CG_SETUP_RESULT_VAR(call_ast, SEM_TYPE_TEXT | SEM_TYPE_NOTNULL); bprintf(cg_main_output, "{\n"); cg_call_named_external(" char *_printf_result = sqlite3_mprintf", arg_list); bprintf(cg_main_output, " %s(%s);\n", rt->cql_string_release, result_var.ptr); bprintf(cg_main_output, " %s = %s(_printf_result);\n", result_var.ptr, rt->cql_string_ref_new); bprintf(cg_main_output, " sqlite3_free(_printf_result);\n"); bprintf(cg_main_output, "}\n"); CG_CLEANUP_RESULT_VAR(); } static void cg_func_cql_get_blob_size(ast_node *ast, charbuf*is_null, charbuf *value) { Contract(is_ast_call(ast)); EXTRACT_ANY_NOTNULL(name_ast, ast->left); EXTRACT_STRING(name, name_ast); EXTRACT_NOTNULL(call_arg_list, ast->right); EXTRACT(arg_list, call_arg_list->right); EXTRACT_ANY_NOTNULL(expr, arg_list->left); sem_t sem_type_var = name_ast->sem->sem_type; CG_RESERVE_RESULT_VAR(ast, sem_type_var); // Evaluate the expression and stow it in a temporary. CG_PUSH_EVAL(expr, C_EXPR_PRI_ROOT); CHARBUF_OPEN(temp); // store cql_get_blob_size call in temp. e.g: cql_get_blob_size(expr_value) bprintf(&temp, "%s(%s)", rt->cql_get_blob_size, expr_value.ptr); if (is_not_nullable(sem_type_var)) { // The result is known to be not nullable therefore we can store directly the value to the result buff bprintf(is_null, "0"); bprintf(value, "%s", temp.ptr); } else { CG_USE_RESULT_VAR(); cg_store(cg_main_output, result_var.ptr, sem_type_var, sem_type_var, expr_is_null.ptr, temp.ptr); } CHARBUF_CLOSE(temp); CG_POP_EVAL(expr); CG_CLEANUP_RESULT_VAR(); } // This is some kind of function call in an expression context. Look up the method // and call one of the cg_func_* workers above. All arg combos are known to be good // because semantic analysis verified them already. static void cg_expr_call(ast_node *ast, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { Contract(is_ast_call(ast)); EXTRACT_ANY_NOTNULL(name_ast, ast->left) EXTRACT_STRING(name, name_ast); // name( [arg_list] ) if (find_func(name) || find_proc(name)) { cg_user_func(ast, is_null, value); } else { symtab_entry *entry = symtab_find(cg_funcs, name); Invariant(entry); // names have already been verified! ((void (*)(ast_node *, charbuf *, charbuf *))entry->val)(ast, is_null, value); } } // Numeric literal, spit it out. static void cg_expr_num(ast_node *expr, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { Contract(is_ast_num(expr)); EXTRACT_NUM_TYPE(num_type, expr); EXTRACT_NUM_VALUE(lit, expr); // a numeric literal bprintf(is_null, "0"); if (num_type == NUM_LONG) { // add long suffix if needed bprintf(value, "_64(%s)", lit); } else { bprintf(value, "%s", lit); } } static void cg_expr_str(ast_node *expr, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { // String could be an id, or a literal -- literals start with single quote. Contract(is_ast_str(expr)); EXTRACT_STRING(str, expr); if (is_strlit(expr)) { // Note str is the lexeme, so it is still quoted and escaped. cg_string_literal(str, value); bprintf(is_null, "0"); } else { cg_id(expr, is_null, value); } } static void cg_expr_dot(ast_node *expr, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { // X.Y has a net local name computed by semantic analysis. Use it like any other id. Contract(is_ast_dot(expr)); cg_id(expr, is_null, value); } static void cg_expr_null(ast_node *expr, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { Contract(is_ast_null(expr)); // null literal bprintf(value, "NULL"); bprintf(is_null, "1"); } // This is the main entry point for codegen of an expression. It dispatches // to one of the above workers for all the complex types and handles a few primitives // in place. See the introductory notes to understand is_null and value. static void cg_expr(ast_node *expr, charbuf *is_null, charbuf *value, int32_t pri) { Contract(is_null); Contract(value); Contract(value->used == 1); // just the null (i.e. empty buffer) Contract(is_null->used == 1); // just the null (i.e. empty buffer) // These are all the expressions there are, we have to find it in this table // or else someone added a new expression type and it isn't supported yet. symtab_entry *entry = symtab_find(cg_exprs, expr->type); Invariant(entry); cg_expr_dispatch *disp = (cg_expr_dispatch*)entry->val; disp->func(expr, disp->str, is_null, value, pri, disp->pri_new); } // This is a nested select expression. To evaluate we will // * prepare a temporary to hold the result // * generate the bound SQL statement // * extract the exactly one argument into the result variable // which is of exactly the right type // * use that variable as the result. // The helper methods take care of sqlite error management. static void cg_expr_select(ast_node *ast, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { Contract(is_select_stmt(ast)); // SELECT [select_opts] [select_expr_list_con] sem_t sem_type_result = ast->sem->sem_type; CG_SETUP_RESULT_VAR(ast, sem_type_result); cg_bound_sql_statement(NULL, ast, CG_PREPARE | CG_MINIFY_ALIASES); // exactly one column is allowed, already checked in semantic analysis, fetch it bprintf(cg_main_output, "_rc_ = sqlite3_step(_temp_stmt);\n"); cg_error_on_rc_notequal("SQLITE_ROW"); cg_get_column(sem_type_result, "_temp_stmt", 0, result_var.ptr, cg_main_output); bprintf(cg_main_output, "cql_finalize_stmt(&_temp_stmt);\n"); CG_CLEANUP_RESULT_VAR(); } // select if nothing is exactly the same codegen as regular select // the throwing which is done by default was make explcit. The normal // codegen already does the "throw" (i.e. goto the current error target). static void cg_expr_select_if_nothing_throw(ast_node *ast, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { Contract(is_ast_select_if_nothing_throw_expr(ast)); EXTRACT_ANY_NOTNULL(select_expr, ast->left); cg_expr_select(select_expr, op, is_null, value, pri, pri_new); } // This helper does the evaluation of the select statement portion of the // (SELECT ... IF NOTHING ...) forms. Importantly the result type of the // select might not exactly match the result type of expression because // the default value could be of a different type and it might cause the // overall expression to be not null. So here we have to fetch just the // select statement part into its own result variable of the exact correct type // later we will safely assign that result to the final type if it held a value static void cg_expr_select_frag(ast_node *ast, charbuf *is_null, charbuf *value) { sem_t sem_type_result = ast->sem->sem_type; CG_SETUP_RESULT_VAR(ast, sem_type_result); cg_bound_sql_statement(NULL, ast, CG_PREPARE | CG_MINIFY_ALIASES); // exactly one column is allowed, already checked in semantic analysis, fetch it bprintf(cg_main_output, "_rc_ = sqlite3_step(_temp_stmt);\n"); cg_error_on_expr("_rc_ != SQLITE_ROW && _rc_ != SQLITE_DONE"); bprintf(cg_main_output, "if (_rc_ == SQLITE_ROW) {\n"); cg_get_column(sem_type_result, "_temp_stmt", 0, result_var.ptr, cg_main_output); CG_CLEANUP_RESULT_VAR(); // note that callers are expected to check the remaining error codes and clean up // the temp statement. } // This is a nested select expression. To evaluate we will // * prepare a temporary to hold the result // * generate the bound SQL statement // * extract the exactly one argument into the result variable // which is of exactly the right type // * use that variable as the result. // * if there is no row, we use the default expression // The helper methods takes care of sqlite error management. static void cg_expr_select_if_nothing(ast_node *ast, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { Contract(is_ast_select_if_nothing_expr(ast)); EXTRACT_ANY_NOTNULL(select_stmt, ast->left); EXTRACT_ANY_NOTNULL(expr, ast->right); // SELECT [select_opts] [select_expr_list_con] IF NOTHING expr sem_t sem_type_result = ast->sem->sem_type; sem_t sem_type_expr = expr->sem->sem_type; sem_t sem_type_select = select_stmt->sem->sem_type; // this is the overall result CG_SETUP_RESULT_VAR(ast, sem_type_result); CHARBUF_OPEN(select_is_null); CHARBUF_OPEN(select_value); // the select statement might have a different result type than overall // e.g. (select an_int from somewhere if nothing 2.5), the overall result is real cg_expr_select_frag(select_stmt, &select_is_null, &select_value); // we're inside of the "if (__rc__ == SQLITE_ROW) {" case // we need to store the result of the select in our output variable // note that these are known to be compatible (already verified) but they might not // be the exact same type, hence the copy. In this case we're definitely using the value. bprintf(cg_main_output, " "); cg_store(cg_main_output, result_var.ptr, sem_type_result, sem_type_select, select_is_null.ptr, select_value.ptr); bprintf(cg_main_output, "}\n"); bprintf(cg_main_output, "else {\n "); // if no row found, then evaluate and use the default CG_PUSH_EVAL(expr, C_EXPR_PRI_ASSIGN); cg_store(cg_main_output, result_var.ptr, sem_type_result, sem_type_expr, expr_is_null.ptr, expr_value.ptr); CG_POP_EVAL(expr); bprintf(cg_main_output, "}\n"); bprintf(cg_main_output, "cql_finalize_stmt(&_temp_stmt);\n"); CHARBUF_CLOSE(select_value); CHARBUF_CLOSE(select_is_null); CG_CLEANUP_RESULT_VAR(); } // This is a nested select expression. To evaluate we will // * prepare a temporary to hold the result // * generate the bound SQL statement // * extract the exactly one argument into the result variable // which is of exactly the right type // * use that variable as the result. // * if there is no row, or the returned value is null we use the default expression // The helper methods take care of sqlite error management. static void cg_expr_select_if_nothing_or_null(ast_node *ast, CSTR op, charbuf *is_null, charbuf *value, int32_t pri, int32_t pri_new) { Contract(is_ast_select_if_nothing_or_null_expr(ast)); EXTRACT_ANY_NOTNULL(select_stmt, ast->left); EXTRACT_ANY_NOTNULL(expr, ast->right); // SELECT [select_opts] [select_expr_list_con] IF NOTHING expr sem_t sem_type_result = ast->sem->sem_type; sem_t sem_type_expr = expr->sem->sem_type; sem_t sem_type_select = select_stmt->sem->sem_type; CG_SETUP_RESULT_VAR(ast, sem_type_result); CHARBUF_OPEN(select_is_null); CHARBUF_OPEN(select_value); // the select statement might have a different result type than overall // e.g. (select an_int from somewhere if nothing 2.5), the overall result is real cg_expr_select_frag(select_stmt, &select_is_null, &select_value); // we're inside of the "if (__rc__ == SQLITE_ROW) {" case // in this variation we have to first see if the result is null before we use it bprintf(cg_main_output, "}\n"); bprintf(cg_main_output, "if (_rc_ == SQLITE_DONE || %s) {\n ", select_is_null.ptr); // now row or null result, evaluate the default CG_PUSH_EVAL(expr, C_EXPR_PRI_ASSIGN); cg_store(cg_main_output, result_var.ptr, sem_type_result, sem_type_expr, expr_is_null.ptr, expr_value.ptr); CG_POP_EVAL(expr); bprintf(cg_main_output, "} else { \n "); // ok to use the value we fetched, go ahead an copy it to its final destination // note this may change the type but only in a compatible way cg_store(cg_main_output, result_var.ptr, sem_type_result, sem_type_select, select_is_null.ptr, select_value.ptr); bprintf(cg_main_output, "}\n"); bprintf(cg_main_output, "_rc_ = SQLITE_OK;\n"); bprintf(cg_main_output, "cql_finalize_stmt(&_temp_stmt);\n"); CHARBUF_CLOSE(select_value); CHARBUF_CLOSE(select_is_null); CG_CLEANUP_RESULT_VAR(); } // This is the elementary piece of the if-then construct, it's one condition // and one statement list. It can happen in the context of the top level // if or any else-if. The conditional generated requires either simple true // for not nulls or nullable true (i.e. null is false, false is false). static void cg_cond_action(ast_node *ast) { Contract(is_ast_cond_action(ast)); EXTRACT(stmt_list, ast->right); EXTRACT_ANY_NOTNULL(expr, ast->left); // [expr ast->left] THEN stmt_list sem_t sem_type_expr = expr->sem->sem_type; cg_line_directive_max(expr, cg_main_output); CG_PUSH_EVAL(expr, C_EXPR_PRI_ROOT); if (is_ast_null(expr) || is_not_nullable(sem_type_expr)) { bprintf(cg_main_output, "if (%s) {\n", expr_value.ptr); } else { bprintf(cg_main_output, "if (cql_is_nullable_true(%s, %s)) {\n", expr_is_null.ptr, expr_value.ptr); } CG_POP_EVAL(expr); if (stmt_list) { cg_stmt_list(stmt_list); cg_line_directive_max(stmt_list, cg_main_output); } bprintf(cg_main_output, "}\n"); } // Recursively emits the else-if chain. These have to nest to allow for // expressions to generate statements. static void cg_elseif_list(ast_node *ast, ast_node *elsenode) { if (ast) { Contract(is_ast_elseif(ast)); EXTRACT(cond_action, ast->left); // ELSE IF [cond_action] bprintf(cg_main_output, "else {\n"); CG_PUSH_MAIN_INDENT(else, 2); cg_cond_action(cond_action); cg_elseif_list(ast->right, elsenode); CG_POP_MAIN_INDENT(else); bprintf(cg_main_output, "}\n"); } else if (elsenode) { Contract(is_ast_else(elsenode)); // ELSE [stmt_list] cg_line_directive_min(elsenode, cg_main_output); EXTRACT(stmt_list, elsenode->left); bprintf(cg_main_output, "else {\n"); cg_stmt_list(stmt_list); bprintf(cg_main_output, "}\n"); } } // As with the other cases the fact that expressions might require statements // complicates the codegen. If there is an else-if (expression) that expression // might itself require statements to compute the expression. Even a logical AND // might require statements if there is nullability involved. // That means the overall pattern has to look like this, with nesting. // // prep statements; // result = final expression; // if (result) { // statements; // } // else { // prep statements; // result = final expression; // if (result) { // statements; // } // else { // statements; // } // } static void cg_if_stmt(ast_node *ast) { Contract(is_ast_if_stmt(ast)); EXTRACT_NOTNULL(cond_action, ast->left); EXTRACT_NOTNULL(if_alt, ast->right); // IF [cond_action] [if_alt] cg_cond_action(cond_action); EXTRACT(elseif, if_alt->left); EXTRACT_NAMED(elsenode, else, if_alt->right); cg_elseif_list(elseif, elsenode); // END IF } // This code uses the same cg_store helper method to do an assignment as // is used all over the place for assigning to scratch variables. All // we have to do here is pull the name and types out of the ast. static void cg_assign(ast_node *ast) { Contract(is_ast_assign(ast) || is_ast_let_stmt(ast)); EXTRACT_ANY_NOTNULL(name_ast, ast->left); EXTRACT_ANY_NOTNULL(expr, ast->right); CSTR name = name_ast->sem->name; // crucial: use the canonical name not the specified name Contract(stack_level == 0); // SET [name] := [expr] sem_t sem_type_var = name_ast->sem->sem_type; sem_t sem_type_expr = expr->sem->sem_type; CG_PUSH_EVAL(expr, C_EXPR_PRI_ASSIGN); cg_store(cg_main_output, name, sem_type_var, sem_type_expr, expr_is_null.ptr, expr_value.ptr); CG_POP_EVAL(expr); } // In the LET statement, we declare the variable based on type, emit that // then do the usual SET codegen. static void cg_let_stmt(ast_node *ast) { Contract(is_ast_let_stmt(ast)); EXTRACT_ANY_NOTNULL(name_ast, ast->left); EXTRACT_STRING(name, name_ast); cg_declare_simple_var(name_ast->sem->sem_type, name); cg_assign(ast); } // This is the processing for a single parameter in a stored proc declaration. // All we have to do is emit the type signature of that parameter. This is // precisely what cg_var_decl with CG_VAR_DECL_PROTO is for... static void cg_param(ast_node *ast, charbuf *decls) { Contract(is_ast_param(ast)); EXTRACT_NOTNULL(param_detail, ast->right); EXTRACT_ANY_NOTNULL(name_ast, param_detail->left) EXTRACT_STRING(name, name_ast); // [in out] name [datatype] sem_t sem_type = name_ast->sem->sem_type; cg_var_decl(decls, sem_type, name, CG_VAR_DECL_PROTO); } // Walk all the params of a stored proc and emit each one with a comma where needed. // cg_param does all the hard work. static void cg_params(ast_node *ast, charbuf *decls) { Contract(is_ast_params(ast)); while (ast) { Contract(is_ast_params(ast)); EXTRACT_NOTNULL(param, ast->left); cg_param(param, decls); if (ast->right) { bprintf(decls, ", "); } ast = ast->right; } } // Emit any initialization code needed for the parameters // in particular out parameters assume that there is garbage // in the out location, so they hammer a NULL or 0 into that slot. static void cg_param_init(ast_node *ast, charbuf *body) { Contract(is_ast_param(ast)); EXTRACT_NOTNULL(param_detail, ast->right); EXTRACT_ANY_NOTNULL(name_ast, param_detail->left) EXTRACT_STRING(name, name_ast); // [in out] name [datatype] sem_t sem_type = name_ast->sem->sem_type; // In a proc decl the out arg initialized to null, this avoids attempting // to release any incoming garbage value and ensures some sanity in the event // the the return code is ignored... Nobody ignores return codes, right? if (is_out_parameter(sem_type) && !is_in_parameter(sem_type)) { if (is_ref_type(sem_type)) { bprintf(body, " *(void **)%s = NULL; // set out arg to non-garbage\n", name); } else if (is_nullable(sem_type)) { bprintf(body, " cql_set_null(*%s); // set out arg to non-garbage\n", name); } else { bprintf(body, " *%s = 0; // set out arg to non-garbage\n", name); } } } // Walk all the params of a stored proc, if any of them require initialization code // in the body, emit that here. static void cg_params_init(ast_node *ast, charbuf *body) { Contract(is_ast_params(ast)); while (ast) { Contract(is_ast_params(ast)); EXTRACT_NOTNULL(param, ast->left); cg_param_init(param, body); ast = ast->right; } } // This is used when we are making a call to a known stored proc. This is usable // only in limited cases where we have previously set up local variables whose // names exactly match the formal names of the function we are attempting to call. // In particular the function that generates a rowset knows how to call the corresponding // function that generates a statement because their signatures exactly match. static void cg_param_name(ast_node *ast, charbuf *output) { Contract(is_ast_param(ast)); EXTRACT_NOTNULL(param_detail, ast->right); EXTRACT_ANY_NOTNULL(name_ast, param_detail->left) EXTRACT_STRING(name, name_ast); bprintf(output, "%s", name); } // This loops through the parameters and emits each one as part of a call // where we have a local for each parameter. See above. static void cg_param_names(ast_node *ast, charbuf *output) { Contract(is_ast_params(ast)); while (ast) { Contract(is_ast_params(ast)); EXTRACT_NOTNULL(param, ast->left); cg_param_name(param, output); if (ast->right) { bprintf(output, ", "); } ast = ast->right; } } // row types are emitted in a canonical order to make comparison, hashing // and other operations easier. For now that order is simply primitive // types and then reference types. This will become more strict over time // as other areas take advantage of ordering to generate more compact code. static void cg_fields_in_canonical_order(charbuf *output, sem_struct *sptr) { uint32_t count = sptr->count; // first primitive types for (int32_t i = 0; i < count; i++) { sem_t sem_type = sptr->semtypes[i]; if (is_ref_type(sem_type)) { continue; } CSTR col = sptr->names[i]; bprintf(output, " "); cg_var_decl(output, sem_type, col, CG_VAR_DECL_PROTO); bprintf(output, ";\n"); } // then reference types for (int32_t i = 0; i < count; i++) { sem_t sem_type = sptr->semtypes[i]; if (!is_ref_type(sem_type)) { continue; } CSTR col = sptr->names[i]; bprintf(output, " "); cg_var_decl(output, sem_type, col, CG_VAR_DECL_PROTO); bprintf(output, ";\n"); } } // This function generates a struct definition into the indicated output. // The struct will be named for the current proc name and the given name // at least one of which must be non-null. This is used for the result // type of procs with the OUT keyword and for automatic cursors. // The struct includes the _has_row_ boolean plus the fields of the // sem_struct provided. static void cg_c_struct_for_sptr(charbuf *output, sem_struct *sptr, CSTR name) { Invariant(sptr); CSTR scope = current_proc_name(); Contract(scope || name); // no scope and no name makes no sense CSTR suffix = (name && scope) ? "_" : ""; scope = scope ? scope : ""; name = name ? name : ""; CG_CHARBUF_OPEN_SYM(row_type, scope, suffix, name, "_row"); bprintf(output, "\ntypedef struct %s {\n", row_type.ptr); // emit the two standard fields, _has_row_, _refs_count_, and _refs_offset_ bprintf(output, " cql_bool _has_row_;\n"); bprintf(output, " cql_uint16 _refs_count_;\n"); bprintf(output, " cql_uint16 _refs_offset_;\n"); cg_fields_in_canonical_order(output, sptr); bprintf(output, "} %s;\n", row_type.ptr); CHARBUF_CLOSE(row_type); } static int32_t refs_count_sptr(sem_struct *sptr) { int32_t refs_count = 0; for (int32_t i = 0; i < sptr->count; i++) { if (is_ref_type(sptr->semtypes[i])) { refs_count++; } } return refs_count; } // Emit the offsets for the given struct type into the output with the given name static void cg_col_offsets(charbuf *output, sem_struct *sptr, CSTR sym_name, CSTR struct_name) { uint32_t count = sptr->count; CSTR prefix = in_var_group_emit ? "" : "static "; bprintf(output, "\n%scql_uint16 %s[] = { %d", prefix, sym_name, count); for (int32_t i = 0; i < sptr->count; i++) { bprintf(output, ",\n"); bprintf(output, " cql_offsetof(%s, %s)", struct_name, sptr->names[i]); } bprintf(output, "\n};\n"); } static void cg_data_types(charbuf *output, sem_struct *sptr, CSTR sym_name) { CSTR prefix = in_var_group_emit ? "" : "static "; bprintf(output, "\n%suint8_t %s[] = {\n", prefix, sym_name); for (int32_t i = 0; i < sptr->count; i++) { if (i != 0) { bprintf(output, ",\n"); } bprintf(output, " "); cg_data_type(output, false, sptr->semtypes[i]); } bprintf(output, "\n};\n"); } // Emit the offsets for the reference types in the given struct type into the output static void cg_refs_offset(charbuf *output, sem_struct *sptr, CSTR offset_sym, CSTR struct_name) { int32_t refs_count = refs_count_sptr(sptr); bprintf(output, "\n#define %s ", offset_sym); for (int32_t i = 0; i < sptr->count; i++) { if (is_ref_type(sptr->semtypes[i])) { bprintf(output, "cql_offsetof(%s, %s) // count = %d\n", struct_name, sptr->names[i], refs_count); break; } } } static void find_identity_columns_callback(CSTR _Nonnull name, ast_node *_Nonnull found_ast, void *_Nullable context) { Invariant(context); charbuf *output = (charbuf*)context; Invariant(current_proc); Invariant(current_proc->sem); Invariant(current_proc->sem->sptr); sem_struct *sptr = current_proc->sem->sptr; // already checked in semantic analysis int32_t col_index = sem_column_index(sptr, name); Invariant(col_index >= 0); bprintf(output, " %d, // %s\n", col_index, name); } // Emit the array of identity columns (used by cql_rows_same to determine which columns identify the "same" record) // Return 1 if any identity columns were found; otherwise 0 static bool_t cg_identity_columns( charbuf *headers_output, charbuf *defs_output, CSTR proc_name, ast_node *_Nullable misc_attrs, CSTR identity_columns_sym) { if (!misc_attrs) { return false; } CHARBUF_OPEN(cols); uint32_t count = find_identity_columns(misc_attrs, &find_identity_columns_callback, &cols); if (count > 0) { bprintf(headers_output, "%scql_uint16 %s[];\n\n", rt->symbol_visibility, identity_columns_sym); bprintf(defs_output, "\ncql_uint16 %s[] = { %d,\n%s};\n", identity_columns_sym, count, cols.ptr); } CHARBUF_CLOSE(cols); return count > 0; } // Emit the teardown information for use in in a rowset row or cursor row // this is just the count of references and the offset of the first reference // in the row structure. Since the references are stored together and are // at the end of the row you can alway clean up a row using just the offset // and the count. static void cg_struct_teardown_info(charbuf *output, sem_struct *sptr, CSTR name) { Invariant(sptr); int32_t refs_count = refs_count_sptr(sptr); if (!refs_count) { return; } CSTR scope = current_proc_name(); Contract(scope || name); // no scope and no name makes no sense CSTR suffix = (name && scope) ? "_" : ""; scope = scope ? scope : ""; name = name ? name : ""; CG_CHARBUF_OPEN_SYM(count_sym, scope, suffix, name, "_refs_count"); CG_CHARBUF_OPEN_SYM(offset_sym, scope, suffix, name, "_refs_offset"); CG_CHARBUF_OPEN_SYM(row_type, scope, suffix, name, "_row"); cg_refs_offset(output, sptr, offset_sym.ptr, row_type.ptr); CHARBUF_CLOSE(row_type); CHARBUF_CLOSE(offset_sym); CHARBUF_CLOSE(count_sym); } // Emit the return code variables for the procedure // if the procedure uses throw then it needs the saved RC as well so we can re-throw it void cg_emit_rc_vars(charbuf *output) { bprintf(output, " %s _rc_ = SQLITE_OK;\n", rt->cql_code); } // For each parameter, emit a contract that enforces nullability as follows: // // * In the case of an IN NOT NULL parameter, enforce that the caller has not // passed NULL. // * Similarly, in the case of an OUT or INOUT parameter, again enforce that the // caller has not passed NULL. // * In addition to the previous case, in the case of an INOUT NOT NULL // parameter, enforce that the pointer provided by the caller does not point // to NULL. // // The contracts emitted always match the _Notnull parameter attributes in the // declaration of the procedure. // // NOTE: These are temporarily being emitted as tripwires instead of contracts. static void cg_emit_contracts(ast_node *ast, charbuf *b) { Contract(is_ast_params(ast)); Contract(b); bool_t did_emit_contract = 0; int32_t position = 1; for (ast_node *params = ast; params; params = params->right, position++) { Contract(is_ast_params(params)); EXTRACT_NOTNULL(param, params->left); EXTRACT_NOTNULL(param_detail, param->right); EXTRACT_ANY_NOTNULL(name_ast, param_detail->left); EXTRACT_STRING(name, name_ast); sem_t sem_type = name_ast->sem->sem_type; bool_t is_nonnull_ref_type = is_not_nullable(sem_type) && is_ref_type(sem_type); if (is_inout_parameter(sem_type) && is_nonnull_ref_type) { // This is the special case of `INOUT arg R NOT NULL`, where `R` is a // reference type. This case is special because both the argument and what // it points to must not be null; the former because we'll write to it, // and the latter because we'll read it and expect it to not be NULL. bprintf(b, " cql_contract_argument_notnull_when_dereferenced((void *)%s, %d);\n", name, position); did_emit_contract = 1; } else if (is_out_parameter(sem_type) || is_nonnull_ref_type) { // Here, only the argument itself must not be null. This is either because // we have an OUT argument and thus only need to be able to write to the // address given, or because we have an `IN arg R NOT NULL` (where `R` is // some reference type) that we'll read and expect to not be NULL. bprintf(b, " cql_contract_argument_notnull((void *)%s, %d);\n", name, position); did_emit_contract = 1; } } if (did_emit_contract) { bprintf(b, "\n"); } } #define EMIT_DML_PROC 1 // emit a prototype for the fetch results function into the indicated buffer // we need some context such as "is it a dml proc" to do this correctly but // otherwise this is just using some of our standard helpers static void cg_emit_fetch_results_prototype( bool_t dml_proc, ast_node *params, CSTR proc_name, CSTR result_set_name, charbuf *decl) { CG_CHARBUF_OPEN_SYM(fetch_results_sym, proc_name, "_fetch_results"); CG_CHARBUF_OPEN_SYM(result_set_ref, result_set_name, "_result_set_ref"); // either return code or void if (dml_proc) { bprintf(decl, "CQL_WARN_UNUSED %s ", rt->cql_code); } else { bprintf(decl, "void "); } // proc name bprintf(decl, "%s(", fetch_results_sym.ptr); // optional db reference if (dml_proc) { bprintf(decl, "sqlite3 *_Nonnull _db_,"); } // result set type bprintf(decl, " %s _Nullable *_Nonnull result_set", result_set_ref.ptr); // args to forward if (params) { bprintf(decl, ", "); cg_params(params, decl); } CHARBUF_CLOSE(result_set_ref); CHARBUF_CLOSE(fetch_results_sym); } // The prototype for the given procedure goes into the given buffer. This // is a naked prototype, so additional arguments could be added -- it will be // missing the trailing ")" and it will not have EXPORT or anything like that // on it. static void cg_emit_proc_prototype(ast_node *ast, charbuf *proc_decl) { Contract(is_ast_create_proc_stmt(ast) || is_ast_declare_proc_stmt(ast)); EXTRACT_NOTNULL(proc_params_stmts, ast->right); EXTRACT(params, proc_params_stmts->left); EXTRACT_MISC_ATTRS(ast, misc_attrs); CSTR name; if (is_ast_create_proc_stmt(ast)) { EXTRACT_STRING(n, ast->left); name = n; } else { EXTRACT_NOTNULL(proc_name_type, ast->left); EXTRACT_STRING(n, proc_name_type->left); name = n; } 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); // 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); if (private_proc) { bprintf(proc_decl, "static "); } bool_t need_comma = 0; if (dml_proc) { bprintf(proc_decl, "CQL_WARN_UNUSED %s %s(sqlite3 *_Nonnull _db_", rt->cql_code, proc_sym.ptr); if (result_set_proc) { bprintf(proc_decl, ", sqlite3_stmt *_Nullable *_Nonnull _result_stmt"); } need_comma = 1; } else { bprintf(proc_decl, "void %s(", proc_sym.ptr); } if (out_union_proc) { CG_CHARBUF_OPEN_SYM(result_set_ref, name, "_result_set_ref"); if (need_comma) { bprintf(proc_decl, ", "); } // result set type bprintf(proc_decl, "%s _Nullable *_Nonnull _result_set_", result_set_ref.ptr); need_comma = 1; CHARBUF_CLOSE(result_set_ref); } // CREATE PROC [name] ( [params] ) if (params) { if (need_comma) { bprintf(proc_decl, ", "); } cg_params(params, proc_decl); } if (out_stmt_proc) { if (dml_proc || params) { bprintf(proc_decl, ", "); } CG_CHARBUF_OPEN_SYM(result_type, name, "_row"); bprintf(proc_decl, "%s *_Nonnull _result_", result_type.ptr); CHARBUF_CLOSE(result_type); } if (!params && !out_stmt_proc && !out_union_proc && !dml_proc) { bprintf(proc_decl, "void"); // make foo(void) rather than foo() } CHARBUF_CLOSE(proc_sym); CHARBUF_CLOSE(proc_name_base); } // Emitting a stored proc is mostly setup. We have a bunch of housekeeping to do: // * create new scratch buffers for the body and the locals and the cleanup section // * save the current output globals // * set the globals to point to those buffers // * save the old scratch masks and create new ones // * emit the prototype of the C function for this proc into two streams: // * the .h file (in prototype form) // * the current main_output (in function definition form) // * use the helper method above to emit the parameters // * recursively spit out the statements // * when this is all done assemble the pieces into the original output streams // * procedures that use SQL will get a hidden _db_ argument // * procedures that return a result set will get a statement output // * and the additional procedures for creating the result set and accessing it are emitted 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)); } // Here we have to emit the prototype for the declared function as a C prototype // this is just like our stored procs but we also have a return type. See cg_declare_proc_stmt // for a comparison. static void cg_declare_func_stmt(ast_node *ast) { Contract(is_ast_declare_func_stmt(ast)); EXTRACT_STRING(name, ast->left); EXTRACT_NOTNULL(func_params_return, ast->right); EXTRACT(params, func_params_return->left); sem_t sem_type_return = func_params_return->right->sem->sem_type; CHARBUF_OPEN(func_decl); CG_CHARBUF_OPEN_SYM(func_sym, name); cg_var_decl(&func_decl, sem_type_return, func_sym.ptr, 0); bprintf(&func_decl, "("); // DECLARE FUNC [name] ( [params] ) returntype if (params) { cg_params(params, &func_decl); } else { bprintf(&func_decl, "void"); } bprintf(cg_header_output, "%s%s);\n", rt->symbol_visibility, func_decl.ptr); CHARBUF_CLOSE(func_sym); CHARBUF_CLOSE(func_decl); } static void cg_declare_select_func_stmt(ast_node *ast) { Contract(is_ast_declare_select_func_stmt(ast)); // We do not emit the declaration of the sql UDF into the header file // since it is not callable from C (unlike regular declared functions) // NO-OP } // Emit the prototype for the declared method, but no body. static void cg_declare_proc_stmt(ast_node *ast) { Contract(is_ast_declare_proc_stmt(ast)); EXTRACT_NOTNULL(proc_name_type, ast->left); EXTRACT_ANY_NOTNULL(name_ast, proc_name_type->left); EXTRACT_STRING(name, name_ast); EXTRACT_NOTNULL(proc_params_stmts, ast->right); EXTRACT(params, proc_params_stmts->left); EXTRACT_MISC_ATTRS(ast, misc_attrs); Contract(!current_proc); current_proc = ast; bool_t private_proc = misc_attrs && exists_attribute_str(misc_attrs, "private"); bool_t out_stmt_proc = has_out_stmt_result(ast); bool_t out_union_proc = has_out_union_stmt_result(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"); CG_CHARBUF_OPEN_SYM(result_set, name, "_result_set"); cg_result_set_type_decl(cg_fwd_ref_output, result_set.ptr, result_set_ref.ptr); CHARBUF_CLOSE(result_set); CHARBUF_CLOSE(result_set_ref); } if (out_stmt_proc) { cg_c_struct_for_sptr(cg_fwd_ref_output, ast->sem->sptr, NULL); } if (private_proc) { bprintf(cg_declarations_output, "%s);\n\n", proc_decl.ptr); } else { bprintf(cg_fwd_ref_output, "%s%s);\n\n", rt->symbol_visibility, proc_decl.ptr); } current_proc = NULL; CHARBUF_CLOSE(proc_decl); } static void cg_declare_simple_var(sem_t sem_type, CSTR name) { // if in a variable group we only emit the header part of the declarations if (!in_var_group_decl) { cg_var_decl(cg_declarations_output, sem_type, name, CG_VAR_DECL_LOCAL); } if (!in_proc) { bprintf(cg_header_output, "%s", rt->symbol_visibility); cg_var_decl(cg_header_output, sem_type, name, CG_VAR_DECL_PROTO); bprintf(cg_header_output, ";\n"); } } // Emit a bunch of variable declarations for normal variables. // cg_var_decl does exactly this job for us. Add any global variables to // the header file output. static void cg_declare_vars_type(ast_node *declare_vars_type) { Contract(is_ast_declare_vars_type(declare_vars_type)); EXTRACT_NOTNULL(name_list, declare_vars_type->left); // DECLARE [name_list] [data_type] for (ast_node *ast = name_list; ast; ast = ast->right) { EXTRACT_ANY_NOTNULL(name_ast, ast->left); EXTRACT_STRING(name, name_ast); cg_declare_simple_var(name_ast->sem->sem_type, name); } } // This is a callback method handed to the gen_ method that creates SQL for us // it will call us every time it finds a variable that needs to be bound. That // variable is replaced by ? in the SQL output. We end up with a list of variables // to bind on a silver platter (but in reverse order). static bool_t cg_capture_variables(ast_node *ast, void *context, charbuf *buffer) { // all variables have a name Contract(ast->sem->name); // If the current context is inline function expansion then arg variables // are emitted as is -- we rewrite these so that they come from an inline table // e.g. // 'select x + y' // becomes // '(select x + y from (select arg1 x, arg2 y))' // // as a result x, y are not bound variables if (in_inline_function_fragment) { return false; } cur_variable_count++; symtab_entry *entry = symtab_find(proc_arg_aliases, ast->sem->name); if (entry) { // this variable has been rewritten to a new name, use the alias ast = entry->val; } list_item **head = (list_item**)context; add_item_to_list(head, ast); bprintf(buffer, "?"); return true; } // This is a callback method handed to the gen_ method that creates SQL for us // it will call us every time it finds a cte table that needs to be generated. // If this is one of the tables that is supposed to be an "argument" then // we will remove the stub definition of the CTE. References to this name // will be changed to required table in another callback static bool_t cg_suppress_cte(ast_node *ast, void *context, charbuf *buffer) { Contract(is_ast_cte_table(ast)); EXTRACT(cte_decl, ast->left); EXTRACT_STRING(name, cte_decl->left); // if we have an alias we suppress the name symtab_entry *entry = symtab_find(proc_cte_aliases, name); return !!entry; } // This a callback method handed to the gen_ method that creates SQL for us // it will call us every time it finds a table reference that needs to be generated. // If this is one of the tables that is supposed to be an "argument" then // we will emit the desired value instead of the stub name. Note that // this is always the name of a CTE and CTE of the old name was suppressed // using the callback above cg_suppress_cte static bool_t cg_table_rename(ast_node *ast, void *context, charbuf *buffer) { // this is a simple table factor, so an actual name... EXTRACT_STRING(name, ast); bool_t handled = false; // if we have an alias we suppress the name symtab_entry *entry = symtab_find(proc_cte_aliases, name); if (entry) { EXTRACT(cte_binding, entry->val); EXTRACT_STRING(actual, cte_binding->left); bprintf(buffer, "%s", actual); handled = true; } return handled; } // This helper method fetchs a single column from a select statement. The result // is to be stored in the local variable "var" which will be in the correct state // including nullability. There are helpers for most cases, otherwise // we can use normal sqlite accessors. Strings of course create a cql_string_ref // and blobs create a cql_blog_ref. static void cg_get_column(sem_t sem_type, CSTR cursor, int32_t index, CSTR var, charbuf *output) { sem_t core_type = core_type_of(sem_type); bprintf(output, " "); if (is_nullable(sem_type)) { switch (core_type) { case SEM_TYPE_BOOL: bprintf(output, "cql_column_nullable_bool(%s, %d, &%s);\n", cursor, index, var); break; case SEM_TYPE_INTEGER: bprintf(output, "cql_column_nullable_int32(%s, %d, &%s);\n", cursor, index, var); break; case SEM_TYPE_REAL: bprintf(output, "cql_column_nullable_double(%s, %d, &%s);\n", cursor, index, var); break; case SEM_TYPE_LONG_INTEGER: bprintf(output, "cql_column_nullable_int64(%s, %d, &%s);\n", cursor, index, var); break; case SEM_TYPE_TEXT: bprintf(output, "cql_column_nullable_string_ref(%s, %d, &%s);\n", cursor, index, var); break; case SEM_TYPE_BLOB: bprintf(output, "cql_column_nullable_blob_ref(%s, %d, &%s);\n", cursor, index, var); break; } } else { switch (core_type) { case SEM_TYPE_BOOL: bprintf(output, "%s = sqlite3_column_int(%s, %d) != 0;\n", var, cursor, index); break; case SEM_TYPE_INTEGER: bprintf(output, "%s = sqlite3_column_int(%s, %d);\n", var, cursor, index); break; case SEM_TYPE_REAL: bprintf(output, "%s = sqlite3_column_double(%s, %d);\n", var, cursor, index); break; case SEM_TYPE_LONG_INTEGER: bprintf(output, "%s = sqlite3_column_int64(%s, %d);\n", var, cursor, index); break; case SEM_TYPE_TEXT: bprintf(output, "cql_column_string_ref(%s, %d, &%s);\n", cursor, index, var); break; case SEM_TYPE_BLOB: bprintf(output, "cql_column_blob_ref(%s, %d, &%s);\n", cursor, index, var); break; } } } static void cg_cql_datatype(sem_t sem_type, charbuf *output) { if (!is_nullable(sem_type)) { bprintf(output, "CQL_DATA_TYPE_NOT_NULL | "); } switch (core_type_of(sem_type)) { case SEM_TYPE_BOOL: bprintf(output, "CQL_DATA_TYPE_BOOL"); break; case SEM_TYPE_INTEGER: bprintf(output, "CQL_DATA_TYPE_INT32"); break; case SEM_TYPE_REAL: bprintf(output, "CQL_DATA_TYPE_DOUBLE"); break; case SEM_TYPE_LONG_INTEGER: bprintf(output, "CQL_DATA_TYPE_INT64"); break; case SEM_TYPE_TEXT: bprintf(output, "CQL_DATA_TYPE_STRING"); break; case SEM_TYPE_OBJECT: bprintf(output, "CQL_DATA_TYPE_OBJECT"); break; default: // nothing else left Contract(is_blob(sem_type)); bprintf(output, "CQL_DATA_TYPE_BLOB"); break; } } // CQL uses the helper method cql_multifetch to get all the columns from a statement // This helper generates the correct CQL_DATA_TYPE_* data info and emits the // correct argument. static void cg_fetch_column(sem_t sem_type, CSTR var) { cg_cql_datatype(sem_type, cg_main_output); bprintf(cg_main_output, ", "); if (!is_out_parameter(sem_type)) { bprintf(cg_main_output, "&"); } bprintf(cg_main_output, "%s", var); } // CQL uses the helper method cql_multibind to bind all the columns to a statement // This helper generates the correct CQL_DATA_TYPE_* data info and emits the // arg in the expected format (pointers for nullable primitives) the value // for all ref types plus all non nullables. static void cg_bind_column(sem_t sem_type, CSTR var) { cg_cql_datatype(sem_type, cg_main_output); bprintf(cg_main_output, ", "); bool_t needs_address = is_nullable(sem_type) && !is_ref_type(sem_type); CSTR prefix = ""; if (needs_address) { // out parameter is already an address if (!is_out_parameter(sem_type)) { prefix = "&"; } } else { // don't want address use * prefix on out args if (is_out_parameter(sem_type)) { prefix = "*"; } } bprintf(cg_main_output, "%s%s", prefix, var); } // Emit a declaration for the temporary statement _temp_stmt_ if we haven't // already done so. Also emit the cleanup once. static void ensure_temp_statement() { if (!temp_statement_emitted) { bprintf(cg_declarations_output, "sqlite3_stmt *_temp_stmt = NULL;\n"); bprintf(cg_cleanup_output, " cql_finalize_stmt(&_temp_stmt);\n"); temp_statement_emitted = 1; } } // Now we either find the piece already and get its number or else // we can make a new piece. This is all about creating the shared // identifiers. Note that we use character offsets in the main string // as the identifiers so that we can easily offset from the base. This // saves us from having yet another array. Note also that we might want to // encode these ids in a variable length encoding so that we can have more // than 64k of them... static int32_t cg_intern_piece(CSTR str, int32_t len) { symtab_entry *entry = symtab_find(text_pieces, str); if (entry) { return (int32_t)(int64_t)(entry->val); } int32_t result = piece_last_offset; symtab_add(text_pieces, Strdup(str), piece_last_offset + (char *)NULL); piece_last_offset += len + 1; // include space for the nil bprintf(cg_pieces_output, " \"%s\\0\" // %d\n", str, result); return result; } // Variable length integer encoding: any byte that starts with the high bit set // indicates that there are more bytes. The last byte does not have the high bit set. // So the one-byte encoding is just the simple integer as one byte. static void cg_varinteger(int32_t val, charbuf *output) { do { // strip 7 bits uint32_t byte = val & 0x7f; if (val > 0x7f) { byte |= 0x80; // variable length encoding, if the high bit is set, then read another byte } val >>= 7; // peel off another 7 bits val &= 0x01ffffff; // mask off the any sign extension bprintf(output, "\\x%02x", byte); // this is the byte in hex form for a C string } while (val); } // We found a shareable fragment, encode it for emission into the literal. // Importantly these ids are 32 bits but we store them in a variable length // encoding because 32 bits everywhere eats the savings static void cg_flush_piece(CSTR start, CSTR cur, charbuf *output) { CHARBUF_OPEN(temp); int32_t len = (int32_t)(cur - start); while (start < cur) { cg_encode_char_as_c_string_literal(*start, &temp); start++; } int32_t offset = cg_intern_piece(temp.ptr, len); CHARBUF_CLOSE(temp); cg_varinteger(offset + 1, output); } // Break the input string into pieces that are likely to be shared, assign each // a number and then emit the array of those numbers instead of the original string. // We're doing this because there is a lot of redundancy in typically generated // SQL (e.g. the words SELECT, DROP, EXISTS appear a lot) and we can encode this // much more economically. Note also column names like system_function_name are // broken because the system_ part is often shared. cql_noexport uint32_t cg_statement_pieces(CSTR in, charbuf *output) { Contract(in); int32_t len = (int32_t)strlen(in); Contract(len); uint32_t count = 0; CSTR start = in; CSTR cur = in; int32_t prev_state = 0; int32_t cur_state = 0; bputc(output, '"'); cg_varinteger(len + 1, output); for (; *cur ; cur++, prev_state = cur_state) { char ch = *cur; if (ch == ' ' || ch == '\n') { cur_state = 0; // state 0 is a run of whitespace } else if ((ch >= 'a' && ch <= 'z') || (ch >= '@' && ch <= 'Z') || (ch >= '0' && ch <= '9')) { cur_state = 1; // state 1 is a run of alpha-ish charcters } else { cur_state = 2; // state 2 is a run of misc characters like operators or whatever } if (prev_state == cur_state) { continue; // keep going as long as we're on the same kind of run } if (cur - start <= 4 && cur_state == 0) { continue; // if we found whitespace keep going if we haven't seen at least 4 characters } // Ok we have something worthy of flushing: // one last chance to grow it some. We dont want single spaces to go into the output // by themselves because it's costly. Include this space in the token. Note that // this is already normalized output so multiple spaces are not a possibility. // Space and then newline is also shunned (it'll work but it doesn't happen because // gen_sql never creates that stuff). if (cur_state == 0) { cur++; // use the space/newline ch = *cur; // put ourselves into the correct state, here we let _ start an alpha-ish sttate after a break if ((ch >= 'a' && ch <= 'z') || (ch >= '@' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_') { cur_state = 1; // back to run of alpha } // note cur has been advanced now and it might be null (!) } // if we've anything to flush at this point the run is over, flush it. if (start < cur) { cg_flush_piece(start, cur, output); start = cur; count++; // if we advanced off the end above when we skipped over the space, we can exit now // we don't want to advance again off the end of the string. if (!*cur) { break; } } } // if there's anything left pending when we hit the end, flush it. if (start < cur) { cg_flush_piece(start, cur, output); count++; } bputc(output, '"'); return count; } // This tells us how many fragments we emitted using some size math static int32_t cg_fragment_count() { return (int32_t)(shared_fragment_strings.used / sizeof(CSTR)); } // when we complete a chunk of fragment text we have to emit the predicates // for the variables that were in that chunk. We do this in the same // context as the conditional for that string. static void cg_flush_variable_predicates() { if (!has_conditional_fragments) { return; } while (prev_variable_count < cur_variable_count) { if (cur_fragment_predicate == 0 || cur_fragment_predicate + 1 == max_fragment_predicate) { bprintf(cg_main_output, "_vpreds_%d[%d] = 1; // pred %d known to be 1\n", cur_bound_statement, prev_variable_count++, cur_fragment_predicate); } else { // If we're back in previous context we can always just use the predicate value // for that context which was set in an earlier block. // TODO: I think we can prove that it's always true in the code block we are in // so this could be = 1 and hence is the same as the above. bprintf(cg_main_output, "_vpreds_%d[%d] = _preds_%d[%d];\n", cur_bound_statement, prev_variable_count++, cur_bound_statement, cur_fragment_predicate); } } } // If we have set up the predicate for this chunk of text we can just use it // we see that by looking at how many predicates we set up and if we // are past that point. If we need a predicate for the current line // we use the predicate value for the "current" predicate scope, // which nests. Whatever the current predicate is we use that // and make an entry in the array. So that way there is always // one computed predicate for each chunk of text we plan to emit. static void cg_fragment_copy_pred() { if (!has_conditional_fragments) { return; } int32_t count = cg_fragment_count(); if (count + 1 == max_fragment_predicate) { return; } if (cur_fragment_predicate == 0) { bprintf(cg_main_output, "_preds_%d[%d] = 1;\n", cur_bound_statement, max_fragment_predicate++); } else { // TODO: I think we can prove that it's always true in the code block we are in // so this could be = 1 and hence is the same as the above. bprintf(cg_main_output, "_preds_%d[%d] = _preds_%d[%d];\n", cur_bound_statement, max_fragment_predicate++, cur_bound_statement, cur_fragment_predicate); } cg_flush_variable_predicates(); } // First we make sure we have a predicate row and then we emit the line // assuming there is anything to emit... static void cg_emit_one_frag(charbuf *buffer) { // TODO: can we make this an invariant? if (buffer->used > 1) { cg_fragment_copy_pred(); CSTR str = Strdup(buffer->ptr); bytebuf_append_var(&shared_fragment_strings, str); bclear(buffer); } } // Emit a fragment from a statement, note that this can nest static void cg_fragment_stmt(ast_node *stmt, charbuf *buffer) { gen_one_stmt(stmt); cg_emit_one_frag(buffer); cg_flush_variable_predicates(); } // a new block in a conditional, this is the "it's true" case for it // assign it a number and move on. Note the code is always inside of // if (the_expression_was_true) {...} static void cg_fragment_setpred() { cur_fragment_predicate = max_fragment_predicate; if (has_conditional_fragments) { bprintf(cg_main_output, "_preds_%d[%d] = 1;\n", cur_bound_statement, max_fragment_predicate++); } } // Emit the if condition for the conditional fragment and then generate the // predicate setting as well as the SQL for that part of the fragment. static void cg_fragment_cond_action(ast_node *ast, charbuf *buffer) { Contract(is_ast_cond_action(ast)); EXTRACT_NOTNULL(stmt_list, ast->right); EXTRACT_ANY_NOTNULL(expr, ast->left); // [expr ast->left] THEN stmt_list sem_t sem_type_expr = expr->sem->sem_type; cg_line_directive_max(expr, cg_main_output); CG_PUSH_EVAL(expr, C_EXPR_PRI_ROOT); if (is_ast_null(expr) || is_not_nullable(sem_type_expr)) { bprintf(cg_main_output, "if (%s) {\n", expr_value.ptr); } else { bprintf(cg_main_output, "if (cql_is_nullable_true(%s, %s)) {\n", expr_is_null.ptr, expr_value.ptr); } CG_POP_EVAL(expr); int32_t cur_fragment_predicate_saved = cur_fragment_predicate; CG_PUSH_MAIN_INDENT(ifbody, 2); cg_fragment_setpred(); // and we emit the next statement string fragment cg_fragment_stmt(stmt_list->left, buffer); cur_fragment_predicate = cur_fragment_predicate_saved; CG_POP_MAIN_INDENT(ifbody); bprintf(cg_main_output, "}\n"); } // Here we're just walking the elseif list, as with normal codegen when we get // to the end we deal with the elsenode. We can't do the else node in the caller // because we need to emit it inside the deepest matching parens. So we just // push the elsenode down the recursion until its needed. static void cg_fragment_elseif_list(ast_node *ast, ast_node *elsenode, charbuf *buffer) { if (ast) { Contract(is_ast_elseif(ast)); EXTRACT(cond_action, ast->left); // ELSE IF [cond_action] bprintf(cg_main_output, "else {\n"); CG_PUSH_MAIN_INDENT(else, 2); cg_fragment_cond_action(cond_action, buffer); cg_fragment_elseif_list(ast->right, elsenode, buffer); CG_POP_MAIN_INDENT(else); bprintf(cg_main_output, "}\n"); } else if (elsenode) { Contract(is_ast_else(elsenode)); // ELSE [stmt_list] cg_line_directive_min(elsenode, cg_main_output); EXTRACT(stmt_list, elsenode->left); bprintf(cg_main_output, "else {\n"); CG_PUSH_MAIN_INDENT(else, 2); int32_t cur_fragment_predicate_saved = cur_fragment_predicate; cg_fragment_setpred(); // this is the next string fragment cg_fragment_stmt(stmt_list->left, buffer); cur_fragment_predicate = cur_fragment_predicate_saved; CG_POP_MAIN_INDENT(else); bprintf(cg_main_output, "}\n"); } } static bool_t cg_inline_func(ast_node *call_ast, void *context, charbuf *buffer) { Contract(is_ast_call(call_ast)); EXTRACT_STRING(proc_name, call_ast->left); EXTRACT_NOTNULL(call_arg_list, call_ast->right); EXTRACT(arg_list, call_arg_list->right); // flush what we have so far cg_emit_one_frag(buffer); ast_node *ast = find_proc(proc_name); Contract(is_ast_create_proc_stmt(ast)); EXTRACT_NOTNULL(proc_params_stmts, ast->right); EXTRACT(params, proc_params_stmts->left); EXTRACT(stmt_list, proc_params_stmts->right); EXTRACT_ANY_NOTNULL(stmt, stmt_list->left); bool_t saved_in_inline_function_fragment = in_inline_function_fragment; symtab *saved_proc_arg_aliases = proc_arg_aliases; symtab *saved_proc_cte_aliases = proc_cte_aliases; in_inline_function_fragment = true; proc_arg_aliases = NULL; proc_cte_aliases = NULL; bprintf(buffer, "("); // Emit a fragment from a statement, note that this can nest cg_fragment_stmt(stmt, buffer); proc_arg_aliases = saved_proc_arg_aliases; proc_cte_aliases = saved_proc_cte_aliases; in_inline_function_fragment = saved_in_inline_function_fragment; if (params) { // If there are any args we create a nested select expression // to bind them to the variable names. Note that this means // args are evaluated once which could be important if there // are SQL functions with side-effects being used (highly rare) // or expensive functions. bprintf(buffer, " FROM (SELECT "); while (params) { Invariant(is_ast_params(params)); Invariant(arg_list); // expressions match the args EXTRACT_NOTNULL(param, params->left); EXTRACT_ANY_NOTNULL(expr, arg_list->left); EXTRACT_NOTNULL(param_detail, param->right); EXTRACT_ANY_NOTNULL(param_name_ast, param_detail->left) EXTRACT_STRING(param_name, param_name_ast); gen_root_expr(expr); bprintf(buffer, " %s", param_name); if (params->right) { bprintf(buffer, ", "); } // guaranteed to stay in lock step params = params->right; arg_list = arg_list->right; } bprintf(buffer, ")"); } bprintf(buffer, ")"); cg_emit_one_frag(buffer); return true; } static bool_t cg_call_in_cte(ast_node *cte_body, void *context, charbuf *buffer) { EXTRACT_NOTNULL(call_stmt, cte_body->left); EXTRACT(cte_binding_list, cte_body->right); EXTRACT_STRING(name, call_stmt->left); EXTRACT_ANY(expr_list, call_stmt->right); ast_node *ast = find_proc(name); Contract(is_ast_create_proc_stmt(ast)); 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 saved_in_inline_function_fragment = in_inline_function_fragment; symtab *saved_proc_arg_aliases = proc_arg_aliases; symtab *saved_proc_cte_aliases = proc_cte_aliases; in_inline_function_fragment = false; symtab *new_arg_aliases = symtab_new(); proc_cte_aliases = symtab_new(); while (cte_binding_list) { EXTRACT_NOTNULL(cte_binding, cte_binding_list->left); EXTRACT_STRING(formal, cte_binding->right); EXTRACT_STRING(actual, cte_binding->left); // The "actual" might itself be an alias from the outer scope // be sure to push that down if that's the case. One level // is always enough because each level does its own push if // needed. bool_t handled = false; if (saved_proc_cte_aliases) { symtab_entry *entry = symtab_find(saved_proc_cte_aliases, actual); if (entry) { symtab_add(proc_cte_aliases, formal, entry->val); handled = true; } } if (!handled) { // normal case, the first time a name is aliased symtab_add(proc_cte_aliases, formal, cte_binding); } cte_binding_list = cte_binding_list->right; } if (params) { // move to the next index if we need to alias anything proc_cte_index++; } while (params) { Invariant(is_ast_params(params)); Invariant(expr_list); // expressions match the args EXTRACT_NOTNULL(param, params->left); EXTRACT_ANY_NOTNULL(expr, expr_list->left); EXTRACT_NOTNULL(param_detail, param->right); EXTRACT_ANY_NOTNULL(param_name_ast, param_detail->left) EXTRACT_STRING(param_name, param_name_ast); sem_t sem_type_var = param_name_ast->sem->sem_type; CSTR alias_name = dup_printf("_p%d_%s_", proc_cte_index, param_name); AST_REWRITE_INFO_SET(param->lineno, param->filename); ast_node *alias = new_ast_str(alias_name); symtab_add(new_arg_aliases, param_name, alias); alias->sem = new_sem(sem_type_var); alias->sem->name = alias_name; alias->sem->kind = param_name_ast->sem->kind; AST_REWRITE_INFO_RESET(); // emit the declaration cg_var_decl(cg_declarations_output, sem_type_var, alias_name, CG_VAR_DECL_LOCAL); sem_t sem_type_expr = expr->sem->sem_type; // evaluate the expression and assign // note that any arg aliases here are in the context of the caller not the callee // we're setting up the aliases for the callee right now and they aren't ready yet even // but that's ok because the expressions are in the context of the caller. // todo: if the evaluation has a nested select statement then we will have to re-enter // all of this. We can either ban that (which isn't insane really) or else we can // save the codegen state like callbacks and such so that it can re-enter. That's // the desired path. CG_PUSH_EVAL(expr, C_EXPR_PRI_ASSIGN); cg_store(cg_main_output, alias_name, sem_type_var, sem_type_expr, expr_is_null.ptr, expr_value.ptr); CG_POP_EVAL(expr); // guaranteed to stay in lock step params = params->right; expr_list = expr_list->right; } // exactly one statment Invariant(!stmt_list->right); EXTRACT_ANY_NOTNULL(stmt, stmt_list->left); // now replace the aliases for just this one bit proc_arg_aliases = new_arg_aliases; cg_emit_one_frag(buffer); // we need the column names for our select // we'll accomplish this by generating a CTE wrapper // the column names are were already in the original text but // we want to minify those out, we could turn off alias minification here // but if we did that then we couldn't share the text of the fragment // so instead we make a wrapper that has exatly the column names we need bool_t is_nested_select = is_ast_table_or_subquery(cte_body->parent); CHARBUF_OPEN(wrapper); if (is_nested_select) { // this ensures that all the columns of the select are correctly named bprintf(&wrapper, "WITH _ns_("); sem_struct *sptr = cte_body->sem->sptr; for (int32_t i = 0; i < sptr->count; i++) { bprintf(&wrapper, "%s%s", i == 0 ? "": ", ", sptr->names[i]); } bprintf(&wrapper, ") AS ("); cg_emit_one_frag(&wrapper); } if (is_ast_if_stmt(stmt)) { EXTRACT_NOTNULL(cond_action, stmt->left); EXTRACT_NOTNULL(if_alt, stmt->right); EXTRACT(elseif, if_alt->left); EXTRACT_NAMED_NOTNULL(elsenode, else, if_alt->right); cg_fragment_cond_action(cond_action, buffer); cg_fragment_elseif_list(elseif, elsenode, buffer); } else { cg_fragment_stmt(stmt, buffer); } if (is_nested_select) { bprintf(&wrapper, ") SELECT * FROM _ns_"); cg_emit_one_frag(&wrapper); } CHARBUF_CLOSE(wrapper); symtab_delete(proc_arg_aliases); symtab_delete(proc_cte_aliases); proc_arg_aliases = saved_proc_arg_aliases; proc_cte_aliases = saved_proc_cte_aliases; in_inline_function_fragment = saved_in_inline_function_fragment; return true; } // We're looking for the presence of any shared fragments and in particular // the presence of conditionals within them. We don't have to do much for // this check but we do have to recurse the search as the normal walk doesn't // go into the body of shared fragments and the conditionals might be deeper // in the tree. static bool_t cg_search_conditionals_call_in_cte(ast_node *cte_body, void *context, charbuf *buffer) { EXTRACT_NOTNULL(call_stmt, cte_body->left); EXTRACT_STRING(name, call_stmt->left); ast_node *ast = find_proc(name); Contract(is_ast_create_proc_stmt(ast)); EXTRACT_NOTNULL(proc_params_stmts, ast->right); EXTRACT(params, proc_params_stmts->left); EXTRACT(stmt_list, proc_params_stmts->right); EXTRACT_ANY_NOTNULL(stmt, stmt_list->left); has_conditional_fragments |= is_ast_if_stmt(stmt); has_shared_fragments = true; // recurse the fragment contents, we might find more stuff, like variables // and such deeper in the tree gen_one_stmt(stmt); return false; } // We simply record that we found some variables, any variables static bool_t cg_note_variable_exists(ast_node *cte_body, void *context, charbuf *buffer) { has_variables = true; return false; } // The inline function counts as a shared fragment and we recurse to find any // internal shared fragments or conditional fragments inside of the inline function. // Note that even though it has no FROM clause the inline function could have // a nested select inside of its select list and therefore all fragment types // can appear inside of an inline function fragment. static bool_t cg_note_inline_func(ast_node *call_ast, void *context, charbuf *buffer) { Contract(is_ast_call(call_ast)); EXTRACT_STRING(proc_name, call_ast->left); EXTRACT_NOTNULL(call_arg_list, call_ast->right); ast_node *ast = find_proc(proc_name); Contract(is_ast_create_proc_stmt(ast)); EXTRACT_NOTNULL(proc_params_stmts, ast->right); EXTRACT(params, proc_params_stmts->left); EXTRACT(stmt_list, proc_params_stmts->right); EXTRACT_ANY_NOTNULL(stmt, stmt_list->left); // recurse the fragment contents, we might find more stuff, like variables // and such deeper in the tree gen_one_stmt(stmt); has_shared_fragments = true; return false; } // We set up a walk of the tree using the echo functions but // we are going to note what kinds of things we spotted while doing // the walk. We need to know in advance what style of codegen we'll // be doing. static void cg_classify_fragments(ast_node *stmt) { has_shared_fragments = false; has_conditional_fragments = false; has_variables = false; CHARBUF_OPEN(sql); gen_set_output_buffer(&sql); gen_sql_callbacks callbacks; init_gen_sql_callbacks(&callbacks); callbacks.cte_proc_callback = cg_search_conditionals_call_in_cte; callbacks.variables_callback = cg_note_variable_exists; callbacks.inline_func_callback = cg_note_inline_func; gen_statement_with_callbacks(stmt, &callbacks); CHARBUF_CLOSE(sql); } // This is the most important function for sqlite access; it does the heavy // lifting of generating the C code to prepare and bind a SQL statement. // If cg_exec is true (CG_EXEC) then the statement is executed immediately // and finalized. No results are expected. To accomplish this we do the following: // * figure out the name of the statement, either it's given to us // or we're using the temp statement // * call get_statement_with_callback to get the text of the SQL from the AST // * the callback will give us all the variables to bind // * count the variables so we know what column numbers to use (the list is backwards!) // * if CG_EXEC and no variables we can use the simpler sqlite3_exec form // * bind any variables // * if there are variables CG_EXEC will step and finalize static void cg_bound_sql_statement(CSTR stmt_name, ast_node *stmt, int32_t cg_flags) { list_item *vars = NULL; CSTR amp = "&"; cur_bound_statement++; cur_fragment_predicate = 0; max_fragment_predicate = 0; prev_variable_count = 0; cur_variable_count = 0; bytebuf_open(&shared_fragment_strings); if (stmt_name && !strcmp("_result", stmt_name)) { // predefined out argument amp = ""; } cg_classify_fragments(stmt); if (has_conditional_fragments) { bprintf(cg_main_output, "memset(&_preds_%d[0], 0, sizeof(_preds_%d));\n", cur_bound_statement, cur_bound_statement); if (has_variables) { bprintf(cg_main_output, "memset(&_vpreds_%d[0], 0, sizeof(_vpreds_%d));\n", cur_bound_statement, cur_bound_statement); } } bool_t minify_aliases = !!(cg_flags & CG_MINIFY_ALIASES); bool_t exec_only = !!(cg_flags & CG_EXEC); gen_sql_callbacks callbacks; init_gen_sql_callbacks(&callbacks); callbacks.variables_callback = cg_capture_variables; callbacks.variables_context = &vars; callbacks.star_callback = cg_expand_star; callbacks.minify_casts = 1; callbacks.minify_aliases = minify_aliases; callbacks.long_to_int_conv = true; callbacks.cte_proc_callback = cg_call_in_cte; callbacks.cte_suppress_callback = cg_suppress_cte; callbacks.table_rename_callback = cg_table_rename; callbacks.inline_func_callback = cg_inline_func; CHARBUF_OPEN(temp); gen_set_output_buffer(&temp); gen_statement_with_callbacks(stmt, &callbacks); // whether or not there is a prepare statement bool_t has_prepare_stmt = !exec_only || vars; uint32_t count = 0; for (list_item *item = vars; item; item = item->next, count++) ; if (stmt_name == NULL && has_prepare_stmt) { ensure_temp_statement(); stmt_name = "_temp"; } // take care of what's left in the buffer after the other fragments have been emitted if (has_shared_fragments) { cg_emit_one_frag(&temp); } if (!has_shared_fragments && options.compress) { bprintf(cg_main_output, "/* "); CHARBUF_OPEN(t2); cg_pretty_quote_plaintext(temp.ptr, &t2, PRETTY_QUOTE_C | PRETTY_QUOTE_MULTI_LINE); cg_remove_slash_star_and_star_slash(&t2); // internal "*/" is fatal. "/*" can also be under certain compilation flags bprintf(cg_main_output, "%s", t2.ptr); CHARBUF_CLOSE(t2); bprintf(cg_main_output, " */\n"); if (!has_prepare_stmt) { bprintf(cg_main_output, "_rc_ = cql_exec_frags(_db_,\n"); } else { bprintf(cg_main_output, "_rc_ = cql_prepare_frags(_db_, %s%s_stmt,\n ", amp, stmt_name); } bprintf(cg_main_output, "_pieces_, "); cg_statement_pieces(temp.ptr, cg_main_output); bprintf(cg_main_output, ");\n"); } else { CSTR suffix = has_shared_fragments ? "_var" : ""; if (!has_prepare_stmt) { bprintf(cg_main_output, "_rc_ = cql_exec%s(_db_,\n ", suffix); } else { bprintf(cg_main_output, "_rc_ = cql_prepare%s(_db_, %s%s_stmt,\n ", suffix, amp, stmt_name); } if (!has_shared_fragments) { cg_pretty_quote_plaintext(temp.ptr, cg_main_output, PRETTY_QUOTE_C | PRETTY_QUOTE_MULTI_LINE); } else { int32_t scount = cg_fragment_count(); // declare the predicate variables if needed if (has_conditional_fragments) { bprintf(cg_main_output, "%d, _preds_%d,\n", scount, cur_bound_statement); bprintf(cg_declarations_output, "char _preds_%d[%d];\n", cur_bound_statement, scount); if (has_variables) { bprintf(cg_declarations_output, "char _vpreds_%d[%d];\n", cur_bound_statement, cur_variable_count); } } else { bprintf(cg_main_output, "%d, NULL,\n", scount); } CSTR *strs = (CSTR *)(shared_fragment_strings.ptr); for (size_t i = 0; i < scount; i++) { cg_pretty_quote_plaintext(strs[i], cg_main_output, PRETTY_QUOTE_C | PRETTY_QUOTE_MULTI_LINE); if (i + 1 < scount) { bprintf(cg_main_output, ",\n"); } else { bprintf(cg_main_output, "\n"); } } } bprintf(cg_main_output, ");\n"); } CHARBUF_CLOSE(temp); reverse_list(&vars); if (count) { if (has_conditional_fragments) { bprintf(cg_main_output, "cql_multibind_var(&_rc_, _db_, %s%s_stmt, %d, _vpreds_%d", amp, stmt_name, count, cur_bound_statement); } else { bprintf(cg_main_output, "cql_multibind(&_rc_, _db_, %s%s_stmt, %d", amp, stmt_name, count); } // Now emit the binding args for each variable for (list_item *item = vars; item; item = item->next) { Contract(item->ast->sem->name); bprintf(cg_main_output, ",\n "); cg_bind_column(item->ast->sem->sem_type, item->ast->sem->name); } bprintf(cg_main_output, ");\n"); } cg_error_on_not_sqlite_ok(); if (exec_only && vars) { bprintf(cg_main_output, "_rc_ = sqlite3_step(%s_stmt);\n", stmt_name); cg_error_on_rc_notequal("SQLITE_DONE"); bprintf(cg_main_output, "cql_finalize_stmt(&%s_stmt);\n", stmt_name); } // vars is pool allocated, so we don't need to free it bytebuf_close(&shared_fragment_strings); } // Checks to see if the given statement or statement list is unbound // i.e. it does not mention any variables. We do this so that we // detect if the statement is safe to batch with others. If it had // binding then we would need to bind each statement in the block // seperately and then there would be no point in batching. static bool cg_verify_unbound_stmt(ast_node *stmt) { list_item *vars = NULL; gen_sql_callbacks callbacks; init_gen_sql_callbacks(&callbacks); callbacks.variables_callback = cg_capture_variables; callbacks.variables_context = &vars; CHARBUF_OPEN(temp); gen_set_output_buffer(&temp); gen_statement_with_callbacks(stmt, &callbacks); CHARBUF_CLOSE(temp); // vars is pool allocated, so we don't need to free it return !vars; } // This emits the declaration for an "auto cursor" -- that is a cursor // that includes storage for all the fields it can fetch. It uses the // struct helper to make a suitable struct and the creates the local and // initializes its teardown function. Code also has to go into the cleanup // section for suitable teardown. static void cg_declare_auto_cursor(CSTR cursor_name, sem_node *sem) { Contract(cursor_name); Contract(sem); sem_struct *sptr = sem->sptr; Contract(sptr); int32_t refs_count = refs_count_sptr(sptr); // when we do the variable group the struct has already been emitted if (!in_var_group_emit) { cg_c_struct_for_sptr(cg_fwd_ref_output, sptr, cursor_name); } CSTR scope = current_proc_name(); CSTR suffix = scope ? "_" : ""; scope = scope ? scope : ""; CG_CHARBUF_OPEN_SYM(row_type, scope, suffix, cursor_name, "_row"); if (refs_count) { CG_CHARBUF_OPEN_SYM(refs_offset, scope, suffix, cursor_name, "_refs_offset"); if (in_var_group_decl) { // only extern the cursor bprintf(cg_declarations_output, "%s%s %s;\n", rt->symbol_visibility, row_type.ptr, cursor_name); } else { bprintf(cg_declarations_output, "%s %s = { ._refs_count_ = %d, ._refs_offset_ = %s };\n", row_type.ptr, cursor_name, refs_count, refs_offset.ptr); bprintf(cg_cleanup_output, " cql_teardown_row(%s);\n", cursor_name); cg_struct_teardown_info(cg_fwd_ref_output, sptr, cursor_name); } CHARBUF_CLOSE(refs_offset); } else { bprintf(cg_declarations_output, "%s %s = { 0 };\n", row_type.ptr, cursor_name); } if (sem->sem_type & SEM_TYPE_SERIALIZE) { CHARBUF_OPEN(cols_name); CHARBUF_OPEN(types_name); bprintf(&cols_name, "%s_cols", cursor_name); bprintf(&types_name, "%s_data_types", cursor_name); if (in_var_group_decl) { bprintf(cg_declarations_output, "%suint16_t %s[];\n", rt->symbol_visibility, cols_name.ptr); bprintf(cg_declarations_output, "%suint8_t %s[];\n", rt->symbol_visibility, types_name.ptr); } else { cg_col_offsets(cg_declarations_output, sptr, cols_name.ptr, row_type.ptr); cg_data_types(cg_declarations_output, sptr, types_name.ptr); } CHARBUF_CLOSE(types_name); CHARBUF_CLOSE(cols_name); } CHARBUF_CLOSE(row_type); } static void cg_declare_group_stmt(ast_node *ast) { Contract(is_ast_declare_group_stmt(ast)); Contract(!in_var_group_decl); Contract(!in_var_group_emit); EXTRACT_ANY_NOTNULL(name_ast, ast->left); EXTRACT_STRING(name, name_ast); EXTRACT_NOTNULL(stmt_list, ast->right); // Put a line marker in the header file in case we want a test suite that verifies that. // Note we have to do this only because this only generates declarations so the // normal logic for emitting these doesn't kick in. if (options.test) { bprintf(cg_declarations_output, "\n// The statement ending at line %d\n", ast->lineno); bprintf(cg_fwd_ref_output, "\n// The statement ending at line %d\n", ast->lineno); } // This can be duplicated so make it safe to emit twice. // Note that the struct decls go into a different stream // so we wrap those, too. bprintf(cg_declarations_output, "#ifndef _%s_var_group_decl_\n", name); bprintf(cg_declarations_output, "#define _%s_var_group_decl_ 1\n", name); bprintf(cg_fwd_ref_output, "#ifndef _%s_var_group_structs_\n", name); bprintf(cg_fwd_ref_output, "#define _%s_var_group_structs_ 1\n", name); in_var_group_decl = true; cg_stmt_list(stmt_list); in_var_group_decl = false; bprintf(cg_declarations_output, "#endif\n"); bprintf(cg_fwd_ref_output, "#endif\n"); } static void cg_emit_group_stmt(ast_node *ast) { Contract(is_ast_emit_group_stmt(ast)); EXTRACT(name_list, ast->left); Contract(!in_var_group_decl); Contract(!in_var_group_emit); // Put a line marker in the header file in case we want a test suite that verifies that. // Note we have to do this only because this only generates declarations so the // normal logic for emitting these doesn't kick in. if (options.test) { bprintf(cg_declarations_output, "\n// The statement ending at line %d\n", ast->lineno); bprintf(cg_fwd_ref_output, "\n// The statement ending at line %d\n", ast->lineno); } while (name_list) { EXTRACT_ANY_NOTNULL(name_ast, name_list->left); EXTRACT_STRING(name, name_ast); ast_node *group = find_variable_group(name); Contract(is_ast_declare_group_stmt(group)); EXTRACT_ANY_NOTNULL(group_name_ast, group->left); EXTRACT_STRING(group_name, group_name_ast); EXTRACT_NOTNULL(stmt_list, group->right); Invariant(!Strcasecmp(name, group_name)); in_var_group_emit = true; cg_stmt_list(stmt_list); in_var_group_emit = false; name_list = name_list->right; } } // This causes enum declarations to go into the header file. // Those enum values are not even used in our codegen because the ast is // rewritten to have the actual value rather than the name. However this will // make it possible to use the enums in callers from C. The enum values are // "public" in this sense. This is a lot like the gen_sql code except it will be // in C format. Note C has no floating point enums so we have to do those with macros. static void cg_emit_one_enum(ast_node *ast) { Contract(is_ast_declare_enum_stmt(ast)); EXTRACT_NOTNULL(typed_name, ast->left); EXTRACT_NOTNULL(enum_values, ast->right); EXTRACT_ANY(name_ast, typed_name->left); EXTRACT_STRING(name, name_ast); EXTRACT_ANY_NOTNULL(type, typed_name->right); bprintf(cg_header_output, "#ifndef enum_%s_defined\n", name); bprintf(cg_header_output, "#define enum_%s_defined\n\n", name); if (core_type_of(type->sem->sem_type) != SEM_TYPE_REAL) { bprintf(cg_header_output, "enum %s {", name); while (enum_values) { EXTRACT_NOTNULL(enum_value, enum_values->left); EXTRACT_ANY_NOTNULL(enum_name_ast, enum_value->left); EXTRACT_STRING(enum_name, enum_name_ast); bool_t is_long = core_type_of(type->sem->sem_type) == SEM_TYPE_LONG_INTEGER; bprintf(cg_header_output, "\n %s__%s = ", name, enum_name); if (is_long) { bprintf(cg_header_output, "_64("); } eval_format_number(enum_name_ast->sem->value, cg_header_output); if (is_long) { bprintf(cg_header_output, ")"); } if (enum_values->right) { bprintf(cg_header_output, ","); } enum_values = enum_values->right; } bprintf(cg_header_output, "\n};\n"); } else { bprintf(cg_header_output, "\n// enum %s (floating point values)\n", name); while (enum_values) { EXTRACT_NOTNULL(enum_value, enum_values->left); EXTRACT_ANY_NOTNULL(enum_name_ast, enum_value->left); EXTRACT_STRING(enum_name, enum_name_ast); bprintf(cg_header_output, "#define %s__%s ", name, enum_name); eval_format_number(enum_name_ast->sem->value, cg_header_output); bprintf(cg_header_output, "\n"); enum_values = enum_values->right; } } bprintf(cg_header_output, "\n#endif\n", name); } static void cg_emit_enums_stmt(ast_node *ast) { Contract(is_ast_emit_enums_stmt(ast)); EXTRACT(name_list, ast->left); if (name_list) { // names specified: emit those while (name_list) { // names previously checked, we assert they are good here EXTRACT_STRING(name, name_list->left); EXTRACT_NOTNULL(declare_enum_stmt, find_enum(name)); cg_emit_one_enum(declare_enum_stmt); name_list = name_list->right; } } else { // none specified: emit all for (list_item *item = all_enums_list; item; item = item->next) { EXTRACT_NOTNULL(declare_enum_stmt, item->ast); cg_emit_one_enum(declare_enum_stmt); } } } // This causes global constant declarations to go into the header file. // Those constants are not even used in our codegen because the ast is // rewritten to have the actual value rather than the name. However this will // make it possible to use the constant in callers from C. The constant values are // "public" in this sense. This is a lot like the gen_sql code except it will be // in C format. static void cg_emit_one_const_group(ast_node *ast) { Contract(is_ast_declare_const_stmt(ast)); EXTRACT_ANY_NOTNULL(name_ast, ast->left); EXTRACT_NOTNULL(const_values, ast->right); EXTRACT_STRING(name, name_ast); bprintf(cg_header_output, "#ifndef const_group_%s_defined\n", name); bprintf(cg_header_output, "#define const_group_%s_defined\n\n", name); while (const_values) { EXTRACT_NOTNULL(const_value, const_values->left); EXTRACT_ANY_NOTNULL(const_name_ast, const_value->left); EXTRACT_STRING(const_name, const_name_ast); bprintf(cg_header_output, "#define %s ", const_name); if (is_numeric(const_value->sem->sem_type)) { bool_t is_long = core_type_of(const_value->sem->sem_type) == SEM_TYPE_LONG_INTEGER; if (is_long) { bprintf(cg_header_output, "_64("); } eval_format_number(const_value->sem->value, cg_header_output); if (is_long) { bprintf(cg_header_output, ")"); } } else { // we don't make a string object for string literals that are being emitted, just the C literal CHARBUF_OPEN(quoted); EXTRACT_STRING(literal, const_value->right); cg_requote_literal(literal, &quoted); bprintf(cg_header_output, "%s", quoted.ptr); CHARBUF_CLOSE(quoted); } bprintf(cg_header_output, "\n"); const_values = const_values->right; } bprintf(cg_header_output, "\n#endif\n", name); } static void cg_emit_constants_stmt(ast_node *ast) { Contract(is_ast_emit_constants_stmt(ast)); EXTRACT_NOTNULL(name_list, ast->left); if (name_list) { // names specified: emit those while (name_list) { // names previously checked, we assert they are good here EXTRACT_STRING(name, name_list->left); EXTRACT_NOTNULL(declare_const_stmt, find_constant_group(name)); cg_emit_one_const_group(declare_const_stmt); name_list = name_list->right; } } } // Declaring a cursor causes us to do the following: // * emit a local variable for the cursor in the declarations section // * emit cleanup logic for that local in the cleanup section // * execute the select or call statement that is associated with the cursor // * store the resulting statement for use later in fetch // * declare a hidden has_row local for the cursor so that the cursor name // can be used in expressions to see if a row was fetched. static void cg_declare_cursor(ast_node *ast) { Contract(is_ast_declare_cursor(ast)); EXTRACT_ANY_NOTNULL(name_ast, ast->left); EXTRACT_STRING(cursor_name, name_ast); bool_t out_union_proc = false; bool_t is_boxed = !!(name_ast->sem->sem_type & SEM_TYPE_BOXED); bool_t is_unboxing = is_ast_str(ast->right); if (is_ast_call_stmt(ast->right)) { out_union_proc = has_out_union_stmt_result(ast); } // only one of these (is boxed makes no sense with out union) Invariant(!out_union_proc || !is_boxed); // can't be both of these either Invariant(!out_union_proc || !is_unboxing); // unboxing implies is_boxed a->b <==> (!a | b) Invariant(!is_unboxing || is_boxed); if (out_union_proc) { EXTRACT_STRING(name, ast->right->left); CG_CHARBUF_OPEN_SYM(result_ref, name, "_result_set_ref"); bprintf(cg_declarations_output, "%s %s_result_set_ = NULL;\n", result_ref.ptr, cursor_name); bprintf(cg_declarations_output, "%s %s_row_num_ = 0;\n", rt->cql_int32, cursor_name); bprintf(cg_declarations_output, "%s %s_row_count_ = 0;\n", rt->cql_int32, cursor_name); bprintf(cg_cleanup_output, " cql_object_release(%s_result_set_);\n", cursor_name); if (cg_in_loop) { // tricky case, the call might iterate so we have to clean up the cursor before we do the call bprintf(cg_main_output, "cql_object_release(%s_result_set_);\n", cursor_name); } CHARBUF_CLOSE(result_ref); } else { bprintf(cg_declarations_output, "sqlite3_stmt *%s_stmt = NULL;\n", cursor_name); if (!is_boxed) { // easy case, no boxing, just finalize on exit. bprintf(cg_cleanup_output, " cql_finalize_stmt(&%s_stmt);\n", cursor_name); if (cg_in_loop) { // tricky case, the call might iterate so we have to clean up the cursor before we do the call bprintf(cg_main_output, "cql_finalize_stmt(&%s_stmt);\n", cursor_name); } } } if (is_select_stmt(ast->right)) { // DECLARE [name] CURSOR FOR [select_stmt] // or // DECLARE [name] CURSOR FOR [explain_stmt] EXTRACT_ANY_NOTNULL(select_stmt, ast->right); if (is_boxed) { // The next prepare will finalize the statement, we don't want to do that // if the cursor is being handled by boxes. The box downcount will take care of it bprintf(cg_main_output, "%s_stmt = NULL;\n", cursor_name); } cg_bound_sql_statement(cursor_name, select_stmt, CG_PREPARE|CG_MINIFY_ALIASES); } else if (is_unboxing) { // DECLARE [name] CURSOR FOR [named_box_object] CHARBUF_OPEN(box_name); bprintf(cg_main_output, "%s_stmt = cql_unbox_stmt(%s);\n", cursor_name, ast->right->sem->name); bprintf(&box_name, "%s_object_", cursor_name); cg_copy(cg_main_output, box_name.ptr, SEM_TYPE_OBJECT, ast->right->sem->name); CHARBUF_CLOSE(box_name); } else { // DECLARE [name] CURSOR FOR [call_stmt]] if (is_boxed) { // The next prepare will finalize the statement, we don't want to do that // if the cursor is being handled by boxes. The box downcount will take care of it bprintf(cg_main_output, "%s_stmt = NULL;\n", cursor_name); } EXTRACT_NOTNULL(call_stmt, ast->right); cg_call_stmt_with_cursor(call_stmt, cursor_name); } if (is_boxed) { // An object will control the lifetime of the cursor. If the cursor is boxed // this is the object reference that will be used. This way the exit path is // uniform regardless of whether or not the object was in fact boxed in the // control flow. This is saying that it might be boxed later so we use this // general mechanism for lifetime. The cg_var_decl helper handles cleanup too. CHARBUF_OPEN(box_name); bprintf(&box_name, "%s_object_", cursor_name); cg_var_decl(cg_declarations_output, SEM_TYPE_OBJECT, box_name.ptr, CG_VAR_DECL_LOCAL); // the unbox case gets the object from the unbox operation above, so skip if unboxing if (!is_unboxing) { // Note we have to clear the stashed box object and then accept the new box without // increasing the retain count on the new box because it starts with a +1 as usual. // This is a job for cg_copy_for_create! CHARBUF_OPEN(box_value); bprintf(&box_value, "cql_box_stmt(%s_stmt)", cursor_name); cg_copy_for_create(cg_main_output, box_name.ptr, SEM_TYPE_OBJECT, box_value.ptr); CHARBUF_CLOSE(box_value); } CHARBUF_CLOSE(box_name); } if (name_ast->sem->sem_type & SEM_TYPE_HAS_SHAPE_STORAGE) { cg_declare_auto_cursor(cursor_name, name_ast->sem); } else { // if it's a global cursor (`!in_proc`) we have to assume we will need the // variable eventually so we emit it now; if it's a local then we only emit // it if we know it'll be used later on (as indicated by `SEM_TYPE_FETCH_INTO`) if (!in_proc || name_ast->sem->sem_type & SEM_TYPE_FETCH_INTO) { // make the cursor_has_row hidden variable CHARBUF_OPEN(temp); bprintf(&temp, "_%s_has_row_", cursor_name); cg_var_decl(cg_declarations_output, SEM_TYPE_BOOL | SEM_TYPE_NOTNULL, temp.ptr, CG_VAR_DECL_LOCAL); CHARBUF_CLOSE(temp); } } } // This is the cursor boxing primitive, we'll make an object variable for this cursor here // Note since the cursor is boxed its lifetime is already controlled by an object associated // with the cursor. This happens as soon as the cursor is created, however it is created. // The codegen system knows that the cursor may be boxed at some point using the SEM_TYPE_BOXED flag static void cg_set_from_cursor(ast_node *ast) { Contract(is_ast_set_from_cursor(ast)); EXTRACT_ANY_NOTNULL(variable, ast->left); EXTRACT_ANY_NOTNULL(cursor, ast->right); EXTRACT_STRING(cursor_name, cursor); EXTRACT_STRING(var_name, variable); CHARBUF_OPEN(value); bprintf(&value, "%s_object_", cursor_name); CSTR prefix = ""; if (is_out_parameter(variable->sem->sem_type)) { prefix = "*"; } CHARBUF_OPEN(tmp_var_name); bprintf(&tmp_var_name, "%s%s", prefix, var_name); cg_copy(cg_main_output, tmp_var_name.ptr, SEM_TYPE_OBJECT, value.ptr); CHARBUF_CLOSE(tmp_var_name); CHARBUF_CLOSE(value); } static void cg_declare_cursor_like(ast_node *name_ast) { EXTRACT_STRING(cursor_name, name_ast); Contract(name_ast->sem->sem_type & SEM_TYPE_HAS_SHAPE_STORAGE); cg_declare_auto_cursor(cursor_name, name_ast->sem); } static void cg_declare_cursor_like_name(ast_node *ast) { Contract(is_ast_declare_cursor_like_name(ast)); Contract(ast->right); EXTRACT_ANY_NOTNULL(name_ast, ast->left); cg_declare_cursor_like(name_ast); } static void cg_declare_cursor_like_select(ast_node *ast) { Contract(is_ast_declare_cursor_like_select(ast)); Contract(is_select_stmt(ast->right)); EXTRACT_ANY_NOTNULL(name_ast, ast->left); cg_declare_cursor_like(name_ast); } // The value cursor form for sure will be fetched. We emit the necessary locals // for the cursor here. Those are one for "_has_row_" field and another for each // element of the structure the cursor holds. static void cg_declare_value_cursor(ast_node *ast) { Contract(is_ast_declare_value_cursor(ast)); EXTRACT_ANY_NOTNULL(name_ast, ast->left); EXTRACT_STRING(cursor_name, name_ast); EXTRACT_NOTNULL(call_stmt, ast->right); // DECLARE [name] CURSOR FETCH FROM [call_stmt]] cg_declare_auto_cursor(cursor_name, name_ast->sem); cg_call_stmt_with_cursor(call_stmt, cursor_name); } // Fetch values has been checked for the presence of all columns and seed values // have already been added if needed. All we have to generate evaluation of each value // and then a store. There is no "fetch values into name_list" form. static void cg_fetch_values_stmt(ast_node *ast) { Contract(is_ast_fetch_values_stmt(ast)); EXTRACT(insert_dummy_spec, ast->left); EXTRACT(name_columns_values, ast->right); EXTRACT_ANY_NOTNULL(cursor, name_columns_values->left) EXTRACT(columns_values, name_columns_values->right); EXTRACT_NOTNULL(column_spec, columns_values->left); EXTRACT(insert_list, columns_values->right); EXTRACT(name_list, column_spec->left); if (insert_dummy_spec) { cg_insert_dummy_spec(insert_dummy_spec); } // get the canonical name of the cursor (the string might be case-sensitively different) CSTR cursor_name = cursor->sem->name; // FETCH name [( name_list )] FROM VALUES (insert_list) [insert_dummy_spec] ast_node *value = insert_list; bprintf(cg_main_output, "%s._has_row_ = 1;\n", cursor_name); for (ast_node *item = name_list ; item; item = item->right, value = value->right) { EXTRACT_ANY_NOTNULL(expr, value->left); EXTRACT_ANY_NOTNULL(col, item->left); EXTRACT_STRING(var, col); CG_PUSH_EVAL(expr, C_EXPR_PRI_ROOT); CHARBUF_OPEN(temp); bprintf(&temp, "%s.%s", cursor_name, var); cg_store(cg_main_output, temp.ptr, col->sem->sem_type, expr->sem->sem_type, expr_is_null.ptr, expr_value.ptr); CHARBUF_CLOSE(temp); CG_POP_EVAL(expr); } } static void cg_fetch_cursor_from_blob_stmt(ast_node *ast) { Contract(is_ast_fetch_cursor_from_blob_stmt(ast)); CSTR cursor_name = ast->left->sem->name; EXTRACT_ANY_NOTNULL(blob, ast->right); Invariant(is_blob(blob->sem->sem_type)); CG_PUSH_EVAL(blob, C_EXPR_PRI_ROOT); bprintf(cg_main_output, "_rc_ = cql_deserialize_from_blob(%s, &%s, &%s._has_row_, %s_cols, %s_data_types);\n", blob_value.ptr, cursor_name, cursor_name, cursor_name, cursor_name); cg_error_on_rc_notequal("SQLITE_OK"); CG_POP_EVAL(blob); } static void cg_set_blob_from_cursor_stmt(ast_node *ast) { Contract(is_ast_set_blob_from_cursor_stmt(ast)); CSTR blob_name = ast->left->sem->name; CSTR cursor_name = ast->right->sem->name; CSTR prefix = is_out_parameter(ast->left->sem->sem_type) ? "" : "&"; bprintf(cg_main_output, "_rc_ = cql_serialize_to_blob(%s%s, &%s, %s._has_row_, %s_cols, %s_data_types);\n", prefix, blob_name, cursor_name, cursor_name, cursor_name, cursor_name); cg_error_on_rc_notequal("SQLITE_OK"); } // Fetch has already been rigorously checked so we don't have to worry about // argument counts or type mismatches in the codegen. We have two cases: // * Fetch into variables // * loop over the variables which must match with the columns (!) and // use the cg_get_column helpers to emit the code for a store // * Fetch into auto variables // * loop over the field names of the sem_struct that corresponds to the cursor // * set each local according to the automatically generated name as above // Note: cg_get_column does the error processing static void cg_fetch_stmt(ast_node *ast) { Contract(is_ast_fetch_stmt(ast)); EXTRACT_ANY_NOTNULL(cursor_ast, ast->left); EXTRACT(name_list, ast->right); // use the canonical name, not the AST name (case could be different) CSTR cursor_name = cursor_ast->sem->name; // FETCH [name] [INTO [name_list]] bool_t uses_out_union = !!(ast->sem->sem_type & SEM_TYPE_USES_OUT_UNION); CHARBUF_OPEN(row_test); if (uses_out_union) { bprintf(cg_main_output, "%s_row_num_++;\n", cursor_name); bprintf(&row_test, "%s_row_num_ < %s_row_count_", cursor_name, cursor_name); } else { bprintf(cg_main_output, "_rc_ = sqlite3_step(%s_stmt);\n", cursor_name); bprintf(&row_test, "_rc_ == SQLITE_ROW"); } if (ast->sem->sem_type & SEM_TYPE_HAS_SHAPE_STORAGE) { bprintf(cg_main_output, "%s._has_row_ = %s;\n", cursor_name, row_test.ptr); } else { bprintf(cg_main_output, "_%s_has_row_ = %s;\n", cursor_name, row_test.ptr); } CHARBUF_CLOSE(row_test); // if there is a row, then we need to read the row into the variables // there are two alternatives: reading into locals/args or reading into // auto-generated cursor variables. Either way we get each column. sem_struct *sptr = ast->left->sem->sptr; if (uses_out_union) { bool_t dml_proc = is_dml_proc(ast->sem->sem_type); CSTR db_sym = "NULL"; if (dml_proc) { // The db pointer passed to cql_copyoutrow(...) is used only to decode any // encoded columns. // We provide the db pointer only if the result_set to copy were created // by a DML proc. The idea being that if it wasn't a DML proc that made // the result set then it could not have encoded anything since it also, // did not have the db pointer to do the encoding. So our decode call will // match what the proc could have done. db_sym = "_db_"; } bprintf(cg_main_output, "cql_copyoutrow(%s, (cql_result_set_ref)%s_result_set_, %s_row_num_, %d", db_sym, cursor_name, cursor_name, sptr->count); } else { bprintf(cg_main_output, "cql_multifetch(_rc_, %s_stmt, %d", cursor_name, sptr->count); } CSTR newline = ",\n "; if (name_list) { int32_t i = 0; // column get is zero based for (ast_node *item = name_list; item; item = item->right, i++) { EXTRACT_ANY_NOTNULL(name_ast, item->left); EXTRACT_STRING(var, name_ast); sem_t sem_type_var = name_ast->sem->sem_type; bprintf(cg_main_output, "%s", newline); cg_fetch_column(sem_type_var, var); } } else { for (int32_t i = 0; i < sptr->count; i++) { CHARBUF_OPEN(temp); bprintf(&temp, "%s.%s", cursor_name, sptr->names[i]); bprintf(cg_main_output, "%s", newline); cg_fetch_column(sptr->semtypes[i], temp.ptr); CHARBUF_CLOSE(temp); } } bprintf(cg_main_output, ");\n"); if (!uses_out_union) { cg_error_on_expr("_rc_ != SQLITE_ROW && _rc_ != SQLITE_DONE"); } } static void cg_fetch_call_stmt(ast_node *ast) { Contract(is_ast_fetch_call_stmt(ast)); EXTRACT_STRING(cursor_name, ast->left); EXTRACT_ANY_NOTNULL(call_stmt, ast->right); cg_call_stmt_with_cursor(call_stmt, cursor_name); } // The update cursor statement differs from the more general fetch form in that // it is only to be used to tweak fields in an already loaded cursor. The sematics // are that if you try to "update" a cursor with no row the update is ignored. // The purpose of this is to let you edit one or two fields of a row as you fetch them // before using OUT or OUT UNION or INSERT ... FROM CURSOR. You want to do this // without having to restate all the columns, which besides being verbose makes it hard // for people to see what things you are changing and what you are not. static void cg_update_cursor_stmt(ast_node *ast) { Contract(is_ast_update_cursor_stmt(ast)); EXTRACT_ANY(cursor, ast->left); EXTRACT_STRING(name, cursor); EXTRACT_NOTNULL(columns_values, ast->right); EXTRACT_NOTNULL(column_spec, columns_values->left); EXTRACT_ANY_NOTNULL(name_list, column_spec->left); EXTRACT_ANY_NOTNULL(insert_list, columns_values->right); bprintf(cg_main_output, "if (%s._has_row_) {\n", name); CG_PUSH_MAIN_INDENT(stores, 2); ast_node *col = name_list; ast_node *val = insert_list; for ( ; col && val; col = col->right, val = val->right) { ast_node *expr = val->left; ast_node *name_ast = col->left; CG_PUSH_EVAL(expr, C_EXPR_PRI_ROOT); CHARBUF_OPEN(temp); bprintf(&temp, "%s.%s", name, name_ast->sem->name); cg_store(cg_main_output, temp.ptr, name_ast->sem->sem_type, expr->sem->sem_type, expr_is_null.ptr, expr_value.ptr); CHARBUF_CLOSE(temp); CG_POP_EVAL(expr); } CG_POP_MAIN_INDENT(stores); bprintf(cg_main_output, "}\n"); } // Here we just emit the various case labels for the expression list of // a WHEN clause. There isn't much to this. // * the correct indent level is already set up // * if the constants are 64 make sure we emit them as such // * we know evaluation will work because the semantic pass already checked it // * formatting numbers never fails static void cg_switch_expr_list(ast_node *ast, sem_t sem_type_switch_expr) { Contract(is_ast_expr_list(ast)); while (ast) { Contract(is_ast_expr_list(ast)); EXTRACT_ANY_NOTNULL(expr, ast->left); eval_node result = EVAL_NIL; eval(expr, &result); Invariant(result.sem_type != SEM_TYPE_ERROR); // already checked bool_t is_long = core_type_of(sem_type_switch_expr) == SEM_TYPE_LONG_INTEGER; bprintf(cg_main_output, "case "); if (is_long) { bprintf(cg_main_output, "_64("); } eval_format_number(&result, cg_main_output); if (is_long) { bprintf(cg_main_output, ")"); } bprintf(cg_main_output, ":\n"); ast = ast->right; } } // Switch actually generates pretty easily because of the constraints that were // placed on the various expressions. We know that the case lables are all // integers and we know that the expression type of the switch expression is // a not null integer type so we can easily generate the switch form. Anything // that could go wrong has already been checked. static void cg_switch_stmt(ast_node *ast) { Contract(is_ast_switch_stmt(ast)); EXTRACT_NOTNULL(switch_body, ast->right); EXTRACT_ANY_NOTNULL(expr, switch_body->left); EXTRACT_NOTNULL(switch_case, switch_body->right); // SWITCH [expr] [switch_body] END // SWITCH [expr] ALL VALUES [switch_body] END CG_PUSH_EVAL(expr, C_EXPR_PRI_ROOT); bprintf(cg_main_output, "switch (%s) {\n", expr_value.ptr); CG_POP_EVAL(expr); CG_PUSH_MAIN_INDENT(cases, 2); bool_t first_case = true; bool_t has_default = false; for (ast_node *temp = switch_case; temp; temp = temp->right) { EXTRACT_NOTNULL(connector, temp->left); if (!connector->left) { has_default = true; } } while (switch_case) { EXTRACT_NOTNULL(connector, switch_case->left); EXTRACT(stmt_list, connector->right); // no stmt list corresponds to WHEN ... THEN NOTHING // we can skip the entire case set unless there is a default // in which case we have to emit it with just break... if (stmt_list || has_default) { if (!first_case) { bprintf(cg_main_output, "\n"); // break between statement lists } first_case = false; // no expr list corresponds to the else case if (connector->left) { EXTRACT_NOTNULL(expr_list, connector->left); cg_switch_expr_list(expr_list, expr->sem->sem_type); } else { bprintf(cg_main_output, "default:\n"); } if (stmt_list) { cg_stmt_list(stmt_list); } bprintf(cg_main_output, " break;\n"); } switch_case = switch_case->right; } CG_POP_MAIN_INDENT(cases); bprintf(cg_main_output, "}\n", expr_value.ptr); } // "While" suffers from the same problem as IF and as a consequence // generating while (expression) would not generalize. // The overall pattern for while has to look like this: // // for (;;) { // prep statements; // condition = final expression; // if (!condition) break; // // statements; // } // // Note that while can have leave and continue substatements which have to map // to break and continue. That means other top level statements that aren't loops // must not create a C loop construct or break/continue would have the wrong target. static void cg_while_stmt(ast_node *ast) { Contract(is_ast_while_stmt(ast)); EXTRACT_ANY_NOTNULL(expr, ast->left); EXTRACT(stmt_list, ast->right); sem_t sem_type = expr->sem->sem_type; // WHILE [expr] BEGIN [stmt_list] END bprintf(cg_main_output, "for (;;) {\n"); CG_PUSH_EVAL(expr, C_EXPR_PRI_ROOT); if (is_nullable(sem_type)) { bprintf(cg_main_output, "if (!cql_is_nullable_true(%s, %s)) break;\n", expr_is_null.ptr, expr_value.ptr); } else { bprintf(cg_main_output, "if (!(%s)) break;\n", expr_value.ptr); } bool_t loop_saved = cg_in_loop; cg_in_loop = true; CG_POP_EVAL(expr); cg_stmt_list(stmt_list); bprintf(cg_main_output, "}\n"); cg_in_loop = loop_saved; } // The general pattern for this is very simple: // for (;;) { // do the fetch; // if (no rows) break; // do your loop; // } // It has to be this because the fetch might require many statements. // There are helpers for all of this so it's super simple. static void cg_loop_stmt(ast_node *ast) { Contract(is_ast_loop_stmt(ast)); EXTRACT_NOTNULL(fetch_stmt, ast->left); EXTRACT(stmt_list, ast->right); EXTRACT_ANY_NOTNULL(cursor_ast, fetch_stmt->left); // get the canonical name of the cursor (the name in the tree might be case-sensitively different) CSTR cursor_name = cursor_ast->sem->name; // LOOP [fetch_stmt] BEGIN [stmt_list] END bprintf(cg_main_output, "for (;;) {\n"); CG_PUSH_MAIN_INDENT(loop, 2); cg_fetch_stmt(fetch_stmt); if (fetch_stmt->left->sem->sem_type & SEM_TYPE_HAS_SHAPE_STORAGE) { bprintf(cg_main_output, "if (!%s._has_row_) break;\n", cursor_name); } else { // variable already emitted by the fetch statement above if needed bprintf(cg_main_output, "if (!_%s_has_row_) break;\n", cursor_name); } bool_t loop_saved = cg_in_loop; cg_in_loop = true; CG_POP_MAIN_INDENT(loop); cg_stmt_list(stmt_list); bprintf(cg_main_output, "}\n"); cg_in_loop = loop_saved; } // Only SQL loops are allowed to use C loops, so "continue" is perfect static void cg_continue_stmt(ast_node *ast) { Contract(is_ast_continue_stmt(ast)); // CONTINUE bprintf(cg_main_output, "continue;\n"); } // Only SQL loops are allowed to use C loops, so "break" is perfect static void cg_leave_stmt(ast_node *ast) { Contract(is_ast_leave_stmt(ast)); // LEAVE bprintf(cg_main_output, "break;\n"); } // Only SQL loops are allowed to use C loops, so "break" is perfect static void cg_return_stmt(ast_node *ast) { Contract(is_ast_return_stmt(ast) || is_ast_rollback_return_stmt(ast) || is_ast_commit_return_stmt(ast)); // RETURN bool_t dml_proc = is_dml_proc(current_proc->sem->sem_type); if (dml_proc) { bprintf(cg_main_output, "_rc_ = SQLITE_OK; // clean up any SQLITE_ROW value or other non-error\n"); } bprintf(cg_main_output, "goto %s; // return\n", CQL_CLEANUP_DEFAULT_LABEL); return_used = true; } // Rollback the current procedure savepoint, then perform a return. // Note that to rollback a savepoint you have to do the rollback AND the release // and then you're unwound to the savepoint state. The transaction in flight is // still in flight if there is one. static void cg_rollback_return_stmt(ast_node *ast) { Contract(is_ast_rollback_return_stmt(ast)); AST_REWRITE_INFO_SET(ast->lineno, ast->filename); ast_node *rollback = new_ast_rollback_trans_stmt(new_ast_str(current_proc_name())); ast_node *release = new_ast_release_savepoint_stmt(new_ast_str(current_proc_name())); AST_REWRITE_INFO_RESET(); cg_bound_sql_statement(NULL, rollback, CG_EXEC); cg_bound_sql_statement(NULL, release, CG_EXEC); cg_return_stmt(ast); } // Commits the current procedure savepoint, then perform a return. // Note savepoint semantics are just "release" is sort of like commit // in that it doesn't rollback and becomes part of the current transaction // which may or may not commit but that's what we mean by commit. static void cg_commit_return_stmt(ast_node *ast) { Contract(is_ast_commit_return_stmt(ast)); AST_REWRITE_INFO_SET(ast->lineno, ast->filename); ast_node *commit = new_ast_release_savepoint_stmt(new_ast_str(current_proc_name())); AST_REWRITE_INFO_RESET(); cg_bound_sql_statement(NULL, commit, CG_EXEC); cg_return_stmt(ast); } // Finalize the statement object associated with the cursor. // Note this sets the cursor to null, so you can do it again. Cleanup // might also do this. That's fine. static void cg_close_stmt(ast_node *ast) { Contract(is_ast_close_stmt(ast)); EXTRACT_ANY_NOTNULL(cursor_ast, ast->left); EXTRACT_STRING(name, cursor_ast); // CLOSE [name] sem_t sem_type = cursor_ast->sem->sem_type; if (!(sem_type & SEM_TYPE_VALUE_CURSOR)) { bprintf(cg_main_output, "cql_finalize_stmt(&%s_stmt);\n", name); } if (sem_type & SEM_TYPE_HAS_SHAPE_STORAGE) { sem_struct *sptr = cursor_ast->sem->sptr; int32_t refs_count = refs_count_sptr(sptr); if (refs_count) { bprintf(cg_main_output, "cql_teardown_row(%s);\n", name); } } } // The OUT statement copies the current value of a cursor into an implicit // OUT structure variable (_result_). The type of the variable is inferred // from the cursor you return. All OUT statements in any given proc must // agree on the exact type (this has already been verified). At this point // all we have to do is copy the fields. static void cg_out_stmt(ast_node *ast) { Contract(is_ast_out_stmt(ast)); // get the canonical name of the cursor (the name in the tree might be case-sensitively different) CSTR cursor_name = ast->left->sem->name; // OUT [cursor_name] // if there is a row, then we need to the values into the result CHARBUF_OPEN(var); CHARBUF_OPEN(value); sem_t sem_type_has_row = SEM_TYPE_BOOL | SEM_TYPE_NOTNULL; ast_node *proc_name_ast = get_proc_name(current_proc); EXTRACT_STRING(proc_name, proc_name_ast); // We can just blindly copy out the values because FETCH puts something // intelligent there if there is no row available (e.g. null strings) bprintf(&var, "_result_->%s", "_has_row_"); bprintf(&value, "%s._has_row_", cursor_name); cg_copy(cg_main_output, var.ptr, sem_type_has_row, value.ptr); CG_CHARBUF_OPEN_SYM(sym, proc_name, "_refs_offset"); sem_struct *sptr = ast->left->sem->sptr; int32_t refs_count = refs_count_sptr(sptr); if (refs_count) { // if no ref count it starts null and stays null bprintf(cg_main_output, "_result_->_refs_count_ = %d;\n", refs_count); bprintf(cg_main_output, "_result_->_refs_offset_ = %s;\n", sym.ptr); } for (int32_t i = 0; i < sptr->count; i++) { bclear(&var); bclear(&value); bprintf(&var, "_result_->%s", sptr->names[i]); bprintf(&value, "%s.%s", cursor_name, sptr->names[i]); cg_copy(cg_main_output, var.ptr, sptr->semtypes[i], value.ptr); } CHARBUF_CLOSE(sym); CHARBUF_CLOSE(value); CHARBUF_CLOSE(var); } static void cg_out_union_stmt(ast_node *ast) { Contract(is_ast_out_union_stmt(ast)); // get the canonical name of the cursor (the name in the tree might be case-sensitively different) CSTR cursor_name = ast->left->sem->name; // OUT UNION [cursor_name] bprintf(cg_main_output, "cql_retain_row(%s);\n", cursor_name); bprintf(cg_main_output, "if (%s._has_row_) ", cursor_name); bprintf(cg_main_output, "cql_bytebuf_append(&_rows_, (const void *)&%s, sizeof(%s));\n", cursor_name, cursor_name); } // emit the string literal into the otuput if the current runtime matches static void cg_echo_stmt(ast_node *ast) { Contract(is_ast_echo_stmt(ast)); EXTRACT_STRING(rt_name, ast->left); EXTRACT_STRING(str, ast->right); // @ECHO [rt], [str] if (!Strcasecmp(rt_name, options.rt)) { if (current_proc) { cg_decode_string_literal(str, cg_main_output); } else { cg_decode_string_literal(str, cg_declarations_output); } } } // This is the helper method to dispatch a call to an external function like "printf" // given a name in the AST. This is for when the user coded the call. static void cg_call_external(ast_node *ast) { Contract(is_ast_call_stmt(ast)); EXTRACT_ANY_NOTNULL(name_ast, ast->left); EXTRACT_STRING(name, name_ast); EXTRACT_ANY(arg_list, ast->right); return cg_call_named_external(name, arg_list); } // This is performs an external function call, normalizing strings and passing // the current value of nullables. It's all straight up value-calls. This form // is used when the name might not be in the AST, such as we need a call to // a sqlite helper method with user provided args. All we do here is emit // the name and then use the arg list helper. // The arg list helper gives us prep/invocation/cleanup buffers which we must emit. static void cg_call_named_external(CSTR name, ast_node *arg_list) { CHARBUF_OPEN(invocation); CHARBUF_OPEN(prep); CHARBUF_OPEN(cleanup); // Note this function is called in an expression context such as // for the builtin "printf" SQL function it can also be called in the call // statement context such as "call printf();" In the second case it's // top level and the stack doesn't matter as it will be reset but in the first // case we need to restore the temp stack after we are done with the args. int32_t stack_level_saved = stack_level; bprintf(&invocation, "%s(", name); cg_emit_external_arglist(arg_list, &prep, &invocation, &cleanup); bprintf(&invocation, ");\n"); bprintf(cg_main_output, "%s%s%s", prep.ptr, invocation.ptr, cleanup.ptr); stack_level = stack_level_saved; // put the scratch stack back CHARBUF_CLOSE(cleanup); CHARBUF_CLOSE(prep); CHARBUF_CLOSE(invocation); } // This is the hard work of doing the call actually happens. We have to: // * evaluate each argument in the arg list // * for string literals, don't make a string_ref, just emit the literal as a quoted string // it's going to a C style call anyway... // * for strings // * add a prep statement to convert the string to a const char * in a temporary // * include the temporary in the arg list // * add a cleanup statement to release the temporary // * for others, use the expression value // * burn the top of the stack, in case the result is stored in a temporary, // each arg gets a fresh top of stack so they don't clobber each others results. static void cg_emit_external_arglist(ast_node *arg_list, charbuf *prep, charbuf *invocation, charbuf *cleanup) { for (ast_node *item = arg_list; item; item = item->right) { EXTRACT_ANY(arg, item->left); sem_t sem_type_arg = arg->sem->sem_type; if (is_strlit(arg)) { // special case, don't make a string object for string literals that are going to // external methods like printf CHARBUF_OPEN(quoted); EXTRACT_STRING(literal, arg); cg_requote_literal(literal, &quoted); bprintf(invocation, "%s", quoted.ptr); CHARBUF_CLOSE(quoted); } else { CG_PUSH_EVAL(arg, C_EXPR_PRI_ROOT); if (is_text(sem_type_arg)) { // external/unknown proc, convert to cstr first temp_cstr_count++; bprintf(prep, "cql_alloc_cstr(_cstr_%d, %s);\n", temp_cstr_count, arg_value.ptr); bprintf(invocation, "_cstr_%d", temp_cstr_count); bprintf(cleanup, "cql_free_cstr(_cstr_%d, %s);\n", temp_cstr_count, arg_value.ptr); } else { bprintf(invocation, "%s", arg_value.ptr); } CG_POP_EVAL(arg); } if (item->right) { bprintf(invocation, ", "); } } } // We have to release any valid object we have before we call an out function // or we will leak a reference. static void cg_release_out_arg_before_call(sem_t sem_type_arg, sem_t sem_type_param, CSTR name) { if (is_ref_type(sem_type_arg)) { if (!is_in_parameter(sem_type_param)) { cg_store(cg_main_output, name, sem_type_arg, sem_type_arg, "1", "NULL"); } } } // When performing a call there are several things we might need to do to the arguments // in order to get the correct calling convention. // * strings are already references, they go as is. // * not-nullables can go as is, unless // * if the paramater is not nullable and the argument is compatible but not an exact match, // then we box the argument into a temporary not nullable and pass that through // * finally, both the paramater and the argument was not nullable then we have to recover // the variable name from the evaluated value. static void cg_emit_one_arg(ast_node *arg, sem_t sem_type_param, sem_t sem_type_arg, charbuf *invocation) { CG_PUSH_EVAL(arg, C_EXPR_PRI_ROOT); do { // check for special case of out parameters if (is_out_parameter(sem_type_param)) { Contract(is_variable(sem_type_arg)); // previously checked (semantic pass) if (is_out_parameter(sem_type_arg)) { bprintf(invocation, "%s", arg->sem->name); } else { bprintf(invocation, "&%s", arg->sem->name); } cg_release_out_arg_before_call(sem_type_arg, sem_type_param, arg->sem->name); break; } if (is_ref_type(sem_type_arg)) { // normal case, pass the reference bprintf(invocation, "%s", arg_value.ptr); break; } bool_t must_box_arg = 0; if (is_nullable(sem_type_param)) { must_box_arg |= is_ast_null(arg); must_box_arg |= is_not_nullable(sem_type_arg); must_box_arg |= core_type_of(sem_type_arg) != core_type_of(sem_type_param); } if (must_box_arg) { // we have to pass a nullable of the exact type, box to that. CG_PUSH_TEMP(box_var, sem_type_param); cg_store(cg_main_output, box_var.ptr, sem_type_param, sem_type_arg, arg_is_null.ptr, arg_value.ptr); bprintf(invocation, "%s", box_var.ptr); CG_POP_TEMP(box_var); // burn the stack slot for the temporary, it can't be re-used during the call stack_level++; break; } if (is_nullable(sem_type_param)) { // This is the unfortunate case where we have split out temporary or variable that // the "not nullable" that we need into two parts and we need them back together // for the call... so we just strip off the .value from the value we were given. const int32_t dot_value_length = 6; // .value is 6 characters. // if it was not null we'd be boxing, above. Invariant(is_nullable(sem_type_arg)); // if it was a null literal, we'd be boxing Invariant(!is_null_type(sem_type_arg)); // it's bigger than .value Invariant(arg_value.used > dot_value_length); // it ends in .value Invariant(!strcmp(".value", arg_value.ptr + arg_value.used - dot_value_length - 1)); arg_value.used -= dot_value_length; arg_value.ptr[arg_value.used - 1] = 0; } // either way arg_value is now correct bprintf(invocation, "%s", arg_value.ptr); } while (0); CG_POP_EVAL(arg); } // This generates the invocation for a user defined external function. // Basically we do a simple C invoke with the matching argument types which are known exactly // we do the usual argument conversions using cg_emit_one_arg just like when calling procedures // however we capture the return type in a temporary variable created exactly for this purpose. // Note we do this without using the usual macros because those would invoke the function twice: // one time for is_null and one time for _value which is a no-no. So here we emit a direct // assignment much like we would in cg_store. Except we don't have to do all the cg_store things // because we know the target is a perfectly matching local variable we just created. static void cg_user_func(ast_node *ast, charbuf *is_null, charbuf *value) { Contract(is_ast_call(ast)); EXTRACT_ANY_NOTNULL(name_ast, ast->left); EXTRACT_STRING(name, name_ast); EXTRACT_NOTNULL(call_arg_list, ast->right); EXTRACT(arg_list, call_arg_list->right); ast_node *params = NULL; ast_node *func_stmt = find_func(name); CSTR func_name = NULL; bool_t proc_as_func = 0; bool_t dml_proc = 0; if (func_stmt) { EXTRACT_STRING(fname, func_stmt->left); params = get_func_params(func_stmt); func_name = fname; } else { // has to be one of these two, already validated ast_node *proc_stmt = find_proc(name); Invariant(proc_stmt); params = get_proc_params(proc_stmt); ast_node *proc_name_ast = get_proc_name(proc_stmt); EXTRACT_STRING(pname, proc_name_ast); func_name = pname; proc_as_func = 1; dml_proc = is_dml_proc(proc_stmt->sem->sem_type); } sem_t sem_type_result = ast->sem->sem_type; // The answer will be stored in this scratch variable, any type is possible CG_SETUP_RESULT_VAR(ast, sem_type_result); CHARBUF_OPEN(invocation); CG_CHARBUF_OPEN_SYM(func_sym, func_name); if (dml_proc) { // at least one arg for the out arg so add _db_ with comma bprintf(&invocation, "_rc_ = %s(_db_, ", func_sym.ptr); } else { bprintf(&invocation, "%s(", func_sym.ptr); } ast_node *item; for (item = arg_list; item; item = item->right, params = params->right) { EXTRACT_ANY(arg, item->left); sem_t sem_type_arg = arg->sem->sem_type; EXTRACT_NOTNULL(param, params->left); sem_t sem_type_param = param->sem->sem_type; cg_emit_one_arg(arg, sem_type_param, sem_type_arg, &invocation); // if any more items in the list or the out arg still pending, we need comma if (item->right || proc_as_func) { bprintf(&invocation, ", "); } } if (!item && params) { // The only way this happens is when calling a stored proc like a function // using the last arg as the return type. Invariant(proc_as_func); Invariant(!params->right); EXTRACT_NOTNULL(param, params->left); sem_t param_type = param->sem->sem_type; Invariant(is_out_parameter(param_type)); Invariant(!is_in_parameter(param_type)); // the result variable is not an in/out arg, it's just a regular local // it's otherwise the same as the paramater by consruction sem_t arg_type = param_type & sem_not(SEM_TYPE_OUT_PARAMETER|SEM_TYPE_IN_PARAMETER); cg_release_out_arg_before_call(arg_type, param_type, result_var.ptr); bprintf(&invocation, "&%s", result_var.ptr); } bprintf(&invocation, ")"); // Now store the result of the call. // the only trick here is we have to make sure we honor create semantics // otherwise we can just copy the data since the variable is for sure // an exact match for the call return by construction. if (proc_as_func) { // just do the function call, the result variable assignment happens as part of the call bprintf(cg_main_output, "%s;\n", invocation.ptr); if (dml_proc) { // cascade the failure cg_error_on_not_sqlite_ok(); } } else if (is_create_func(func_stmt->sem->sem_type)) { cg_copy_for_create(cg_main_output, result_var.ptr, func_stmt->sem->sem_type, invocation.ptr); } else { cg_copy(cg_main_output, result_var.ptr, func_stmt->sem->sem_type, invocation.ptr); } CHARBUF_CLOSE(func_sym); CHARBUF_CLOSE(invocation); CG_CLEANUP_RESULT_VAR(); // this will restore the scratch stack for us } // Forward the call processing to the general helper (with cursor arg) static void cg_call_stmt(ast_node *ast) { // If the call has a result set it is stored in our result parameter // just like a loose select statement would be. Note this can be // overridden by a later result which is totally ok. Same as for select // statements. return cg_call_stmt_with_cursor(ast, NULL); } // emit the declarations for anything implicitly declared then do a normal call static void cg_declare_out_call_stmt(ast_node *ast) { Contract(is_ast_declare_out_call_stmt(ast)); EXTRACT_NOTNULL(call_stmt, ast->left); EXTRACT(expr_list, call_stmt->right); for (; expr_list; expr_list = expr_list->right) { EXTRACT_ANY_NOTNULL(arg, expr_list->left); if (arg->sem->sem_type & SEM_TYPE_IMPLICIT) { EXTRACT_STRING(var_name, arg); cg_declare_simple_var(arg->sem->sem_type, var_name); } } cg_call_stmt(call_stmt); } // This helper method walks all the args and all the formal paramaters at the same time // it gets the appropriate type info for each and then generates the expression // for the evaluation of that argument. static void cg_emit_proc_params(charbuf *output, ast_node *params, ast_node *args) { for (ast_node *item = args; item; item = item->right, params = params->right) { EXTRACT_ANY_NOTNULL(arg, item->left); sem_t sem_type_arg = arg->sem->sem_type; EXTRACT_NOTNULL(param, params->left); sem_t sem_type_param = param->sem->sem_type; // note this might require type conversion, handled here. cg_emit_one_arg(arg, sem_type_param, sem_type_arg, output); if (item->right) { bprintf(output, ", "); } } } // A call statement has several varieties: // * an external call to an unknown proc // * use the external call helper // * if the target is a dml proc // * add the _db_ argument, for sure we have it because if we call a DML proc // we are a DML proc so we, too, had such an arg. Pass it along. // * capture the _rc_ return code and do the error processing. // * if the proc returns a relational result (see below) we use the given // cursor to capture it, or else we use the functions result argument // as indicated below // // There are a variety of call forms (we'll see the symmetric version of this // in cg_create_proc_stmt). The first thing to consider is, does the procedure // produce some kind of relational result, there are four ways it can do this: // // 1. It returns a statement (it used a loose SELECT) // 2. It returns a single row (it used OUT) // 3. It returns a result set (it used OUT UNION) // 4. It returns no relational result, just out args maybe. // // Now we have to consider this particular call, and the chief question is // are we capturing the relational result in a cursor? If we are then referring // to the above: // // 1a. The cursor will be a statement cursor, holding the SQLite statement // 2a. The cursor will hold the row, it is a value cursor (you can't step it) // 3a. The cursor will hold a pointer to the result set which can be indexed // 4a. A cursor cannot be used if there is no relational result. // // Note that the error case above has already been detected in semantic analysis // so we would not be here if it happened. This is true of the other error cases // as well. If we're doing code-gen we know we're good. // // If the result is not captured in a cursor then we have the following outcomes // // 1b. The current procedure returns statement as a relational result // (just as though it had done the select) // 2b. This is not allowed, the row must be captured by a cursor (error). // 3b. The current procedure returns the result set (just as though it had done // the OUT UNION) // 4b. This is a "normal" function call with just normal arguments // // Compounding the above, the procedure might use the database or not. If it uses // the database (dml_proc) we have to add that argument and we expect a success code. // If it doesn't use the database it can still return a relational result with // OUT or OUT UNION. It can't have done a SELECT (no database) or could it have // called a procedure that did a SELECT (again, no database). So the statement // cursor case is eliminated. This creates a fairly complex matrix but most of the // logic is highly similar. // // In call cases we can use the arg helper method to emit each arg. There are // several rules for each kind of arg, described above in cg_emit_one_arg. static void cg_call_stmt_with_cursor(ast_node *ast, CSTR cursor_name) { Contract(is_ast_call_stmt(ast)); EXTRACT_ANY_NOTNULL(name_ast, ast->left); EXTRACT_STRING(name, name_ast); EXTRACT_ANY(expr_list, ast->right); // check for call to unknown proc, use canonical calling convention for those ast_node *proc_stmt = find_proc(name); if (!proc_stmt) { cg_call_external(ast); return; } ast_node *proc_name_ast = get_proc_name(proc_stmt); EXTRACT_STRING(proc_name, proc_name_ast); ast_node *params = get_proc_params(proc_stmt); bool_t dml_proc = is_dml_proc(proc_stmt->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); CSTR fetch_results = out_union_proc ? "_fetch_results" : ""; CHARBUF_OPEN(invocation); CG_CHARBUF_OPEN_SYM(proc_sym, proc_name, fetch_results); CG_CHARBUF_OPEN_SYM(result_type, proc_name, "_row"); CG_CHARBUF_OPEN_SYM(result_sym, proc_name, "_row", "_data"); CG_CHARBUF_OPEN_SYM(result_set_ref, name, "_result_set_ref"); // most cases will emit hidden arg, such as the db bool_t prefix_args = true; if (dml_proc) { bprintf(&invocation, "_rc_ = %s(_db_", proc_sym.ptr); if (out_union_proc && !cursor_name) { // This is case 3b above. The tricky bit here is that there might // be more than one such call. The callee is not going to release // the out arg as it might be junk from the callee's perspective so // we have to release it in case this call is in a loop or if this // call is repeated in some other way bprintf(cg_main_output, "cql_object_release(*_result_set_);\n"); bprintf(&invocation, ", (%s *)_result_set_", result_set_ref.ptr); } else if (out_union_proc) { // this is case 3a above. Invariant(cursor_name); // either specified or the default _result_ variable bprintf(&invocation, ", &%s_result_set_", cursor_name); } else if (result_set_proc && cursor_name == NULL) { // This is case 1b above, prop the result as our output. As with case // 3b above we have to pre-release _result_stmt_ because of repetition. bprintf(cg_main_output, "cql_finalize_stmt(_result_stmt);\n"); bprintf(&invocation, ", _result_stmt"); } else if (result_set_proc) { // this is case 1a above Invariant(cursor_name); // either specified or the default _result_ variable bprintf(&invocation, ", &%s_stmt", cursor_name); } } else { bprintf(&invocation, "%s(", proc_sym.ptr); if (out_union_proc && !cursor_name) { // this is 3b again, but with no database arg. As with case // 3b above we have to pre-release _result_stmt_ because of repetition. bprintf(cg_main_output, "cql_object_release(*_result_set_);\n"); bprintf(&invocation, "(%s *)_result_set_", result_set_ref.ptr); } else if (out_union_proc) { // this is 3a again, but with no database arg Invariant(cursor_name); // either specified or the default _result_ variable bprintf(&invocation, "&%s_result_set_", cursor_name); } else { // no prefix args were emitted (case 4b, with no DML) prefix_args = false; } } // if we emitted something (most cases) and there are args, we need a comma now if (prefix_args && expr_list) { bprintf(&invocation, ", "); } // we don't need to manage the stack, we're always called at the top level // we're wiping it when we exit this function anyway Invariant(stack_level == 0); // emit provided args, the param specs are needed for possible type conversions cg_emit_proc_params(&invocation, params, expr_list); // For a fetch results proc we have to add the out argument here. // Declare that variable if needed. if (out_stmt_proc) { // this is case 2a above Invariant(cursor_name); // this would be 2b, not allowed(!) if (dml_proc || params) { bprintf(&invocation, ", "); } bprintf(cg_main_output, "cql_teardown_row(%s);\n", cursor_name); bprintf(&invocation, "(%s *)&%s", result_type.ptr, cursor_name); bprintf(&invocation, "); // %s identical to cursor type\n", result_type.ptr); } else { bprintf(&invocation, ");\n"); } bprintf(cg_main_output, "%s", invocation.ptr); if (out_union_proc && cursor_name) { // case 3a, capturing the cursor, we set the row index to -1 (it will be pre-incremented) bprintf(cg_main_output, "%s_row_num_ = %s_row_count_ = -1;\n", cursor_name, cursor_name); } if (dml_proc) { // if there is an error code, check it, and cascade the failure cg_error_on_not_sqlite_ok(); } if (out_union_proc && cursor_name) { // case 3a again bprintf(cg_main_output, "%s_row_count_ = cql_result_set_get_count((cql_result_set_ref)%s_result_set_);\n", cursor_name, cursor_name); } CHARBUF_CLOSE(result_set_ref); CHARBUF_CLOSE(result_sym); CHARBUF_CLOSE(result_type); CHARBUF_CLOSE(proc_sym); CHARBUF_CLOSE(invocation); } // Straight up DDL invocation. The ast has the statement, execute it! // We don't minify the aliases because DDL can have views and the view column names // can be referred to in users of the view. Loose select statements can have // no external references to column aliases. static void cg_any_ddl_stmt(ast_node *ast) { cg_bound_sql_statement(NULL, ast, CG_EXEC|CG_NO_MINIFY_ALIASES); } // Straight up DML invocation. The ast has the statement, execute it! static void cg_std_dml_exec_stmt(ast_node *ast) { cg_bound_sql_statement(NULL, ast, CG_EXEC|CG_MINIFY_ALIASES); } // DML with PREPARE. The ast has the statement. // Note: _result_ is the output variable for the sqlite3_stmt we generate // this was previously added when the stored proc params were generated. static void cg_select_stmt(ast_node *ast) { Contract(is_select_stmt(ast)); cg_bound_sql_statement("_result", ast, CG_PREPARE|CG_MINIFY_ALIASES); } // DML with PREPARE. The ast has the statement. static void cg_with_select_stmt(ast_node *ast) { Contract(is_ast_with_select_stmt(ast)); cg_select_stmt(ast); } static void cg_insert_dummy_spec(ast_node *ast) { EXTRACT_ANY_NOTNULL(expr, ast->left); // the seed expr CSTR name = "_seed_"; sem_t sem_type_var = SEM_TYPE_INTEGER | SEM_TYPE_NOTNULL; sem_t sem_type_expr = expr->sem->sem_type; if (!seed_declared) { cg_var_decl(cg_declarations_output, sem_type_var, name, CG_VAR_DECL_LOCAL); seed_declared = 1; } CG_PUSH_EVAL(expr, C_EXPR_PRI_ASSIGN); cg_store(cg_main_output, name, sem_type_var, sem_type_expr, expr_is_null.ptr, expr_value.ptr); CG_POP_EVAL(expr); } static void cg_opt_seed_process(ast_node *ast) { Contract(is_ast_insert_stmt(ast)); EXTRACT_ANY_NOTNULL(insert_type, ast->left); EXTRACT(insert_dummy_spec, insert_type->left); if (insert_dummy_spec) { cg_insert_dummy_spec(insert_dummy_spec); } } // DML invocation but first set the seed variable if present static void cg_insert_stmt(ast_node *ast) { Contract(is_ast_insert_stmt(ast)); cg_opt_seed_process(ast); cg_bound_sql_statement(NULL, ast, CG_EXEC | CG_NO_MINIFY_ALIASES); } // DML invocation but first set the seed variable if present static void cg_with_insert_stmt(ast_node *ast) { Contract(is_ast_with_insert_stmt(ast)); EXTRACT_NOTNULL(insert_stmt, ast->right); cg_opt_seed_process(insert_stmt); cg_bound_sql_statement(NULL, ast, CG_EXEC | CG_NO_MINIFY_ALIASES); } // DML invocation but first set the seed variable if present static void cg_with_upsert_stmt(ast_node *ast) { Contract(is_ast_with_upsert_stmt(ast)); EXTRACT_NOTNULL(upsert_stmt, ast->right); EXTRACT_NOTNULL(insert_stmt, upsert_stmt->left); cg_opt_seed_process(insert_stmt); cg_bound_sql_statement(NULL, ast, CG_EXEC | CG_NO_MINIFY_ALIASES); } // DML invocation but first set the seed variable if present static void cg_upsert_stmt(ast_node *ast) { Contract(is_ast_upsert_stmt(ast)); EXTRACT_NOTNULL(insert_stmt, ast->left); cg_opt_seed_process(insert_stmt); cg_bound_sql_statement(NULL, ast, CG_EXEC | CG_NO_MINIFY_ALIASES); } // Very little magic is needed to do try/catch in our context. The error // handlers for all the sqlite calls check _rc_ and if it's an error they // "goto" the current error target. That target is usually CQL_CLEANUP_DEFAULT_LABEL. // Inside the try block, the cleanup handler is changed to the catch block. // The catch block puts it back. Otherwise, generate nested statements as usual. static void cg_trycatch_helper(ast_node *try_list, ast_node *try_extras, ast_node *catch_list) { CHARBUF_OPEN(catch_start); CHARBUF_OPEN(catch_end); // We need unique labels for this block ++catch_block_count; bprintf(&catch_start, "catch_start_%d", catch_block_count); bprintf(&catch_end, "catch_end_%d", catch_block_count); // Divert the error target. CSTR saved_error_target = error_target; bool_t saved_error_target_used = error_target_used; error_target = catch_start.ptr; error_target_used = 0; // Emit the try code. bprintf(cg_main_output, "// try\n{\n"); cg_stmt_list(try_list); if (try_extras) { cg_stmt_list(try_extras); } // If we get to the end, skip the catch block. bprintf(cg_main_output, " goto %s;\n}\n", catch_end.ptr); // Emit the catch code, with labels at the start and the end. if (error_target_used) { bprintf(cg_main_output, "%s: ", catch_start.ptr); } // Restore the error target, the catch block runs with the old error target error_target = saved_error_target; error_target_used = saved_error_target_used; CSTR rcthrown_saved = rcthrown_current; bprintf(cg_main_output, "{\n"); CHARBUF_OPEN(rcthrown); bprintf(&rcthrown, "_rc_thrown_%d", ++rcthrown_index); rcthrown_current = rcthrown.ptr; bool_t rcthrown_used_saved = rcthrown_used; rcthrown_used = false; CHARBUF_OPEN(catch_block); charbuf *main_saved = cg_main_output; cg_main_output = &catch_block; cg_stmt_list(catch_list); cg_main_output = main_saved; if (rcthrown_used) { bprintf(cg_main_output, " int32_t %s = _rc_;\n", rcthrown.ptr); } bprintf(cg_main_output, "%s", catch_block.ptr); CHARBUF_CLOSE(catch_block); rcthrown_current = rcthrown_saved; rcthrown_used = rcthrown_used_saved; bprintf(cg_main_output, "}\n%s:;\n", catch_end.ptr); CHARBUF_CLOSE(rcthrown); CHARBUF_CLOSE(catch_end); CHARBUF_CLOSE(catch_start); } // the helper does all the work, see those notes static void cg_trycatch_stmt(ast_node *ast) { Contract(is_ast_trycatch_stmt(ast)); EXTRACT_NAMED(try_list, stmt_list, ast->left); EXTRACT_NAMED(catch_list, stmt_list, ast->right); cg_trycatch_helper(try_list, NULL, catch_list); } // this is just a special try/catch static void cg_proc_savepoint_stmt(ast_node *ast) { Contract(is_ast_proc_savepoint_stmt(ast)); EXTRACT(stmt_list, ast->left); if (stmt_list) { AST_REWRITE_INFO_SET(ast->lineno, ast->filename); ast_node *savepoint = new_ast_savepoint_stmt(new_ast_str(current_proc_name())); ast_node *release1 = new_ast_release_savepoint_stmt(new_ast_str(current_proc_name())); ast_node *release2 = new_ast_release_savepoint_stmt(new_ast_str(current_proc_name())); ast_node *rollback = new_ast_rollback_trans_stmt(new_ast_str(current_proc_name())); ast_node *try_extra_stmts = new_ast_stmt_list(release1, NULL); ast_node *throw_stmt = new_ast_throw_stmt(); ast_node *catch_stmts = new_ast_stmt_list(rollback, new_ast_stmt_list(release2, new_ast_stmt_list(throw_stmt, NULL))); AST_REWRITE_INFO_RESET(); cg_bound_sql_statement(NULL, savepoint, CG_EXEC); cg_trycatch_helper(stmt_list, try_extra_stmts, catch_stmts); } } // Convert _rc_ into an error code. If it already is one keep it. // Then go to the current error target. static void cg_throw_stmt(ast_node *ast) { Contract(is_ast_throw_stmt(ast)); bprintf(cg_main_output, "_rc_ = cql_best_error(%s);\n", rcthrown_current); bprintf(cg_main_output, "goto %s;\n", error_target); error_target_used = 1; rcthrown_used = 1; } // Dispatch to one of the statement helpers using the symbol table. // There are special rules for the DDL methods. If they appear in a // global context (outside of any stored proc) they do not run, they // are considered declarations only. static void cg_one_stmt(ast_node *stmt, ast_node *misc_attrs) { // we're going to compute the fragment name if needed but we always start clean base_fragment_name = NULL; // reset the temp stack stack_level = 0; symtab_entry *entry = symtab_find(cg_stmts, stmt->type); Contract(entry); if (!in_proc) { // DDL operations not in a procedure are ignored // but they can declare schema during the semantic pass if (entry->val == cg_any_ddl_stmt) { return; } // loose select statements also have no codegen, the global proc has no result type if (is_select_stmt(stmt)) { return; } } // don't emit a # line directive for the echo statement because it will messed up // if the echo doesn't end in a linefeed and that's legal. And there is normally // no visible code for these things anyway. if (!is_ast_echo_stmt(stmt)) { charbuf *line_out = (stmt_nesting_level == 1) ? cg_declarations_output : cg_main_output; cg_line_directive_min(stmt, line_out); } CHARBUF_OPEN(tmp_header); CHARBUF_OPEN(tmp_declarations); CHARBUF_OPEN(tmp_main); CHARBUF_OPEN(tmp_scratch); charbuf *header_saved = cg_header_output; charbuf *declarations_saved = cg_declarations_output; charbuf *main_saved = cg_main_output; charbuf *scratch_saved = cg_scratch_vars_output; // Redirect all output to the temporary buffers so we can see how big it is // The comments need to go before this, so we save the output then check it // then emit the generated code. cg_main_output = &tmp_main; cg_declarations_output = &tmp_declarations; cg_header_output = &tmp_header; cg_scratch_vars_output = &tmp_scratch; // These are all the statements there are, we have to find it in this table // or else someone added a new statement and it isn't supported yet. Invariant(entry); ((void (*)(ast_node*))entry->val)(stmt); // safe to put it back now cg_main_output = main_saved; cg_header_output = header_saved; cg_declarations_output = declarations_saved; cg_scratch_vars_output = scratch_saved; // Emit a helpful comment for top level statements. if (stmt_nesting_level == 1) { charbuf *out = cg_main_output; if (is_ast_declare_vars_type(stmt) || is_proc(stmt) || is_ast_echo_stmt(stmt)) { out = cg_declarations_output; } bool_t skip_comment = false; // don't contaminate echo output with comments except in test, where we need it for verification skip_comment |= (!options.test && is_ast_echo_stmt(stmt)); // If no code gen in the main buffer, don't add a comment, that will force a global proc // We used to have all kinds of special cases to detect the statements that don't generate code // and that was a bug farm. So now instead we just look to see if it made code. If it didn't make // code we will not force the global proc to exist because of the stupid comment... skip_comment |= (out == cg_main_output && tmp_main.used == 1); // 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", stmt->lineno); } // emit comments for most statements: we do not want to require the global proc block // just because there was a comment so this is suppressed for "no code" things if (!skip_comment) { if (options.test) { bprintf(out, "\n// The statement ending at line %d\n", stmt->lineno); } else { bprintf(cg_header_output, "\n// Generated from %s:%d\n", stmt->filename, stmt->lineno); bprintf(cg_declarations_output, "\n// Generated from %s:%d\n", stmt->filename, stmt->lineno); } // emit source comment bprintf(out, "\n/*\n"); CHARBUF_OPEN(tmp); gen_stmt_level = 1; gen_set_output_buffer(&tmp); if (misc_attrs) { gen_misc_attrs(misc_attrs); } gen_one_stmt(stmt); cg_remove_slash_star_and_star_slash(&tmp); // internal "*/" is fatal. "/*" can also be under certain compilation flags bprintf(out, "%s", tmp.ptr); CHARBUF_CLOSE(tmp); bprintf(out, ";\n*/\n"); } } // and finally write what we saved bprintf(cg_main_output, "%s", tmp_main.ptr); bprintf(cg_header_output, "%s", tmp_header.ptr); bprintf(cg_scratch_vars_output, "%s", tmp_scratch.ptr); bprintf(cg_declarations_output, "%s", tmp_declarations.ptr); CHARBUF_CLOSE(tmp_scratch); CHARBUF_CLOSE(tmp_main); CHARBUF_CLOSE(tmp_declarations); CHARBUF_CLOSE(tmp_header); } // Emit the nested statements with one more level of indenting. static void cg_stmt_list(ast_node *head) { if (!head) { return; } stmt_nesting_level++; charbuf *saved_main = cg_main_output; CHARBUF_OPEN(temp); cg_main_output = &temp; // Check to see if the block starts with a sequence of DDL/DML, if it does we can execute // it in one go; this saves us a lot of error checking code at the expense of less precise // error tracing. if (in_proc && options.compress) { ast_node *ast = head; ast_node *prev = head; for (; ast; ast = ast->right) { EXTRACT_STMT_AND_MISC_ATTRS(stmt, misc_attrs, ast); symtab_entry *entry = symtab_find(cg_stmts, stmt->type); Contract(entry); if (entry->val != cg_any_ddl_stmt && entry->val != cg_std_dml_exec_stmt) { break; } prev = ast; } // try to run the first statements of the statement list in bulk // but only do this if we found at least 2 statements that match if (ast != head && ast != head->right) { ast_node *new_head = ast; // temporarily nix the tail of the statement list prev->right = NULL; // we can't do this if any of the statements require variable binding if (cg_verify_unbound_stmt(head)) { // unbound batch, we can do this cg_bound_sql_statement(NULL, head, CG_EXEC|CG_NO_MINIFY_ALIASES); // now skip this batch, we already emitted them all in one go head = new_head; } // repair the statement list back to normal prev->right = new_head; } } for (ast_node *ast = head; ast; ast = ast->right) { EXTRACT_STMT_AND_MISC_ATTRS(stmt, misc_attrs, ast); cg_one_stmt(stmt, misc_attrs); } cg_main_output = saved_main; bindent(cg_main_output, &temp, 2); CHARBUF_CLOSE(temp); stmt_nesting_level--; } // Here we generate the type constant for the column // The type must be one of the types that we can fetch // from SQLite so that means not a struct, not the null type // (the null type is reserved for the literal NULL, it's // not a storage class) and certainly nothing that is // a struct or sentinel type. Likewise objects cannot // be the result of a select, so that's out too. static void cg_data_type(charbuf *buffer, bool_t encode, sem_t sem_type) { Contract(is_unitary(sem_type)); Contract(!is_null_type(sem_type)); sem_t core_type = core_type_of(sem_type); switch (core_type) { case SEM_TYPE_INTEGER: bprintf(buffer, "CQL_DATA_TYPE_INT32"); break; case SEM_TYPE_LONG_INTEGER: bprintf(buffer, "CQL_DATA_TYPE_INT64"); break; case SEM_TYPE_REAL: bprintf(buffer, "CQL_DATA_TYPE_DOUBLE"); break; case SEM_TYPE_BOOL: bprintf(buffer, "CQL_DATA_TYPE_BOOL"); break; case SEM_TYPE_TEXT: bprintf(buffer, "CQL_DATA_TYPE_STRING"); break; case SEM_TYPE_BLOB: bprintf(buffer, "CQL_DATA_TYPE_BLOB"); break; case SEM_TYPE_OBJECT: bprintf(buffer, "CQL_DATA_TYPE_OBJECT"); break; } if (is_not_nullable(sem_type)) { bprintf(buffer, " | CQL_DATA_TYPE_NOT_NULL"); } if (encode) { bprintf(buffer, " | CQL_DATA_TYPE_ENCODED"); } } // All the data you need to make a getter or setter... // there's a lot of it and most of it is the same for all cases typedef struct function_info { CSTR name; CSTR col; int32_t col_index; charbuf *defs; charbuf *headers; bool_t uses_out; sem_t ret_type; sem_t name_type; CSTR result_set_ref_type; CSTR row_struct_type; CSTR sym_suffix; CSTR value_suffix; uint32_t frag_type; } function_info; // Using the information above we emit a column getter. The essence of this // is to reach into the data field of the result set and index the requested row // then fetch the column. There's two parts to this: // * First we need to compute the name of the getter, it's fairly simple coming // from the name of the procedure that had the select and the field name that we // are getting. // * Second we emit the body of the getter there's a few cases here // * for fragments, we don't do our own getting, the master assembled query knows // all the pieces so we delegate to it; but we need to emit a declaration for // the getter in the master query // * for normal rowsets it's foo->data[i].column // * for single row result sets it's just foo->data->column; there is only the one row static void cg_proc_result_set_getter(function_info *info) { charbuf *h = info->headers; charbuf *d = info->defs; sem_t sem_type = info->ret_type; Invariant(info->frag_type != FRAG_TYPE_EXTENSION); CG_CHARBUF_OPEN_SYM_WITH_PREFIX( col_getter_sym, "", info->name, "_get_", info->col, info->sym_suffix); CHARBUF_OPEN(func_decl); cg_var_decl(&func_decl, sem_type, col_getter_sym.ptr, CG_VAR_DECL_PROTO); bprintf(&func_decl, "(%s _Nonnull result_set", info->result_set_ref_type); // a procedure that uses OUT gives exactly one row, so no index in the API if (!info->uses_out) { bprintf(&func_decl, ", %s row", rt->cql_int32); } bprintf(&func_decl, ")"); bprintf(h, "%s%s;\n", rt->symbol_visibility, func_decl.ptr); // base fragment will not generate any function bodies, this is left to the actual assembly fragment // all we're doing here is generating the .h file so that you could call the getters if (info->frag_type == FRAG_TYPE_BASE) { goto cleanup; } bprintf(d, "\n%s {\n", func_decl.ptr); // Note that the special handling of assembly fragments is not needed for the non-inline-getters case // because in that case all the getters are emitted into the defs section anyway. bprintf(d, " %s *data = (%s *)%s((cql_result_set_ref)result_set);\n", info->row_struct_type, info->row_struct_type, rt->cql_result_set_get_data); // Single row result set is always data[0] // And data->field looks nicer than data[0].field if (info->uses_out) { bprintf(d, " return data->%s%s;\n", info->col, info->value_suffix ? info->value_suffix : ""); } else { bprintf(d, " return data[row].%s%s;\n", info->col, info->value_suffix ? info->value_suffix : ""); } bprintf(d, "}\n"); cleanup: CHARBUF_CLOSE(func_decl); CHARBUF_CLOSE(col_getter_sym); } // The inlineable version of the getter can be generated instead of the opened coded version as above // This type inlines well because it uses a small number of standard helpers to do the fetching. // The situation is not so different from the original open coded case above. // This code mirrors cg_proc_result_set_getter // * First we need to compute the name of the getter, it's fairly simple coming // from the name of the procedure that had the select and the field name that we // are getting. // * Second we emit the body of the getter there's a few cases here // * for fragments, we don't do our own getting, the master assembled query knows // all the pieces so we delegate to it; but we need to emit a declaration for // the getter in the master query // * for normal rowsets it's something like cql_result_set_get_bool(result_set, row, column) // * for single row result sets it's just cql_result_set_get_bool(result_set, 0, column) static void cg_proc_result_set_type_based_getter(function_info *_Nonnull info) { charbuf *h = info->headers; charbuf *d = info->defs; charbuf *out = NULL; Invariant(info->frag_type != FRAG_TYPE_EXTENSION); CG_CHARBUF_OPEN_SYM_WITH_PREFIX( col_getter_sym, rt->symbol_prefix, info->name, "_get_", info->col, info->sym_suffix); CHARBUF_OPEN(func_decl); cg_var_decl(&func_decl, info->ret_type, col_getter_sym.ptr, CG_VAR_DECL_PROTO); bprintf(&func_decl, "(%s _Nonnull result_set", info->result_set_ref_type); // a procedure that uses OUT gives exactly one row, so no index in the API if (!info->uses_out) { bprintf(&func_decl, ", %s row", rt->cql_int32); } bprintf(&func_decl, ")"); CSTR row = info->uses_out ? "0" : "row"; // not set yet Invariant(!out); if (info->frag_type == FRAG_TYPE_ASSEMBLY || info->frag_type == FRAG_TYPE_BASE) { // In the assembly or base fragment case we emit only the prototype into the header file // The body goes into the main section. This is necessary because the extension fragments could be in other // linkage units and they will try to call these getters. So the linkage has to allow the extension getters // to have access to the authoritative getters. In the base case we're merely foreshadowing the actual // definitions that will be created by the assembly but limited to the base columns. // The body of the function will be emitted into the defs section (d). bprintf(h, "\n%s%s;", rt->symbol_visibility, func_decl.ptr); // in the base fragment case we only emit the prototype that the assembly will generate // and no body... we're done at this point if (info->frag_type == FRAG_TYPE_BASE) { goto cleanup; } bprintf(d, "\n%s%s {\n", rt->symbol_visibility, func_decl.ptr); out = d; } else { // The inline body will all go into the header file in the normal case. // Note it's ok for these to be static inline because they have a different name // (they include the extension frag) so they don't conflict with the base/assembly frag names // These guys just forward on to the assembly fragment bprintf(h, "\nstatic inline %s {\n", func_decl.ptr); out = h; } // definitely set now Invariant(out); bprintf(out, " return "); if (is_ref_type(info->name_type) && is_nullable(info->ret_type)) { bprintf(out, "%s((cql_result_set_ref)result_set, %s, %d) ? NULL : ", rt->cql_result_set_get_is_null, row, info->col_index); } switch (info->name_type) { case SEM_TYPE_NULL: bprintf(out, "%s", rt->cql_result_set_get_is_null); break; case SEM_TYPE_BOOL: bprintf(out, "%s", rt->cql_result_set_get_bool); break; case SEM_TYPE_REAL: bprintf(out, "%s", rt->cql_result_set_get_double); break; case SEM_TYPE_INTEGER: bprintf(out, "%s", rt->cql_result_set_get_int32); break; case SEM_TYPE_LONG_INTEGER: bprintf(out, "%s", rt->cql_result_set_get_int64); break; case SEM_TYPE_TEXT: bprintf(out, "%s", rt->cql_result_set_get_string); break; case SEM_TYPE_BLOB: bprintf(out, "%s", rt->cql_result_set_get_blob); break; case SEM_TYPE_OBJECT: bprintf(out, "%s", rt->cql_result_set_get_object); break; } bprintf(out, "((cql_result_set_ref)result_set, %s, %d);\n", row, info->col_index); bprintf(out, "}\n"); cleanup: CHARBUF_CLOSE(func_decl); CHARBUF_CLOSE(col_getter_sym); } #define DO_EMIT_SET_NULL true #define DONT_EMIT_SET_NULL false #define DO_USE_INLINE true #define DONT_USE_INLINE false // This function generate a inline or export version of a setter by using the function_info // passed in // * 1) We need to compute the name of the setter and also its visibility // * 2) We check if the function is setnull type using is_set_null parameter and if true we emit new typed // temp new_value_ var and we emit cql_set_null(new_value_), if is not a setnull funtion we emit a // cql_set_notnull(new_value_, new_value). We do this only for types // - cql_nullable_bool // - cql_nullable_double // - cql_nullable_int32 // - cql_nullable_int64 // otherwize we skip this step. // * 3) Using the correct resultset setter we emit the final set to mutate the resultset: cql_result_set_set_<type> // Examples: // using is_set_null = false. // extern void query_set_x(query_result_set_ref _Nonnull result_set, cql_int32 new_value) { // cql_nullable_int32 new_value_; // cql_set_notnull(new_value_, new_value); // cql_result_set_set_int32_col((cql_result_set_ref)result_set, 0, 2, new_value_); // } // using is_set_null = true. // extern void query_set_x_to_null(query_result_set_ref _Nonnull result_set) { // cql_nullable_int32 new_value_; // cql_set_null(new_value_); // cql_result_set_set_int32_col((cql_result_set_ref)result_set, 0, 2, new_value_); // } // use_inline arg is about the function visibility, on true will generate a static inline function // vs a extern function on false static void cg_proc_result_set_setter(function_info *_Nonnull info, bool_t use_inline, bool_t is_set_null) { charbuf *h = info->headers; charbuf *d = info->defs; charbuf *out = NULL; CG_CHARBUF_OPEN_SYM_WITH_PREFIX( col_getter_sym, rt->symbol_prefix, info->name, "_set_", info->col, info->sym_suffix); CHARBUF_OPEN(var_decl); if (!is_set_null) { cg_var_decl(&var_decl, info->ret_type, "new_value", CG_VAR_DECL_PROTO); } CHARBUF_OPEN(func_decl); if (use_inline) { bprintf(&func_decl, "static inline void %s", col_getter_sym.ptr); } else { bprintf(&func_decl, "%svoid %s", rt->symbol_visibility, col_getter_sym.ptr); } bprintf(&func_decl, "(%s _Nonnull result_set", info->result_set_ref_type); // a procedure that uses OUT gives exactly one row, so no index in the API if (!info->uses_out) { bprintf(&func_decl, ", %s row", rt->cql_int32); } if (use_inline) { out = h; } else { if (is_set_null) { bprintf(h, "%s);\n", func_decl.ptr); } else { bprintf(h, "%s, %s);\n", func_decl.ptr, var_decl.ptr); } out = d; } if (is_set_null) { bprintf(out, "\n%s) {\n", func_decl.ptr); } else { bprintf(out, "\n%s, %s) {\n", func_decl.ptr, var_decl.ptr); } CSTR row = info->uses_out ? "0" : "row"; bool_t is_ref = info->name_type == SEM_TYPE_TEXT || info->name_type == SEM_TYPE_OBJECT || info->name_type == SEM_TYPE_BLOB; if (is_ref && is_not_nullable(info->ret_type)) { bprintf(out, " cql_contract_argument_notnull((void *)new_value, 2);\n"); } bool_t generate_nullable_wrapper = !options.generate_type_getters; switch (info->name_type) { case SEM_TYPE_BOOL: if (generate_nullable_wrapper) { bprintf(out, " cql_nullable_bool new_value_;\n"); bprintf(out, " %s;\n", is_set_null ? "cql_set_null(new_value_)" : "cql_set_notnull(new_value_, new_value)"); } bprintf(out, " %s", rt->cql_result_set_set_bool); break; case SEM_TYPE_REAL: if (generate_nullable_wrapper) { bprintf(out, " cql_nullable_double new_value_;\n"); bprintf(out, " %s;\n", is_set_null ? "cql_set_null(new_value_)" : "cql_set_notnull(new_value_, new_value)"); } bprintf(out, " %s", rt->cql_result_set_set_double); break; case SEM_TYPE_INTEGER: if (generate_nullable_wrapper) { bprintf(out, " cql_nullable_int32 new_value_;\n"); bprintf(out, " %s;\n", is_set_null ? "cql_set_null(new_value_)" : "cql_set_notnull(new_value_, new_value)"); } bprintf(out, " %s", rt->cql_result_set_set_int32); break; case SEM_TYPE_LONG_INTEGER: if (generate_nullable_wrapper) { bprintf(out, " cql_nullable_int64 new_value_;\n"); bprintf(out, " %s;\n", is_set_null ? "cql_set_null(new_value_)" : "cql_set_notnull(new_value_, new_value)"); } bprintf(out, " %s", rt->cql_result_set_set_int64); break; case SEM_TYPE_TEXT: bprintf(out, " %s", rt->cql_result_set_set_string); break; case SEM_TYPE_BLOB: bprintf(out, " %s", rt->cql_result_set_set_blob); break; case SEM_TYPE_OBJECT: bprintf(out, " %s", rt->cql_result_set_set_object); break; } bprintf(out, "((cql_result_set_ref)result_set, %s, %d, new_value%s);\n", row, info->col_index, !is_ref && generate_nullable_wrapper ? "_" : ""); bprintf(out, "}\n"); CHARBUF_CLOSE(func_decl); CHARBUF_CLOSE(var_decl); CHARBUF_CLOSE(col_getter_sym); } // Write out the autodrop info into the stream static void cg_one_autodrop(CSTR _Nonnull name, ast_node *_Nonnull misc_attr_value, void *_Nullable context) { Invariant(context); charbuf *output = (charbuf *)context; bprintf(output, "%s\\0", name); } // If a stored proc is marked with the autodrop annotation when we automatically drop the indicated // tables when the proc is finished running. The attributes should look like this: // @attribute(cql:autodrop=(table1, table2, ,...)) static void cg_autodrops(ast_node *misc_attrs, charbuf *output) { if (!misc_attrs) { return; } CHARBUF_OPEN(temp); find_autodrops(misc_attrs, cg_one_autodrop, &temp); if (temp.used > 1) { bprintf(output, " .autodrop_tables = \"%s\",\n", temp.ptr); } CHARBUF_CLOSE(temp); } typedef struct fetch_result_info { CSTR data_types_sym; CSTR col_offsets_sym; CSTR refs_offset_sym; CSTR identity_columns_sym; CSTR row_sym; CSTR proc_sym; CSTR perf_index; ast_node *misc_attrs; int32_t refs_count; bool_t has_identity_columns; bool_t dml_proc; bool_t use_stmt; int32_t indent; CSTR prefix; int16_t encode_context_index; } fetch_result_info; // This generates the cql_fetch_info structure for the various output flavors // there is some variability here and so it's useful to consolidate the logic. // The factors are: // * if this is not a DML proc then there's no DB and no return code // * if there is no statement it means we're returning from a value cursor (which implies the above, too) // * we may or may not have references in the data type, so we include those if needed // * likewise identity columns // * the autodrops helper itself tests for the presence of the attribute in the correct form // // The above represents the runtime cql_fetch_info struct that will be used to either fetch all // rows or else fetch a single row from a given buffer. Either way, the metadata is assembled // here for use at runtime. // // Other fields are not conditional. static void cg_fetch_info(fetch_result_info *info, charbuf *output) { CHARBUF_OPEN(tmp); if (info->prefix) { bprintf(&tmp, "cql_fetch_info %s_info = {\n", info->prefix); } else { bprintf(&tmp, "cql_fetch_info info = {\n"); } if (info->dml_proc) { bprintf(&tmp, " .rc = rc,\n"); bprintf(&tmp, " .db = _db_,\n"); } else { bprintf(&tmp, " .rc = SQLITE_OK,\n"); // this case can't fail, there are no db ops } if (info->use_stmt) { bprintf(&tmp, " .stmt = stmt,\n"); } bprintf(&tmp, " .data_types = %s,\n", info->data_types_sym); bprintf(&tmp, " .col_offsets = %s,\n", info->col_offsets_sym); if (info->refs_count) { bprintf(&tmp, " .refs_count = %d,\n", info->refs_count); bprintf(&tmp, " .refs_offset = %s,\n", info->refs_offset_sym); } if (info->has_identity_columns) { bprintf(&tmp, " .identity_columns = %s,\n", info->identity_columns_sym); } bprintf(&tmp, " .encode_context_index = %d,\n", info->encode_context_index); bprintf(&tmp, " .rowsize = sizeof(%s),\n", info->row_sym); bprintf(&tmp, " .crc = CRC_%s,\n", info->proc_sym); bprintf(&tmp, " .perf_index = &%s,\n", info->perf_index); cg_autodrops(info->misc_attrs, &tmp); bprintf(&tmp, "};\n"); bindent(output, &tmp, info->indent); CHARBUF_CLOSE(tmp); } // If a stored procedure generates a result set then we need to do some extra work // to create the C friendly rowset creating and accessing helpers. If stored // proc "foo" creates a row set then we need to: // * emit a struct "foo_row" that has the shape of each row // * this isn't used by the client code but we use it in our code-gen // * emit a function "foo_fetch_results" that will call "foo" and read the rows // from the statement created by "foo". // * this method will construct a result set object via cql_result_create and store the data // * the remaining functions use cql_result_set_get_data and _get_count to get the data back out // * for each named column emit a function "foo_get_[column-name]" which // gets that column out of the rowset for the indicated row number. // * prototypes for the above go into the main output header file static void cg_proc_result_set(ast_node *ast) { Contract(is_ast_create_proc_stmt(ast)); Contract(is_struct(ast->sem->sem_type)); EXTRACT_NOTNULL(proc_params_stmts, ast->right); EXTRACT(params, proc_params_stmts->left); EXTRACT_STRING(name, ast->left); EXTRACT_MISC_ATTRS(ast, misc_attrs); bool_t suppress_result_set = misc_attrs && exists_attribute_str(misc_attrs, "suppress_result_set"); bool_t is_private = misc_attrs && exists_attribute_str(misc_attrs, "private"); bool_t uses_out_union = has_out_union_stmt_result(ast); if (!uses_out_union && (suppress_result_set || is_private)) { return; } bool_t uses_out = has_out_stmt_result(ast); bool_t result_set_proc = has_result_set(ast); // exactly one of these Invariant(uses_out + uses_out_union + result_set_proc == 1); bool_t dml_proc = is_dml_proc(ast->sem->sem_type); // sets base_fragment_name as well for the current fragment uint32_t frag_type = find_fragment_attr_type(misc_attrs, &base_fragment_name); Invariant(frag_type != FRAG_TYPE_EXTENSION); // register the proc name if there is a callback, the particular result type will do whatever it wants rt->register_proc_name && rt->register_proc_name(name); if (frag_type == FRAG_TYPE_BASE) { // When generating code for the base fragment we're only going to produce headers // and those headers will be for what the eventual assembly fragment name will be // that is the proc that will actually have the fetcher and so forth. Note the name // must match this is verifeid in semantic analysis. name = base_fragment_name; // note we did this AFTER registering the true name of this proc; this avoids // confusing proc name listeners with potentially two copies of the same name } charbuf *h = cg_header_output; charbuf *d = cg_declarations_output; CSTR result_set_name = name; CHARBUF_OPEN(data_types); CHARBUF_OPEN(result_set_create); CHARBUF_OPEN(temp); CG_CHARBUF_OPEN_SYM(getter_prefix, name); CG_CHARBUF_OPEN_SYM(stored_proc_name_sym, name, "_stored_procedure_name"); CG_CHARBUF_OPEN_SYM(result_set_sym, result_set_name, "_result_set"); CG_CHARBUF_OPEN_SYM(result_set_ref, result_set_name, "_result_set_ref"); CG_CHARBUF_OPEN_SYM(proc_sym, name); CG_CHARBUF_OPEN_SYM(row_sym, name, "_row"); CG_CHARBUF_OPEN_SYM(data_types_sym, name, "_data_types"); CG_CHARBUF_OPEN_SYM(data_types_count_sym, name, "_data_types_count"); CG_CHARBUF_OPEN_SYM(col_offsets_sym, name, "_col_offsets"); CG_CHARBUF_OPEN_SYM(refs_offset_sym, name, "_refs_offset"); CG_CHARBUF_OPEN_SYM(identity_columns_sym, name, "_identity_columns"); CG_CHARBUF_OPEN_SYM(result_count_sym, name, "_result_count"); CG_CHARBUF_OPEN_SYM(fetch_results_sym, name, "_fetch_results"); CG_CHARBUF_OPEN_SYM(copy_sym, name, "_copy"); CG_CHARBUF_OPEN_SYM(perf_index, name, "_perf_index"); CG_CHARBUF_OPEN_SYM(set_encoding_sym, name, "_set_encoding"); sem_struct *sptr = ast->sem->sptr; uint32_t count = sptr->count; // setting up perf index unless we are currently emitting an extension or base fragment // which do not fetch result independently (the assembly query generates the fetcher) if (frag_type != FRAG_TYPE_BASE) { bprintf(h, "#define CRC_%s %lldL\n", proc_sym.ptr, (llint_t)crc_charbuf(&proc_sym)); bprintf(d, "static int32_t %s;\n", perf_index.ptr); bprintf(h, "\n%s%s _Nonnull %s;\n", rt->symbol_visibility, rt->cql_string_ref, stored_proc_name_sym.ptr); bprintf(d, "\n%s(%s, \"%s\");\n", rt->cql_string_proc_name, stored_proc_name_sym.ptr, name); if (result_set_proc) { // First build the struct we need // As we walk the fields, construct the teardown operation needed // to clean up that field and save it. bprintf(d, "\ntypedef struct %s {\n", row_sym.ptr); cg_fields_in_canonical_order(d, sptr); bprintf(d, "} %s;\n", row_sym.ptr); } bprintf(h, "\n#define %s %d\n", data_types_count_sym.ptr, count); bprintf(&data_types, "\nuint8_t %s[%s] = {\n", data_types_sym.ptr, data_types_count_sym.ptr); } // If we are generating the typed getters, setup the function tables. // Again, extension fragments and base fragments do not define the actual tables // for the result that's done by the assembly fragment. if (options.generate_type_getters && frag_type != FRAG_TYPE_BASE) { bprintf(h, "\n%suint8_t %s[%s];\n", rt->symbol_visibility, data_types_sym.ptr, data_types_count_sym.ptr); } bprintf(h, "\n"); // the base type emits this info, it's shared by all // so extension fragments always have this arleady as do assembly fragments if (frag_type == FRAG_TYPE_BASE || frag_type == FRAG_TYPE_NONE) { cg_result_set_type_decl(h, result_set_sym.ptr, result_set_ref.ptr); } // we may not want the getters, at all. bool_t suppress_getters = false; if (misc_attrs) { suppress_getters = exists_attribute_str(misc_attrs, "suppress_getters") || exists_attribute_str(misc_attrs, "private") || // private implies suppress result set exists_attribute_str(misc_attrs, "suppress_result_set"); // and suppress result set implies suppress getters } // the index of the encode context column, -1 represents not found int16_t encode_context_index = -1; // we may want the setters. bool_t emit_setters = misc_attrs && exists_attribute_str(misc_attrs, "emit_setters"); // For each field emit the _get_field method for (int32_t i = 0; i < count; i++) { sem_t sem_type = sptr->semtypes[i]; CSTR col = sptr->names[i]; // Neither base fragments nor extension fragments declare the result data shape // the assembly fragement does that, all columns will be known at that time. if (frag_type != FRAG_TYPE_BASE) { bprintf(&data_types, " "); bool_t encode = should_encode_col(col, sem_type, use_encode, encode_columns); cg_data_type(&data_types, encode, sem_type); bprintf(&data_types, ", // %s\n", col); if (encode_context_column != NULL && !strcmp(col, encode_context_column)) { encode_context_index = (int16_t)i; } } if (suppress_getters) { continue; } sem_t core_type = core_type_of(sem_type); bool_t col_is_nullable = is_nullable(sem_type); function_info info = { .name = name, .col = col, .col_index = i, .headers = h, .defs = d, .uses_out = uses_out, .result_set_ref_type = result_set_ref.ptr, .row_struct_type = row_sym.ptr, .frag_type = frag_type, }; // if the current row is equal or greater than the base query count // is considered a private accesor since it belongs to the extension accessors if (frag_type == FRAG_TYPE_ASSEMBLY) { // we already know the base compiled with no errors ast_node *base_proc = find_base_fragment(base_fragment_name); Invariant(base_proc); Invariant(base_proc->sem); Invariant(base_proc->sem->sptr); uint32_t col_count_for_base = base_proc->sem->sptr->count; Invariant(col_count_for_base > 0); } if (options.generate_type_getters) { if (col_is_nullable && !is_ref_type(sem_type)) { info.ret_type = SEM_TYPE_BOOL | SEM_TYPE_NOTNULL; info.name_type = SEM_TYPE_NULL; info.sym_suffix = "_is_null"; cg_proc_result_set_type_based_getter(&info); info.ret_type = core_type | SEM_TYPE_NOTNULL; info.name_type = core_type; info.sym_suffix = "_value"; cg_proc_result_set_type_based_getter(&info); } else { info.ret_type = sem_type; info.name_type = core_type; info.sym_suffix = NULL; cg_proc_result_set_type_based_getter(&info); if (emit_setters) { cg_proc_result_set_setter(&info, DO_USE_INLINE, DONT_EMIT_SET_NULL); } } } else if (col_is_nullable && is_numeric(sem_type)) { info.ret_type = SEM_TYPE_BOOL | SEM_TYPE_NOTNULL; info.sym_suffix = "_is_null"; info.value_suffix = ".is_null"; cg_proc_result_set_getter(&info); info.ret_type = core_type | SEM_TYPE_NOTNULL, info.sym_suffix = "_value"; info.value_suffix = ".value"; cg_proc_result_set_getter(&info); if (emit_setters) { info.name_type = core_type; cg_proc_result_set_setter(&info, DONT_USE_INLINE, DONT_EMIT_SET_NULL); // set null setter info.sym_suffix = "_to_null"; cg_proc_result_set_setter(&info, DONT_USE_INLINE, DO_EMIT_SET_NULL); } } else { info.ret_type = sem_type; info.sym_suffix = NULL; info.value_suffix = NULL; cg_proc_result_set_getter(&info); if (emit_setters) { info.name_type = core_type; cg_proc_result_set_setter(&info, DONT_USE_INLINE, DONT_EMIT_SET_NULL); } } if (use_encode && sensitive_flag(sem_type)) { CG_CHARBUF_OPEN_SYM_WITH_PREFIX( col_getter_sym, rt->symbol_prefix, info.name, "_get_", info.col, "_is_encoded"); bprintf( info.headers, "\n#define %s(rs) \\\n" " %s((%s)rs, %d)\n", col_getter_sym.ptr, rt->cql_result_set_get_is_encoded, rt->cql_result_set_ref, i); CHARBUF_CLOSE(col_getter_sym); } } if (options.generate_type_getters) { bprintf(h, "\n"); } CHARBUF_OPEN(is_null_getter); bool_t generate_copy_attr = misc_attrs && exists_attribute_str(misc_attrs, "generate_copy"); // Check whether we need to generate a copy function. bool_t generate_copy = (generate_copy_attr || (rt->proc_should_generate_copy && rt->proc_should_generate_copy(name))); int32_t refs_count = refs_count_sptr(sptr); // Skip generating reference and column offsets for extension and base fragments since they always // delegate to assembly fragment for retrieving results with proper index if (frag_type != FRAG_TYPE_BASE) { bprintf(&data_types, "};\n"); bprintf(d, data_types.ptr); if (refs_count && !uses_out) { // note: fetch procs have already emitted this. cg_refs_offset(d, sptr, refs_offset_sym.ptr, row_sym.ptr); } cg_col_offsets(d, sptr, col_offsets_sym.ptr, row_sym.ptr); } bool_t has_identity_columns = cg_identity_columns(h, d, name, misc_attrs, identity_columns_sym.ptr); bprintf(&result_set_create, "(%s)%s(%s, count, %d, %s, meta)", result_set_ref.ptr, rt->cql_result_set_ref_new, uses_out ? "row" : "b.ptr", count, data_types_sym.ptr); CHARBUF_CLOSE(is_null_getter); // Emit foo_result_count, which is really just a proxy to cql_result_set_get_count, // but it is hiding the cql_result_set implementation detail from the API of the generated // code by providing a proc-scoped function for it with the typedef for the result set. bclear(&temp); bprintf(&temp, "%s %s(%s _Nonnull result_set)", rt->cql_int32, result_count_sym.ptr, result_set_ref.ptr); bprintf(h, "%s%s;\n", rt->symbol_visibility, temp.ptr); // the base fragment doesn't emit the row count symbol, this is done by the assembly; the base // fragment only emits the header for it. In fact the base fragment only emits headers in general. if (frag_type != FRAG_TYPE_BASE) { bprintf(d, "\n%s {\n", temp.ptr); bprintf(d, " return %s((cql_result_set_ref)result_set);\n", rt->cql_result_set_get_count); bprintf(d, "}\n"); } // Skip generating fetch result function for base fragments since they always get // results fetched through the assembly query if (frag_type != FRAG_TYPE_BASE) { if (uses_out) { // Emit foo_fetch_results, it has the same signature as foo only with a result set // instead of a statement. bclear(&temp); cg_emit_fetch_results_prototype(dml_proc, params, name, result_set_name, &temp); // ready for prototype and function begin now bprintf(h, "%s%s);\n", rt->symbol_visibility, temp.ptr); bprintf(d, "\n%s) {\n", temp.ptr); // emit profiling start signal bprintf(d, " cql_profile_start(CRC_%s, &%s);\n", proc_sym.ptr, perf_index.ptr); // one row result set from out parameter bprintf(d, " *result_set = NULL;\n"); bprintf(d, " %s *row = (%s *)calloc(1, sizeof(%s));\n", row_sym.ptr, row_sym.ptr, row_sym.ptr); bprintf(d, " "); // optional db arg and return code if (dml_proc) { bprintf(d, "cql_code rc = %s(_db_, ", proc_sym.ptr); } else { bprintf(d, "%s(", proc_sym.ptr); } if (params) { cg_param_names(params, d); bprintf(d, ", "); } bprintf(d, "row);\n"); fetch_result_info info = { .dml_proc = dml_proc, .use_stmt = 0, .data_types_sym = data_types_sym.ptr, .col_offsets_sym = col_offsets_sym.ptr, .refs_count = refs_count, .refs_offset_sym = refs_offset_sym.ptr, .has_identity_columns = has_identity_columns, .identity_columns_sym = identity_columns_sym.ptr, .row_sym = row_sym.ptr, .proc_sym = proc_sym.ptr, .perf_index = perf_index.ptr, .misc_attrs = misc_attrs, .indent = 2, .encode_context_index = encode_context_index, }; cg_fetch_info(&info, d); if (dml_proc) { bprintf(d, " return "); } else { bprintf(d, " "); } bprintf(d, "cql_one_row_result(&info, (char *)row, row->_has_row_, (cql_result_set_ref *)result_set);\n"); bprintf(d, "}\n\n"); } else if (result_set_proc) { // Emit foo_fetch_results, it has the same signature as foo only with a result set // instead of a statement. bclear(&temp); cg_emit_fetch_results_prototype(EMIT_DML_PROC, params, name, result_set_name, &temp); // To create the rowset we make a byte buffer object. That object lets us // append row data to an in-memory stream. Each row is fetched by binding // to a row object. We use cg_get_column to read the columns. The row // object of course has exactly the right type for each column. bprintf(h, "%s%s);\n", rt->symbol_visibility, temp.ptr); bprintf(d, "\n%s) {\n", temp.ptr); bprintf(d, " sqlite3_stmt *stmt = NULL;\n"); // emit profiling start signal bprintf(d, " cql_profile_start(CRC_%s, &%s);\n", proc_sym.ptr, perf_index.ptr); // Invoke the base proc to get the statement bprintf(d, " cql_code rc = %s(_db_, &stmt", proc_sym.ptr); if (params) { bprintf(d, ", "); cg_param_names(params, d); } bprintf(d, ");\n"); // Now read in in all the rows using this fetch information fetch_result_info info = { .dml_proc = 1, .use_stmt = 1, .data_types_sym = data_types_sym.ptr, .col_offsets_sym = col_offsets_sym.ptr, .refs_count = refs_count, .refs_offset_sym = refs_offset_sym.ptr, .has_identity_columns = has_identity_columns, .identity_columns_sym = identity_columns_sym.ptr, .row_sym = row_sym.ptr, .proc_sym = proc_sym.ptr, .perf_index = perf_index.ptr, .misc_attrs = misc_attrs, .indent = 2, .encode_context_index = encode_context_index, }; cg_fetch_info(&info, d); bprintf(d, " return cql_fetch_all_results(&info, (cql_result_set_ref *)result_set);\n"); bprintf(d, "}\n\n"); } else { // this is the only case left Invariant(uses_out_union); fetch_result_info info = { .dml_proc = 0, .use_stmt = 0, .data_types_sym = data_types_sym.ptr, .col_offsets_sym = col_offsets_sym.ptr, .refs_count = refs_count, .refs_offset_sym = refs_offset_sym.ptr, .has_identity_columns = has_identity_columns, .identity_columns_sym = identity_columns_sym.ptr, .row_sym = row_sym.ptr, .proc_sym = proc_sym.ptr, .perf_index = perf_index.ptr, .misc_attrs = misc_attrs, .indent = 0, .prefix = proc_sym.ptr, .encode_context_index = encode_context_index, }; cg_fetch_info(&info, d); } } if (generate_copy) { bprintf(h, "#define %s(result_set, result_set_to%s) \\\n" "%s((cql_result_set_ref)(result_set))->copy( \\\n" " (cql_result_set_ref)(result_set), \\\n" " (cql_result_set_ref *)(result_set_to), \\\n" " %s, \\\n" " %s)\n", copy_sym.ptr, uses_out ? "": ", from, count", rt->cql_result_set_get_meta, uses_out ? "0" : "from", uses_out ? "1" : "count"); } if (rt->generate_equality_macros) { bclear(&temp); CG_CHARBUF_OPEN_SYM(hash_sym, name, uses_out ? "_hash" : "_row_hash"); bprintf(h, "#define %s(result_set%s) " "%s((cql_result_set_ref)(result_set))->rowHash((cql_result_set_ref)(result_set), %s)\n", hash_sym.ptr, uses_out ? "" : ", row", rt->cql_result_set_get_meta, uses_out ? "0" : "row"); CHARBUF_CLOSE(hash_sym); CG_CHARBUF_OPEN_SYM(equal_sym, name, uses_out ? "_equal" : "_row_equal"); bprintf(h, "#define %s(rs1%s, rs2%s) \\\n" "%s((cql_result_set_ref)(rs1))->rowsEqual( \\\n" " (cql_result_set_ref)(rs1), \\\n" " %s, \\\n" " (cql_result_set_ref)(rs2), \\\n" " %s)\n", equal_sym.ptr, uses_out ? "" : ", row1", uses_out ? "" : ", row2", rt->cql_result_set_get_meta, uses_out ? "0" : "row1", uses_out ? "0" : "row2"); CHARBUF_CLOSE(equal_sym); if (has_identity_columns) { CG_CHARBUF_OPEN_SYM(same_sym, name, uses_out ? "_same" : "_row_same"); bprintf(h, "#define %s(rs1%s, rs2%s) \\\n" "%s((cql_result_set_ref)(rs1))->rowsSame( \\\n" " (cql_result_set_ref)(rs1), \\\n" " %s, \\\n" " (cql_result_set_ref)(rs2), \\\n" " %s)\n", same_sym.ptr, uses_out ? "" : ", row1", uses_out ? "" : ", row2", rt->cql_result_set_get_meta, uses_out ? "0" : "row1", uses_out ? "0" : "row2"); CHARBUF_CLOSE(same_sym); } } // Add a helper function that overrides CQL_DATA_TYPE_ENCODED bit of a resultset. // It's a debugging function that allow you to turn ON/OFF encoding/decoding when // your app is running. if (use_encode) { bprintf(h, "\nextern void %s(%s col, %s encode);\n", set_encoding_sym.ptr, rt->cql_int32, rt->cql_bool); bprintf(d, "void %s(%s col, %s encode) {\n", set_encoding_sym.ptr, rt->cql_int32, rt->cql_bool); bprintf(d, " return cql_set_encoding(%s, %s, col, encode);\n", data_types_sym.ptr, data_types_count_sym.ptr); bprintf(d, "}\n\n"); } CHARBUF_CLOSE(set_encoding_sym); CHARBUF_CLOSE(perf_index); CHARBUF_CLOSE(copy_sym); CHARBUF_CLOSE(fetch_results_sym); CHARBUF_CLOSE(result_count_sym); CHARBUF_CLOSE(identity_columns_sym); CHARBUF_CLOSE(refs_offset_sym); CHARBUF_CLOSE(col_offsets_sym); CHARBUF_CLOSE(data_types_count_sym); CHARBUF_CLOSE(data_types_sym); CHARBUF_CLOSE(row_sym); CHARBUF_CLOSE(proc_sym); CHARBUF_CLOSE(result_set_ref); CHARBUF_CLOSE(result_set_sym); CHARBUF_CLOSE(stored_proc_name_sym); CHARBUF_CLOSE(getter_prefix); CHARBUF_CLOSE(temp); CHARBUF_CLOSE(result_set_create); CHARBUF_CLOSE(data_types); } // Main entry point for code-gen. This will set up the buffers for the global // variables and any loose calls or DML. Any code that needs to run in the // global scope will be added to the global_proc. This is the only codegen // error that is possible. If you need global code and you don't have a global // proc then you can't proceed. Semantic analysis doƒesn't want to know that stuff. // Otherwise all we do is set up the most general buffers for the global case and // spit out a function with the correct name. cql_noexport void cg_c_main(ast_node *head) { cql_exit_on_semantic_errors(head); exit_on_validating_schema(); CSTR header_file_name = options.file_names[0]; CSTR body_file_name = options.file_names[1]; CSTR exports_file_name = NULL; int32_t export_file_index = 2; if (options.generate_exports) { if (options.file_names_count <= export_file_index) { cql_error("--cg had the wrong number of arguments, argument %d was needed\n", export_file_index + 1); cql_cleanup_and_exit(1); } exports_file_name = options.file_names[export_file_index]; } cg_c_init(); cg_scratch_masks global_scratch_masks; cg_current_masks = &global_scratch_masks; cg_zero_masks(cg_current_masks); if (options.compress) { // seed the fragments with popular ones based on statistics CHARBUF_OPEN(ignored); cg_statement_pieces(", ", &ignored); cg_statement_pieces("AS ", &ignored); cg_statement_pieces("NOT ", &ignored); cg_statement_pieces(".", &ignored); cg_statement_pieces(",", &ignored); cg_statement_pieces(",\n ", &ignored); cg_statement_pieces("id ", &ignored); cg_statement_pieces("id,", &ignored); cg_statement_pieces("KEY", &ignored); cg_statement_pieces("KEY ", &ignored); cg_statement_pieces("TEXT", &ignored); cg_statement_pieces("LONG_", &ignored); cg_statement_pieces("INT ", &ignored); cg_statement_pieces("INTEGER ", &ignored); cg_statement_pieces("ON ", &ignored); cg_statement_pieces("TEXT ", &ignored); cg_statement_pieces("CAST", &ignored); cg_statement_pieces("TABLE ", &ignored); cg_statement_pieces("(", &ignored); cg_statement_pieces(")", &ignored); cg_statement_pieces("( ", &ignored); cg_statement_pieces(") ", &ignored); cg_statement_pieces("0", &ignored); cg_statement_pieces("1", &ignored); cg_statement_pieces("= ", &ignored); cg_statement_pieces("__", &ignored); cg_statement_pieces("NULL ", &ignored); cg_statement_pieces("NULL,", &ignored); cg_statement_pieces("EXISTS ", &ignored); cg_statement_pieces("CREATE ", &ignored); cg_statement_pieces("DROP ", &ignored); cg_statement_pieces("TABLE ", &ignored); cg_statement_pieces("VIEW ", &ignored); cg_statement_pieces("PRIMARY ", &ignored); cg_statement_pieces("IF ", &ignored); cg_statement_pieces("(\n", &ignored); CHARBUF_CLOSE(ignored); } CHARBUF_OPEN(exports_file); CHARBUF_OPEN(header_file); CHARBUF_OPEN(body_file); CHARBUF_OPEN(indent); if (exports_file_name) { exports_output = &exports_file; if (rt->exports_prefix) { bprintf(exports_output, "%s", rt->exports_prefix); } } cg_stmt_list(head); bprintf(&body_file, "%s", rt->source_prefix); if (options.c_include_namespace) { bprintf(&body_file, "#include \"%s/%s\"\n\n", options.c_include_namespace, options.file_names[0]); } else { bprintf(&body_file, "#include \"%s\"\n\n", options.file_names[0]); } bprintf(&body_file, "%s", rt->source_wrapper_begin); bprintf(&body_file, "#pragma clang diagnostic push\n"); bprintf(&body_file, "#pragma clang diagnostic ignored \"-Wunknown-warning-option\"\n"); bprintf(&body_file, "#pragma clang diagnostic ignored \"-Wbitwise-op-parentheses\"\n"); bprintf(&body_file, "#pragma clang diagnostic ignored \"-Wshift-op-parentheses\"\n"); bprintf(&body_file, "#pragma clang diagnostic ignored \"-Wlogical-not-parentheses\"\n"); bprintf(&body_file, "#pragma clang diagnostic ignored \"-Wlogical-op-parentheses\"\n"); bprintf(&body_file, "#pragma clang diagnostic ignored \"-Wliteral-conversion\"\n"); bprintf(&body_file, "#pragma clang diagnostic ignored \"-Wunused-but-set-variable\"\n"); bprintf(&body_file, "%s", cg_fwd_ref_output->ptr); bprintf(&body_file, "%s", cg_constants_output->ptr); if (cg_pieces_output->used > 1) { bprintf(&body_file, "static const char _pieces_[] = \n%s;\n", cg_pieces_output->ptr); } bprintf(&body_file, "%s", cg_declarations_output->ptr); // main function after constants and decls (if needed) bool_t global_proc_needed = cg_main_output->used > 1 || cg_scratch_vars_output->used > 1; if (global_proc_needed) { exit_on_no_global_proc(); bprintf(&body_file, "#define _PROC_ %s\n", global_proc_name); bindent(&indent, cg_scratch_vars_output, 2); bprintf(&body_file, "\ncql_code %s(sqlite3 *_Nonnull _db_) {\n", global_proc_name); cg_emit_rc_vars(&body_file); bprintf(&body_file, "%s", indent.ptr); bprintf(&body_file, "%s", cg_main_output->ptr); bprintf(&body_file, "\n"); if (error_target_used) { bprintf(&body_file, "%s:\n", error_target); } bprintf(&body_file, "%s", cg_cleanup_output->ptr); bprintf(&body_file, " return _rc_;\n"); bprintf(&body_file, "}\n"); bprintf(&body_file, "\n#undef _PROC_\n"); } bprintf(&body_file, "#pragma clang diagnostic pop\n"); bprintf(&body_file, "%s", rt->source_wrapper_end); bprintf(&header_file, "%s", rt->header_prefix); bprintf(&header_file, rt->cqlrt_template, rt->cqlrt); bprintf(&header_file, "%s", rt->header_wrapper_begin); bprintf(&header_file, "%s", cg_header_output->ptr); bprintf(&header_file, "%s", rt->header_wrapper_end); CHARBUF_CLOSE(indent); cql_write_file(header_file_name, header_file.ptr); if (options.nolines || options.test) { cql_write_file(body_file_name, body_file.ptr); } else { CHARBUF_OPEN(body_with_line_directives); cg_insert_line_directives(body_file.ptr, &body_with_line_directives); cql_write_file(body_file_name, body_with_line_directives.ptr); CHARBUF_CLOSE(body_with_line_directives); } if (exports_file_name) { cql_write_file(exports_file_name, exports_file.ptr); } CHARBUF_CLOSE(body_file); CHARBUF_CLOSE(header_file); CHARBUF_CLOSE(exports_file); cg_c_cleanup(); } cql_noexport void cg_c_init(void) { cg_c_cleanup(); // reset globals/statics cg_common_init(); Contract(!error_target_used); // one table for the whole translation unit Contract(!string_literals); string_literals = symtab_new_case_sens(); Contract(!text_pieces); text_pieces = symtab_new_case_sens(); DDL_STMT_INIT(drop_table_stmt); DDL_STMT_INIT(drop_view_stmt); DDL_STMT_INIT(drop_index_stmt); DDL_STMT_INIT(drop_trigger_stmt); DDL_STMT_INIT(create_table_stmt); DDL_STMT_INIT(create_virtual_table_stmt); DDL_STMT_INIT(create_trigger_stmt); DDL_STMT_INIT(create_index_stmt); DDL_STMT_INIT(create_view_stmt); DDL_STMT_INIT(alter_table_add_column_stmt); NO_OP_STMT_INIT(enforce_reset_stmt); NO_OP_STMT_INIT(enforce_normal_stmt); NO_OP_STMT_INIT(enforce_strict_stmt); NO_OP_STMT_INIT(enforce_push_stmt); NO_OP_STMT_INIT(enforce_pop_stmt); NO_OP_STMT_INIT(declare_schema_region_stmt); NO_OP_STMT_INIT(declare_deployable_region_stmt); NO_OP_STMT_INIT(begin_schema_region_stmt); NO_OP_STMT_INIT(end_schema_region_stmt); NO_OP_STMT_INIT(schema_upgrade_version_stmt); NO_OP_STMT_INIT(schema_upgrade_script_stmt); NO_OP_STMT_INIT(schema_ad_hoc_migration_stmt); NO_OP_STMT_INIT(schema_unsub_stmt); NO_OP_STMT_INIT(schema_resub_stmt); NO_OP_STMT_INIT(declare_enum_stmt); NO_OP_STMT_INIT(declare_const_stmt); NO_OP_STMT_INIT(declare_named_type); NO_OP_STMT_INIT(declare_proc_no_check_stmt); STD_DML_STMT_INIT(begin_trans_stmt); STD_DML_STMT_INIT(commit_trans_stmt); STD_DML_STMT_INIT(rollback_trans_stmt); STD_DML_STMT_INIT(savepoint_stmt); STD_DML_STMT_INIT(release_savepoint_stmt); STD_DML_STMT_INIT(delete_stmt); STD_DML_STMT_INIT(with_delete_stmt); STD_DML_STMT_INIT(update_stmt); STD_DML_STMT_INIT(with_update_stmt); // insert forms have some special processing for the 'seed' case STMT_INIT(insert_stmt); STMT_INIT(with_insert_stmt); STMT_INIT(upsert_stmt); STMT_INIT(with_upsert_stmt); // these DML methods need to use prepare and have other processing other than just EXEC STMT_INIT(select_stmt); STMT_INIT(with_select_stmt); STMT_INIT(if_stmt); STMT_INIT(switch_stmt); STMT_INIT(while_stmt); STMT_INIT(leave_stmt); STMT_INIT(continue_stmt); STMT_INIT(return_stmt); STMT_INIT(rollback_return_stmt); STMT_INIT(commit_return_stmt); STMT_INIT(call_stmt); STMT_INIT(declare_out_call_stmt); STMT_INIT(declare_vars_type); STMT_INIT(declare_group_stmt); STMT_INIT(emit_group_stmt); STMT_INIT(assign); STMT_INIT(let_stmt); STMT_INIT(set_from_cursor); STMT_INIT(create_proc_stmt); STMT_INIT(emit_enums_stmt); STMT_INIT(emit_constants_stmt); STMT_INIT(declare_proc_stmt); STMT_INIT(declare_func_stmt); STMT_INIT(declare_select_func_stmt); STMT_INIT(trycatch_stmt); STMT_INIT(proc_savepoint_stmt); STMT_INIT(throw_stmt); STMT_INIT(declare_cursor); STMT_INIT(declare_cursor_like_name); STMT_INIT(declare_cursor_like_select); STMT_INIT(declare_value_cursor); STMT_INIT(loop_stmt); STMT_INIT(fetch_stmt); STMT_INIT(fetch_values_stmt); STMT_INIT(set_blob_from_cursor_stmt); STMT_INIT(fetch_cursor_from_blob_stmt); STMT_INIT(update_cursor_stmt); STMT_INIT(fetch_call_stmt); STMT_INIT(close_stmt); STMT_INIT(out_stmt); STMT_INIT(out_union_stmt); STMT_INIT(echo_stmt); FUNC_INIT(sign); FUNC_INIT(abs); FUNC_INIT(sensitive); FUNC_INIT(nullable); FUNC_INIT(ifnull_throw); FUNC_INIT(ifnull_crash); FUNC_INIT(ifnull); FUNC_INIT(coalesce); FUNC_INIT(last_insert_rowid); FUNC_INIT(changes); FUNC_INIT(printf); FUNC_INIT(cql_get_blob_size); FUNC_INIT(cql_inferred_notnull); EXPR_INIT(num, cg_expr_num, "num", C_EXPR_PRI_ROOT); EXPR_INIT(str, cg_expr_str, "STR", C_EXPR_PRI_ROOT); EXPR_INIT(null, cg_expr_null, "NULL", C_EXPR_PRI_ROOT); EXPR_INIT(dot, cg_expr_dot, "DOT", C_EXPR_PRI_ROOT); EXPR_INIT(lshift, cg_binary, "<<", C_EXPR_PRI_SHIFT); EXPR_INIT(rshift, cg_binary, ">>", C_EXPR_PRI_SHIFT); EXPR_INIT(bin_and, cg_binary, "&", C_EXPR_PRI_BAND); EXPR_INIT(bin_or, cg_binary, "|", C_EXPR_PRI_BOR); EXPR_INIT(mul, cg_binary, "*", C_EXPR_PRI_MUL); EXPR_INIT(div, cg_binary, "/", C_EXPR_PRI_MUL); EXPR_INIT(mod, cg_binary, "%", C_EXPR_PRI_MUL); EXPR_INIT(add, cg_binary, "+", C_EXPR_PRI_ADD); EXPR_INIT(sub, cg_binary, "-", C_EXPR_PRI_ADD); EXPR_INIT(not, cg_unary, "!", C_EXPR_PRI_UNARY); EXPR_INIT(tilde, cg_unary, "~", C_EXPR_PRI_UNARY); EXPR_INIT(uminus, cg_unary, "-", C_EXPR_PRI_UNARY); EXPR_INIT(eq, cg_binary_compare, "==", C_EXPR_PRI_EQ_NE); EXPR_INIT(ne, cg_binary_compare, "!=", C_EXPR_PRI_EQ_NE); EXPR_INIT(lt, cg_binary_compare, "<", C_EXPR_PRI_LT_GT); EXPR_INIT(gt, cg_binary_compare, ">", C_EXPR_PRI_LT_GT); EXPR_INIT(ge, cg_binary_compare, ">=", C_EXPR_PRI_LT_GT); EXPR_INIT(le, cg_binary_compare, "<=", C_EXPR_PRI_LT_GT); EXPR_INIT(call, cg_expr_call, "CALL", C_EXPR_PRI_ROOT); EXPR_INIT(between_rewrite, cg_expr_between_rewrite, "BETWEEN", C_EXPR_PRI_ROOT); EXPR_INIT(and, cg_expr_and, "AND", C_EXPR_PRI_LAND); EXPR_INIT(or, cg_expr_or, "OR", C_EXPR_PRI_LOR); EXPR_INIT(select_stmt, cg_expr_select, "SELECT", C_EXPR_PRI_ROOT); EXPR_INIT(select_if_nothing_expr, cg_expr_select_if_nothing, "SELECT", C_EXPR_PRI_ROOT); EXPR_INIT(select_if_nothing_throw_expr, cg_expr_select_if_nothing_throw, "SELECT", C_EXPR_PRI_ROOT); EXPR_INIT(select_if_nothing_or_null_expr, cg_expr_select_if_nothing_or_null, "SELECT", C_EXPR_PRI_ROOT); EXPR_INIT(with_select_stmt, cg_expr_select, "WITH...SELECT", C_EXPR_PRI_ROOT); EXPR_INIT(is, cg_expr_is, "IS", C_EXPR_PRI_EQ_NE); EXPR_INIT(is_not, cg_expr_is_not, "IS NOT", C_EXPR_PRI_EQ_NE); EXPR_INIT(is_not_true, cg_expr_is_not_true, "IS NOT TRUE", C_EXPR_PRI_EQ_NE); EXPR_INIT(is_not_false, cg_expr_is_not_false, "IS NOT FALSE", C_EXPR_PRI_EQ_NE); EXPR_INIT(is_true, cg_expr_is_true, "IS TRUE", C_EXPR_PRI_EQ_NE); EXPR_INIT(is_false, cg_expr_is_false, "IS FALSE", C_EXPR_PRI_EQ_NE); EXPR_INIT(like, cg_binary_compare, "LIKE", C_EXPR_PRI_EQ_NE); EXPR_INIT(not_like, cg_binary_compare, "LIKE", C_EXPR_PRI_EQ_NE); EXPR_INIT(in_pred, cg_expr_in_pred_or_not_in, "IN", C_EXPR_PRI_ROOT); EXPR_INIT(not_in, cg_expr_in_pred_or_not_in, "NOT IN", C_EXPR_PRI_ROOT); EXPR_INIT(case_expr, cg_expr_case, "CASE", C_EXPR_PRI_ROOT); EXPR_INIT(cast_expr, cg_expr_cast, "CAST", C_EXPR_PRI_ROOT); } // To make sure we start at a zero state. This is really necessary stuff // because of the amalgam. In the context of the amalgam the compiler // might be run more than once without the process exiting. Hence we have // to reset the globals and empty the symbol tables. cql_noexport void cg_c_cleanup() { cg_common_cleanup(); SYMTAB_CLEANUP(string_literals); SYMTAB_CLEANUP(text_pieces); base_fragment_name = NULL; exports_output = NULL; error_target = NULL; cg_current_masks = NULL; cg_in_loop = 0; case_statement_count = 0; catch_block_count = 0; error_target = CQL_CLEANUP_DEFAULT_LABEL; error_target_used = false; rcthrown_current = CQL_RCTHROWN_DEFAULT; rcthrown_used = false; rcthrown_index = 0; return_used = false; piece_last_offset = 0; seed_declared = false; stack_level = 0; string_literals_count = 0; temp_cstr_count = 0; temp_statement_emitted = false; } #endif