sources/ast.c (589 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.
*/
// Assorted definitions for the CQL abstract syntax tree
#define AST_EMIT_DEFS 1
#include "cql.h"
#include "minipool.h"
#include "ast.h"
#include "sem.h"
#include "gen_sql.h"
#include "cg_common.h"
#include "encoders.h"
cql_data_defn( minipool *ast_pool );
cql_data_defn( minipool *str_pool );
cql_data_defn( char *_Nullable current_file );
// Helper object to just hold info in find_attribute_str(...)
typedef struct misc_attrs_type {
CSTR attribute_name;
void * context;
find_ast_str_node_callback str_node_callback;
bool_t presence_only;
uint32_t count;
} misc_attrs_type;
cql_noexport void ast_init() {
minipool_open(&ast_pool);
minipool_open(&str_pool);
}
cql_noexport void ast_cleanup() {
minipool_close(&ast_pool);
minipool_close(&str_pool);
run_lazy_frees();
}
cql_noexport void ast_set_rewrite_info(int32_t lineno, CSTR filename) {
yylineno = lineno;
current_file = (char *)filename;
}
cql_noexport void ast_reset_rewrite_info() {
yylineno = -1;
current_file = NULL;
}
cql_noexport bool_t is_ast_num(ast_node *node) {
return node && (node->type == k_ast_num);
}
cql_noexport bool_t is_ast_int(ast_node *node) {
return node && (node->type == k_ast_int);
}
cql_noexport bool_t is_ast_str(ast_node *node) {
return node && (node->type == k_ast_str);
}
cql_noexport bool_t is_ast_blob(ast_node *node) {
return node && (node->type == k_ast_blob);
}
cql_noexport bool_t is_at_rc(ast_node *node) {
return is_ast_str(node) && !Strcasecmp("@RC", ((str_ast_node*)node)->value);
}
cql_noexport bool_t is_proclit(ast_node *node) {
return is_ast_str(node) && !Strcasecmp("@proc", ((str_ast_node*)node)->value);
}
cql_noexport bool_t is_strlit(ast_node *node) {
return is_ast_str(node) && ((str_ast_node *)node)->value[0] == '\'';
}
cql_noexport bool_t is_id(ast_node *node) {
return is_ast_str(node) && ((str_ast_node *)node)->value[0] != '\'';
}
cql_noexport bool_t is_id_or_dot(ast_node *node) {
return is_id(node) || is_ast_dot(node);
}
cql_noexport bool_t is_primitive(ast_node *node) {
return is_ast_num(node) || is_ast_str(node) || is_ast_blob(node) || is_ast_int(node);
}
cql_noexport bool_t is_proc(ast_node *node) {
return is_ast_create_proc_stmt(node) || is_ast_declare_proc_stmt(node);
}
cql_noexport bool_t is_region(ast_node *ast) {
return is_ast_declare_schema_region_stmt(ast) || is_ast_declare_deployable_region_stmt(ast);
}
cql_noexport bool_t is_select_stmt(ast_node *ast) {
return is_ast_select_stmt(ast) ||
is_ast_explain_stmt(ast) ||
is_ast_with_select_stmt(ast);
}
cql_noexport bool_t is_delete_stmt(ast_node *ast) {
return is_ast_delete_stmt(ast) ||
is_ast_with_delete_stmt(ast);
}
cql_noexport bool_t is_update_stmt(ast_node *ast) {
return is_ast_update_stmt(ast) ||
is_ast_with_update_stmt(ast);
}
cql_noexport bool_t is_insert_stmt(ast_node *ast) {
return is_ast_insert_stmt(ast) ||
is_ast_with_insert_stmt(ast) ||
is_ast_upsert_stmt(ast) ||
is_ast_with_upsert_stmt(ast);
}
cql_noexport bool_t ast_has_left(ast_node *node) {
if (is_primitive(node)) {
return false;
}
return (node->left != NULL);
}
cql_noexport bool_t ast_has_right(ast_node *node) {
if (is_primitive(node)) {
return false;
}
return (node->right != NULL);
}
cql_noexport void ast_set_right(ast_node *parent, ast_node *right) {
parent->right = right;
if (right) {
right->parent = parent;
}
}
cql_noexport void ast_set_left(ast_node *parent, ast_node *left) {
parent->left = left;
if (left) {
left->parent = parent;
}
}
cql_noexport ast_node *new_ast(const char *type, ast_node *left, ast_node *right) {
Contract(current_file && yylineno > 0);
ast_node *ast = _ast_pool_new(ast_node);
ast->type = type;
ast->left = left;
ast->right = right;
ast->lineno = yylineno;
ast->filename = current_file;
ast->sem = NULL;
if (left) left->parent = ast;
if (right) right->parent = ast;
return ast;
}
cql_noexport ast_node *new_ast_opt(int32_t value) {
Contract(current_file && yylineno > 0);
int_ast_node *iast = _ast_pool_new(int_ast_node);
iast->type = k_ast_int;
iast->value = value;
iast->lineno = yylineno;
iast->filename = current_file;
iast->sem = NULL;
return (ast_node *)iast;
}
cql_noexport ast_node *new_ast_str(CSTR value) {
Contract(current_file && yylineno > 0);
str_ast_node *sast = _ast_pool_new(str_ast_node);
sast->type = k_ast_str;
sast->value = value;
sast->lineno = yylineno;
sast->filename = current_file;
sast->sem = NULL;
sast->cstr_literal = false;
return (ast_node *)sast;
}
cql_noexport ast_node *new_ast_num(int32_t num_type, CSTR value) {
Contract(current_file && yylineno > 0);
Contract(value);
num_ast_node *nast = _ast_pool_new(num_ast_node);
nast->type = k_ast_num;
nast->value = value;
nast->lineno = yylineno;
nast->filename = current_file;
nast->sem = NULL;
nast->num_type = num_type;
Contract(nast->value);
return (ast_node *)nast;
}
cql_noexport ast_node *new_ast_blob(CSTR value) {
Contract(current_file && yylineno > 0);
str_ast_node *sast = _ast_pool_new(str_ast_node);
sast->type = k_ast_blob;
sast->value = value;
sast->lineno = yylineno;
sast->filename = current_file;
sast->sem = NULL;
sast->cstr_literal = false;
return (ast_node *)sast;
}
// Get the compound operator name. crash if compound operation integer is
// invalid
cql_noexport CSTR get_compound_operator_name(int32_t compound_operator) {
CSTR result = NULL;
switch (compound_operator) {
case COMPOUND_OP_EXCEPT:
result = "EXCEPT";
break;
case COMPOUND_OP_INTERSECT:
result = "INTERSECT";
break;
case COMPOUND_OP_UNION:
result = "UNION";
break;
case COMPOUND_OP_UNION_ALL:
result = "UNION ALL";
break;
}
Invariant(result);
return result;
}
// This converts C string literal syntax into SQL string literal syntax
// the test of the program expects the SQL style literals. We support
// C style literals largely because they pass through the C pre-processor better.
// Even stuff like the empty string '' causes annoying warnings. However
// the escaping is lightly different. Also C string literals have useful escape sequences
cql_noexport CSTR convert_cstrlit(CSTR cstr) {
CHARBUF_OPEN(decoded);
CHARBUF_OPEN(encoded);
cg_decode_c_string_literal(cstr, &decoded);
cg_encode_string_literal(decoded.ptr, &encoded);
CSTR result = Strdup(encoded.ptr);
CHARBUF_CLOSE(encoded);
CHARBUF_CLOSE(decoded);
return result;
}
cql_noexport ast_node *new_ast_cstr(CSTR value) {
value = convert_cstrlit(value);
str_ast_node *sast = (str_ast_node *)new_ast_str(value);
sast->cstr_literal = true;
return (ast_node *)sast;
}
static char padbuffer[4096];
cql_noexport bool_t print_ast_value(struct ast_node *node) {
bool_t ret = false;
if (is_ast_str(node)) {
cql_output("%s", padbuffer);
if (is_strlit(node)) {
cql_output("{strlit %s}", ((struct str_ast_node *)node)->value);
}
else {
cql_output("{name %s}", ((struct str_ast_node *)node)->value);
}
ret = true;
}
if (is_ast_num(node)) {
cql_output("%s", padbuffer);
EXTRACT_NUM_TYPE(num_type, node);
EXTRACT_NUM_VALUE(val, node);
if (num_type == NUM_BOOL) {
cql_output("{bool %s}", val);
}
else if (num_type == NUM_INT) {
cql_output("{int %s}", val);
}
else if (num_type == NUM_LONG) {
cql_output("{longint %s}", val);
}
else if (num_type == NUM_REAL) {
cql_output("{dbl %s}", val);
}
ret = true;
}
if (is_ast_blob(node)) {
cql_output("%s", padbuffer);
cql_output("{blob %s}", ((struct str_ast_node *)node)->value);
ret = true;
}
if (is_ast_int(node)) {
cql_output("%s", padbuffer);
cql_output("{int %lld}", (llint_t)((struct int_ast_node *)node)->value);
ret = true;
}
if (ret && node->sem) {
cql_output(": ");
print_sem_type(node->sem);
}
if (ret) {
cql_output("\n");
}
return ret;
}
cql_noexport void print_ast_type(ast_node *node) {
cql_output("%s", padbuffer);
cql_output("{%s}", node->type);
if (node->sem) {
cql_output(": ");
print_sem_type(node->sem);
}
cql_output("\n");
}
// Helper function to get the parameters node out of the ast for a proc.
cql_noexport ast_node *get_proc_params(ast_node *ast) {
Contract(is_ast_create_proc_stmt(ast) || is_ast_declare_proc_stmt(ast));
// works for both
EXTRACT_NOTNULL(proc_params_stmts, ast->right);
EXTRACT(params, proc_params_stmts->left);
return params;
}
// Helper function to get the proc name from a declare_proc_stmt or create_proc_stmt
cql_noexport ast_node *get_proc_name(ast_node *ast) {
if (is_ast_create_proc_stmt(ast)) {
return ast->left;
}
Contract(is_ast_declare_proc_stmt(ast));
EXTRACT_NOTNULL(proc_name_type, ast->left);
return proc_name_type->left;
}
// Helper function to get the parameters node out of the ast for a func.
cql_noexport ast_node *get_func_params(ast_node *func_stmt) {
Contract(is_ast_declare_func_stmt(func_stmt) || is_ast_declare_select_func_stmt(func_stmt));
EXTRACT_NOTNULL(func_params_return, func_stmt->right);
EXTRACT(params, func_params_return->left);
return params;
}
// Helper function to extract the list of attribute.
// Walk through a misc_attrs node and call the callbacks :
// - find_ast_misc_attr_callback if misc_attr node is found
// Let's take the example below and see what values will be passed to callbacks
// e.g:
// @attribute(cql:foo=(baa, (name, 'nelly')))
// @attribute(cql:base=raoul)
// create procedure sample()
// begin
// select * from baa;
// end;
//
// 1- find_ast_misc_attr_callback("cql", "foo", <(baa, (name, 'nelly'))>, <context>)
// 2- find_ast_misc_attr_callback("cql", "foo", <raoul>, <context>)
// 3- End
cql_noexport void find_misc_attrs(
ast_node *_Nullable ast_misc_attrs,
find_ast_misc_attr_callback _Nonnull misc_attr_callback,
void *_Nullable context)
{
Contract(is_ast_misc_attrs(ast_misc_attrs));
for (ast_node *misc_attrs = ast_misc_attrs; misc_attrs; misc_attrs = misc_attrs->right) {
Invariant(is_ast_misc_attrs(misc_attrs));
ast_node *misc_attr = misc_attrs->left;
ast_node *misc_attr_key = misc_attr->left;
ast_node *values = misc_attr->right;
CSTR misc_attr_prefix = NULL;
CSTR misc_attr_name = NULL;
if (is_ast_dot(misc_attr_key)) {
EXTRACT_STRING(prefix, misc_attr_key->left);
EXTRACT_STRING(name, misc_attr_key->right);
misc_attr_prefix = prefix;
misc_attr_name = name;
} else {
EXTRACT_STRING(name, misc_attr_key);
misc_attr_name = name;
}
Invariant(misc_attr_name);
misc_attr_callback(misc_attr_prefix, misc_attr_name, values, context);
}
}
// This callback helper dispatches matching string or list of string values
// for the indicated cql:attribute_name. Non-string values are ignored in
// this path. Note that the attribute might be badly formed hence there are
// few Contract enforcement here. We can't crash if the value is unexpected
// we just don't recognize it as properly attributed for whatever (e.g. it just
// isn't a base fragment decl if it has an integer value for the fragment name)
static void ast_find_ast_misc_attr_callback(
CSTR misc_attr_prefix,
CSTR misc_attr_name,
ast_node *ast_misc_attr_values,
void *_Nullable context)
{
misc_attrs_type* misc = (misc_attrs_type*) context;
// First make sure that there is a prefix and name and that they match
if (misc_attr_prefix &&
misc_attr_name &&
!Strcasecmp(misc_attr_prefix, "cql") &&
!Strcasecmp(misc_attr_name, misc->attribute_name)) {
// callback regardless of value, could be any payload
if (misc->presence_only) {
Invariant(!misc->str_node_callback);
misc->count++;
return;
}
// The attribute value might be a string or a list of strings.
// Non-string, non-list attributes are ignored for this callback type
if (is_ast_str(ast_misc_attr_values)) {
if (misc->str_node_callback) {
EXTRACT_STRING(value, ast_misc_attr_values);
misc->str_node_callback(value, ast_misc_attr_values, misc->context);
}
misc->count++;
}
else if (is_ast_misc_attr_value_list(ast_misc_attr_values)) {
for (ast_node *list = ast_misc_attr_values; list; list = list->right) {
// any non-string values are ignored, loop over the rest calling on each string
if (is_ast_str(list->left)) {
EXTRACT_STRING(value, list->left);
if (misc->str_node_callback) {
misc->str_node_callback(value, list->left, misc->context);
}
misc->count++;
}
}
}
}
}
// Helper function to extract the specified string type attribute (if any) from the misc attributes
// provided, and invoke the callback function
cql_noexport uint32_t find_attribute_str(
ast_node *_Nullable misc_attr_list,
find_ast_str_node_callback _Nullable callback,
void *_Nullable context,
const char *attribute_name)
{
Contract(is_ast_misc_attrs(misc_attr_list));
misc_attrs_type misc = {
.str_node_callback = callback,
.context = context,
.attribute_name = attribute_name,
.count = 0,
};
find_misc_attrs(misc_attr_list, ast_find_ast_misc_attr_callback, &misc);
return misc.count;
}
// This callback helper tests only if the attribute matches the search condition
// the value is irrelevant for this type of attribute
static void ast_exists_ast_misc_attr_callback(
CSTR misc_attr_prefix,
CSTR misc_attr_name,
ast_node *ast_misc_attr_values,
void *_Nullable context)
{
misc_attrs_type* misc = (misc_attrs_type*) context;
// First make sure that there is a prefix and name and that they match
if (misc_attr_prefix &&
misc_attr_name &&
!Strcasecmp(misc_attr_prefix, "cql") &&
!Strcasecmp(misc_attr_name, misc->attribute_name)) {
misc->count++;
}
}
// Helper function to extract the specified string type attribute (if any) from the misc attributes
// provided, and invoke the callback function
cql_noexport uint32_t exists_attribute_str(ast_node *_Nullable misc_attr_list, const char *attribute_name)
{
if (!misc_attr_list) {
return 0;
}
Contract(is_ast_misc_attrs(misc_attr_list));
misc_attrs_type misc = {
.str_node_callback = NULL,
.context = NULL,
.attribute_name = attribute_name,
.count = 0,
};
find_misc_attrs(misc_attr_list, ast_exists_ast_misc_attr_callback, &misc);
return misc.count;
}
// Helper function to extract the ok_table_scan nodes (if any) from the misc attributes
// provided, and invoke the callback function.
cql_noexport uint32_t find_ok_table_scan(
ast_node *_Nonnull list,
find_ast_str_node_callback _Nonnull callback,
void *_Nullable context)
{
return find_attribute_str(list, callback, context, "ok_table_scan");
}
// Helper function to extract the auto-drop nodes (if any) from the misc attributes
// provided, and invoke the callback function.
cql_noexport uint32_t find_autodrops(
ast_node *_Nonnull list,
find_ast_str_node_callback _Nonnull callback,
void *_Nullable context)
{
return find_attribute_str(list, callback, context, "autodrop");
}
// Helper function to extract the identity columns (if any) from the misc attributes
// provided, and invoke the callback function
cql_noexport uint32_t find_identity_columns(
ast_node *_Nullable misc_attr_list,
find_ast_str_node_callback _Nonnull callback,
void *_Nullable context)
{
return find_attribute_str(misc_attr_list, callback, context, "identity");
}
// Helper function to extract the shared fragment node (if any) from the misc attributes
// provided, and invoke the callback function.
cql_noexport uint32_t find_shared_fragment_attr(
ast_node *_Nullable misc_attr_list)
{
Contract(is_ast_misc_attrs(misc_attr_list));
misc_attrs_type misc = {
.presence_only = 1,
.attribute_name = "shared_fragment",
.count = 0,
};
find_misc_attrs(misc_attr_list, ast_find_ast_misc_attr_callback, &misc);
return misc.count;
}
// Helper function to extract the blob storage node (if any) from the misc attributes
// provided, and invoke the callback function.
cql_noexport bool_t find_blob_storage_attr(
ast_node *_Nullable misc_attr_list)
{
Contract(is_ast_misc_attrs(misc_attr_list));
misc_attrs_type misc = {
.presence_only = 1,
.attribute_name = "blob_storage",
.count = 0,
};
find_misc_attrs(misc_attr_list, ast_find_ast_misc_attr_callback, &misc);
return misc.count != 0;
}
// Helper function to extract the base fragment node (if any) from the misc attributes
// provided, and invoke the callback function.
cql_noexport uint32_t find_base_fragment_attr(
ast_node *_Nullable misc_attr_list,
find_ast_str_node_callback _Nullable callback,
void *_Nullable context)
{
return find_attribute_str(misc_attr_list, callback, context, "base_fragment");
}
// Helper function to extract the extension fragment node (if any) from the misc attributes
// provided, and invoke the callback function.
cql_noexport uint32_t find_extension_fragment_attr(
ast_node *_Nullable misc_attr_list,
find_ast_str_node_callback _Nullable callback,
void *_Nullable context)
{
return find_attribute_str(misc_attr_list, callback, context, "extension_fragment");
}
// Helper function to extract the assembled fragment node (if any) from the misc attributes
// provided, and invoke the callback function.
cql_noexport uint32_t find_assembly_query_attr(
ast_node *_Nullable misc_attr_list,
find_ast_str_node_callback _Nullable callback,
void *_Nullable context)
{
return find_attribute_str(misc_attr_list, callback, context, "assembly_fragment");
}
// Keep record of the assembly query fragment for result set type reference if
// we are presently emitting an extension fragment stored proc
cql_data_defn ( CSTR base_fragment_name );
// The name of the base fragment is used as part of the field getters names
// we get it from the attribute associated with whichever fragment we're looking at
// semantic rules ensure these are consistent. The base fragment name basically
// let's us tie together extensions so we know which ones are part of which query
// and then we put those together. This function is the callback used to harvest
// the base fragment name from wherever we found it. Each fragment has it.
static void cg_set_base_fragment_name(CSTR _Nonnull name, ast_node *_Nonnull _misc_attr, void *_Nullable _context) {
if (_context) {
CSTR *base_name = (CSTR *)_context;
*base_name = name;
}
}
// Look for the assembly fragment annotations, return the type or MIXED if
// there is no unique type. This is used to find cases where the same attribute
// is present more than once or different/incompatible attributes are present
cql_noexport uint32_t find_fragment_attr_type(ast_node *_Nullable misc_attr_list, CSTR *_Nullable base_name) {
if (!misc_attr_list) {
base_fragment_name = NULL;
return FRAG_TYPE_NONE;
}
uint32_t base = find_base_fragment_attr(misc_attr_list, cg_set_base_fragment_name, base_name);
uint32_t extension = find_extension_fragment_attr(misc_attr_list, cg_set_base_fragment_name, base_name);
uint32_t assembly = find_assembly_query_attr(misc_attr_list, cg_set_base_fragment_name, base_name);
uint32_t shared = find_shared_fragment_attr(misc_attr_list);
if (base + extension + assembly + shared > 1) {
return FRAG_TYPE_MIXED;
}
if (base + extension + assembly + shared == 0) {
return FRAG_TYPE_NONE;
}
if (shared) {
return FRAG_TYPE_SHARED;
}
if (base) {
return FRAG_TYPE_BASE;
}
if (extension) {
return FRAG_TYPE_EXTENSION;
}
Invariant(assembly);
return FRAG_TYPE_ASSEMBLY;
}
// helper to get the fragment type of a given procedure
cql_noexport uint32_t find_proc_frag_type(ast_node *ast) {
Contract(is_ast_create_proc_stmt(ast) || is_ast_declare_proc_stmt(ast));
EXTRACT_MISC_ATTRS(ast, misc_attrs);
return find_fragment_attr_type(misc_attrs, NULL);
}
// helper to get the fragment type of a given procedure
cql_noexport bool_t is_table_blob_storage(ast_node *ast) {
Contract(is_ast_create_table_stmt(ast) || is_ast_create_virtual_table_stmt(ast));
EXTRACT_MISC_ATTRS(ast, misc_attrs);
return misc_attrs && find_blob_storage_attr(misc_attrs);
}
// This can be easily called in the debugger
cql_noexport void print_root_ast(ast_node *node) {
print_ast(node, NULL, 0, false);
}
cql_noexport void print_ast(ast_node *node, ast_node *parent, int32_t pad, bool_t flip) {
if (pad == 0) {
padbuffer[0] = '\0';
}
if (!node) {
return;
}
if (print_ast_value(node)) {
return;
}
if (is_ast_stmt_list(parent) && is_ast_stmt_list(node)) {
print_ast(node->left, node, pad, !node->right);
print_ast(node->right, node, pad, 1);
}
else {
if (pad == 2) {
cql_output("\n");
if (parent && is_ast_stmt_list(parent)) {
cql_output("The statement ending at line %d\n\n", node->lineno);
gen_stmt_level = 1;
EXTRACT_STMT_AND_MISC_ATTRS(stmt, misc_attrs, parent);
if (misc_attrs) {
gen_misc_attrs_to_stdout(misc_attrs);
}
gen_one_stmt_to_stdout(stmt);
cql_output("\n");
#if defined(CQL_AMALGAM_LEAN) && !defined(CQL_AMALGAM_SEM)
// sem off, nothing to print here
#else
// print any error text
if (stmt->sem && stmt->sem->sem_type == SEM_TYPE_ERROR && stmt->sem->error) {
cql_output("%s\n", stmt->sem->error);
}
#endif
}
}
print_ast_type(node);
if (flip && pad >= 2) {
padbuffer[pad-2] = ' ';
}
if (pad == 0) {
padbuffer[pad] = ' ';
}
else {
padbuffer[pad] = '|';
}
padbuffer[pad+1] = ' ';
padbuffer[pad+2] = '\0';
print_ast(node->left, node, pad+2, !node->right);
print_ast(node->right, node, pad+2, 1);
padbuffer[pad] = '\0';
}
}
// Clone a ast tree by the given root ast_node.
// The sems of all nodes are NULL in the new tree.
cql_noexport ast_node *copy_ast_tree(ast_node *_Nonnull node) {
Contract(node);
ast_node *new_node = NULL;
// Note: ast_has_left includes the is_primitive check so it's safe to do on leaf nodes too
// even though they have no left/right at all.
ast_node *left_node = NULL;
ast_node *right_node = NULL;
if (ast_has_left(node)) {
left_node = copy_ast_tree(node->left);
}
if (ast_has_right(node)) {
right_node = copy_ast_tree(node->right);
}
AST_REWRITE_INFO_SET(node->lineno, node->filename);
if (is_ast_num(node)) {
EXTRACT_NUM_TYPE(num_type, node);
EXTRACT_NUM_VALUE(val, node);
new_node = new_ast_num(num_type, val);
} else if (is_ast_int(node)) {
EXTRACT_OPTION(value, node);
new_node = new_ast_opt(value);
} else if (is_ast_blob(node)) {
EXTRACT_BLOBTEXT(value, node);
new_node = new_ast_blob(value);
} else if (is_ast_str(node)) {
EXTRACT_STRING(value, node);
new_node = new_ast_str(value);
} else {
new_node = new_ast(node->type, left_node, right_node);
}
AST_REWRITE_INFO_RESET();
Invariant(new_node);
return new_node;
}