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, "ed);
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, "ed);
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