sources/cg_java.c (439 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 "Java".
#if defined(CQL_AMALGAM_LEAN) && !defined(CQL_AMALGAM_JAVA)
// stubs to avoid link errors
cql_noexport void cg_java_main(ast_node *head) {}
cql_noexport void cg_java_cleanup() {}
#else
#include "cg_java.h"
#include <stdint.h>
#include "ast.h"
#include "cg_common.h"
#include "charbuf.h"
#include "cql.h"
#include "gen_sql.h"
#include "list.h"
#include "sem.h"
#include "symtab.h"
typedef struct cg_java_context {
// Buffer to hold the accumulated offsets for fragment core columns to be used by extensions.
charbuf *frag_col_offsets_for_core;
// Increment on each extension fragment SP to supply total count for assembly query to process
// column offsets
uint32_t frag_col_offset_count;
// Table that maps a fragment name to the interfaces that the assembly will implement. These
// are collected from the in-scope base and extension fragments that have been parsed before
// the assembly.
symtab *frag_assembly_interfaces;
// Table that maps a fragment name to the base interface that the extension will extend.
symtab *frag_extension_interfaces;
// In the Java codegen pipeline, we support only one SP per codegen run. This is to acccomodate
// the fact that in Java we can only generate one top level public class per file
uint32_t generated_proc_count;
} cg_java_context;
// Helper for assembly query to codegen static offset getter function for its extensions to call
// - first extension has a column offset of 0 since all of its columns start right after core
// - for following extension, its offset is the total number of fragment specific columns for all previous fragments
static void cg_java_ext_col_offsets_in_asm(charbuf *body, uint32_t col_count_for_base, cg_java_context *java_context) {
bprintf(body,
"private static final Map<Long, Integer> fragmentColOffsetsForCore = new HashMap<>();\n"
"static {\n");
bindent(body, java_context->frag_col_offsets_for_core, 2);
bprintf(body,
"}\n"
"\n"
"public static int getExtensionColOffset(Long fragmentCRC) {\n"
" Integer offset = fragmentColOffsetsForCore.get(fragmentCRC);\n"
"\n"
" if (offset == null) {\n"
" throw new RuntimeException(\"Invalid CQL fragment CRC \" + fragmentCRC);\n"
" }\n"
"\n"
" return offset;\n"
"}\n"
"\n");
}
static bool_t cg_java_frag_type_query_proc(uint32_t frag_type) {
Contract(frag_type != FRAG_TYPE_EXTENSION);
return frag_type != FRAG_TYPE_BASE;
}
static void cg_java_getter_sig(charbuf *buf, CSTR return_type, CSTR name, CSTR params) {
bprintf(buf, "public %s %s(%s)", return_type, name, params);
}
static void cg_java_proc_result_set_getter(
bool_t fetch_proc,
CSTR name,
CSTR col_name,
int32_t col,
charbuf *java,
sem_t sem_type,
bool_t encode,
bool_t custom_type_for_encoded_column,
uint32_t frag_type,
uint32_t col_count_for_base)
{
Contract(is_unitary(sem_type));
Contract(core_type_of(sem_type) != SEM_TYPE_NULL);
Contract(frag_type != FRAG_TYPE_SHARED);
Contract(frag_type != FRAG_TYPE_EXTENSION);
bool_t notnull = is_not_nullable(sem_type);
sem_t core_type = core_type_of(sem_type);
CSTR return_type;
CSTR field_type;
CSTR prefix = "get_";
CSTR nullable_prefix = notnull ? "" : "_nullable_";
CSTR nullable_attr = notnull ? "" : "@Nullable\n";
switch (core_type) {
case SEM_TYPE_INTEGER:
if (notnull) {
return_type = rt->cql_int32;
} else {
return_type = rt->cql_int32_nullable;
}
field_type = rt->cql_int32_nullable;
break;
case SEM_TYPE_TEXT:
nullable_prefix = "";
if (encode && custom_type_for_encoded_column) {
return_type = rt->cql_string_ref_encode;
field_type = rt->cql_string_ref_encode;
} else {
return_type = rt->cql_string_ref;
field_type = rt->cql_string_ref;
}
break;
case SEM_TYPE_LONG_INTEGER:
if (notnull) {
return_type = rt->cql_int64;
} else {
return_type = rt->cql_int64_nullable;
}
field_type = rt->cql_int64_nullable;
break;
case SEM_TYPE_REAL:
if (notnull) {
return_type = rt->cql_double;
} else {
return_type = rt->cql_double_nullable;
}
field_type = rt->cql_double_nullable;
break;
case SEM_TYPE_BOOL:
if (notnull) {
prefix = "";
return_type = rt->cql_bool;
} else {
return_type = rt->cql_bool_nullable;
}
field_type = rt->cql_bool_nullable;
break;
case SEM_TYPE_BLOB:
nullable_prefix = "";
return_type = rt->cql_blob_ref;
field_type = "Blob";
break;
}
CG_CHARBUF_OPEN_SYM(col_name_camel, prefix, col_name);
CG_CHARBUF_OPEN_SYM(method_name, nullable_prefix, field_type);
CHARBUF_OPEN(col_index);
bprintf(&col_index, "%d", col);
bprintf(java, nullable_attr);
CHARBUF_OPEN(getter_sig);
// patternlint-disable-next-line prefer-sized-ints-in-msys
cg_java_getter_sig(&getter_sig, return_type, col_name_camel.ptr, fetch_proc ? "" : "int row");
if (!options.java_fragment_interface_mode || cg_java_frag_type_query_proc(frag_type)) {
bprintf(java,
rt->cql_result_set_get_data,
getter_sig.ptr,
method_name.ptr,
fetch_proc ? "0" : "row",
col_index.ptr);
} else {
bprintf(java, "%s;\n\n", getter_sig.ptr);
}
if (encode) {
bprintf(java, "public %s %sIsEncoded() {\n", rt->cql_bool, col_name_camel.ptr);
bprintf(java, " return mResultSet.getIsEncoded(%s);\n", col_index.ptr);
bprintf(java, "}\n\n");
}
CHARBUF_CLOSE(getter_sig);
CHARBUF_CLOSE(col_index);
CHARBUF_CLOSE(method_name);
CHARBUF_CLOSE(col_name_camel);
}
static void no_op(CSTR _Nonnull name, ast_node *_Nonnull attr, void *_Nullable context) {
return;
}
static void cg_java_validate_proc_count(cg_java_context *java_context, uint32_t frag_type) {
if (java_context->generated_proc_count == 1 && frag_type == FRAG_TYPE_NONE) {
// We've already generated a Java SP. More SPs are not allowed unless
// this is for either assembly query or fragments supplied for it
cql_error(
"The Java code generator only supports one stored procedure per file. "
"Multiple procedures were found while generating %s\n",
options.file_names[0]
);
cql_cleanup_and_exit(1);
}
}
static void cg_java_fragment_columns(
CSTR name,
cg_java_context *java_context,
uint32_t frag_type,
uint32_t *count,
uint32_t *col_count_for_base)
{
Contract(frag_type != FRAG_TYPE_SHARED);
Contract(frag_type != FRAG_TYPE_EXTENSION);
if (frag_type != FRAG_TYPE_NONE) {
// 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);
*col_count_for_base = base_proc->sem->sptr->count;
}
*count = frag_type == FRAG_TYPE_ASSEMBLY ? *col_count_for_base : *count;
}
static void cg_java_write_implements_interface(
charbuf *buf,
CSTR name,
cg_java_context *java_context,
uint32_t frag_type)
{
Contract(frag_type != FRAG_TYPE_NONE);
Contract(frag_type != FRAG_TYPE_SHARED);
Contract(frag_type != FRAG_TYPE_EXTENSION);
CHARBUF_OPEN(interfaces);
if (options.java_fragment_interface_mode) {
Invariant(base_fragment_name);
// Get the interface buffers
charbuf *frag_extension_interface;
frag_extension_interface = symtab_ensure_charbuf(java_context->frag_extension_interfaces, base_fragment_name);
charbuf *frag_assembly_interface;
frag_assembly_interface = symtab_ensure_charbuf(java_context->frag_assembly_interfaces, base_fragment_name);
if (frag_type == FRAG_TYPE_BASE) {
// The current class name should be registered to both the extension and assembly interfaces. There should
// be nothing in the interface buffers, as the base extension is the first declared semantically.
Invariant(frag_extension_interface->used == 1);
Invariant(frag_assembly_interface->used == 1);
cg_sym_name(cg_symbol_case_pascal, frag_extension_interface, "", name, NULL);
cg_sym_name(cg_symbol_case_pascal, frag_assembly_interface, "", name, NULL);
}
else if (frag_type == FRAG_TYPE_ASSEMBLY) {
// This interface extends the base interface.
bprintf(&interfaces, "%s", frag_assembly_interface->ptr);
}
}
if (interfaces.used > 1) {
bprintf(buf, " implements %s", interfaces.ptr);
}
CHARBUF_CLOSE(interfaces);
}
static void cg_java_write_imports(charbuf *buf, uint32_t frag_type) {
uint32_t used = buf->used;
if (!options.java_fragment_interface_mode) {
if (frag_type == FRAG_TYPE_ASSEMBLY) {
bprintf(buf,
"import java.util.HashMap;\n"
"import java.util.Map;\n");
}
}
if (buf->used != used) {
bprintf(buf, "\n");
}
}
static void cg_java_write_fragment_class_accessors(
charbuf *buf,
CSTR name,
uint32_t frag_type,
cg_java_context *java_context,
uint32_t col_count_for_base)
{
if (frag_type == FRAG_TYPE_ASSEMBLY) {
cg_java_ext_col_offsets_in_asm(buf, col_count_for_base, java_context);
// if is an assembly query we need to expose the resultset to instantiate the fragments from it.
bprintf(buf, "public CQLResultSet toFragment() {\n");
bprintf(buf, " return mResultSet;\n");
bprintf(buf, "}\n\n");
}
}
static void cg_java_write_class_or_interface(
charbuf *buf,
uint32_t frag_type,
charbuf *name,
charbuf *implements_interface,
charbuf *body)
{
if (!options.java_fragment_interface_mode || cg_java_frag_type_query_proc(frag_type)) {
bprintf(buf,
"public final class %s extends CQLViewModel%s {\n\n",
name->ptr,
implements_interface->ptr);
} else {
bprintf(buf,
"public interface %s%s {\n\n",
name->ptr,
implements_interface->ptr);
}
bindent(buf, body, 2);
bprintf(buf, "}\n");
}
static void cg_java_proc_result_set(ast_node *ast, cg_java_context *java_context) {
EXTRACT_MISC_ATTRS(ast, misc_attrs);
EXTRACT_STRING(name, ast->left);
// if getters are suppressed the entire class is moot
// if result set is suppressed the entire class is moot
// private implies result set suppressed so also moot
bool_t suppressed =
exists_attribute_str(misc_attrs, "suppress_getters") ||
exists_attribute_str(misc_attrs, "suppress_result_set") ||
exists_attribute_str(misc_attrs, "private");
if (suppressed) {
return;
}
Invariant(!use_encode);
Invariant(!encode_context_column);
Invariant(!encode_columns);
encode_columns = symtab_new();
init_encode_info(misc_attrs, &use_encode, &encode_context_column, encode_columns);
bool_t custom_type_for_encoded_column = !!exists_attribute_str(misc_attrs, "custom_type_for_encoded_column");
uint32_t frag_type = find_fragment_attr_type(misc_attrs, &base_fragment_name);
bool_t is_query_proc = cg_java_frag_type_query_proc(frag_type);
cg_java_validate_proc_count(java_context, frag_type);
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);
sem_struct *sptr = ast->sem->sptr;
uint32_t count = sptr->count;
uint32_t col_count_for_base = 0;
if (!options.java_fragment_interface_mode) {
cg_java_fragment_columns(name, java_context, frag_type, &count, &col_count_for_base);
}
CHARBUF_OPEN(body);
CHARBUF_OPEN(class_name);
extract_base_path_without_extension(&class_name, options.file_names[0]);
CHARBUF_OPEN(implements_interface);
if (options.java_fragment_interface_mode && frag_type != FRAG_TYPE_NONE) {
cg_java_write_implements_interface(&implements_interface, name, java_context, frag_type);
}
bool_t out_stmt_proc = has_out_stmt_result(ast);
// Stored procedure name constant does not make sense for extension fragments since the application layer
// that coordinates access to them will be dealing with assembly
if (is_query_proc) {
bprintf(&body, "public static final String STORED_PROCEDURE_NAME = \"%s\";\n\n", name);
}
if (!options.java_fragment_interface_mode) {
cg_java_write_fragment_class_accessors(&body, name, frag_type, java_context, col_count_for_base);
}
if (!options.java_fragment_interface_mode || is_query_proc) {
bprintf(&body,
"public %s(CQLResultSet resultSet) {\n"
" super(resultSet);\n"
"}\n\n",
class_name.ptr);
}
bool_t is_string_column_encoded = false;
// 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];
bool_t encode = should_encode_col(col, sem_type, use_encode, encode_columns);
is_string_column_encoded += custom_type_for_encoded_column && encode && core_type_of(sem_type) == SEM_TYPE_TEXT;
cg_java_proc_result_set_getter(
out_stmt_proc,
name,
col,
i,
&body,
sem_type,
encode,
custom_type_for_encoded_column,
frag_type,
col_count_for_base);
}
CG_CHARBUF_OPEN_SYM(get_count, "", "get_count");
CHARBUF_OPEN(get_count_sig);
cg_java_getter_sig(&get_count_sig, rt->cql_int32, get_count.ptr, "");
if (!options.java_fragment_interface_mode || is_query_proc) {
bprintf(&body, rt->cql_result_set_get_count, get_count_sig.ptr);
} else {
bprintf(&body, "%s;\n\n", get_count_sig.ptr);
}
bool_t generate_copy = misc_attrs && exists_attribute_str(misc_attrs, "generate_copy");
if (generate_copy) {
bprintf(&body, rt->cql_result_set_copy, class_name.ptr, class_name.ptr);
}
if (!options.java_fragment_interface_mode || is_query_proc) {
bool_t include_identity_columns = misc_attrs != NULL ? find_identity_columns(misc_attrs, no_op, NULL) != 0 : 0;
bprintf(&body, rt->cql_result_set_has_identity_columns, include_identity_columns ? "true" : "false");
}
CHARBUF_OPEN(cg_java_output);
CSTR custom_class_import = is_string_column_encoded ? rt->cql_string_ref_encode_include : "";
bprintf(&cg_java_output, "%s", rt->source_prefix);
bprintf(&cg_java_output, rt->source_wrapper_begin, options.java_package_name, custom_class_import);
cg_java_write_imports(&cg_java_output, frag_type);
cg_java_write_class_or_interface(
&cg_java_output,
frag_type,
&class_name,
&implements_interface,
&body);
cql_write_file(options.file_names[0], cg_java_output.ptr);
CHARBUF_CLOSE(cg_java_output);
CHARBUF_CLOSE(get_count_sig);
CHARBUF_CLOSE(get_count);
CHARBUF_CLOSE(implements_interface);
CHARBUF_CLOSE(class_name);
CHARBUF_CLOSE(body);
java_context->generated_proc_count++;
use_encode = 0;
symtab_delete(encode_columns);
encode_columns = NULL;
encode_context_column = NULL;
}
static void cg_java_create_proc_stmt(ast_node *ast, cg_java_context *java_context) {
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);
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 (result_set_proc || out_stmt_proc || out_union_proc) {
sem_struct *sptr = ast->sem->sptr;
uint32_t count = sptr->count;
for (int32_t i = 0; i < count; i++) {
sem_t sem_type = sptr->semtypes[i];
// resultsets with objects in java are not supported
if (core_type_of(sem_type) == SEM_TYPE_OBJECT) {
cql_error("out cursors with object columns are not yet supported for java\n");
cql_cleanup_and_exit(1);
}
}
cg_java_proc_result_set(ast, java_context);
}
}
// java codegen only deals with the create proc statement so use an easy dispatch
static void cg_java_one_stmt(ast_node *stmt, cg_java_context *java_context) {
if (is_ast_create_proc_stmt(stmt)) {
cg_java_create_proc_stmt(stmt, java_context);
}
}
static void cg_java_stmt_list(ast_node *head, cg_java_context *java_context) {
uint32_t frag_type = FRAG_TYPE_NONE;
for (ast_node *ast = head; ast; ast = ast->right) {
EXTRACT_STMT_AND_MISC_ATTRS(stmt, misc_attrs, ast);
frag_type = find_fragment_attr_type(misc_attrs, NULL);
if (frag_type == FRAG_TYPE_SHARED) {
// shared fragment types generate no code ever
continue;
}
if (frag_type == FRAG_TYPE_EXTENSION) {
// shared fragment types generate no code ever
continue;
}
if (!options.java_fragment_interface_mode) {
// skiping the base fragment getters since generating in each extension
// will cause collisions including two fragments headers
if (frag_type == FRAG_TYPE_BASE) {
continue;
}
}
cg_java_one_stmt(stmt, java_context);
}
}
static void cg_java_init(void) {
cg_common_init();
}
// Main entry point for code-gen.
cql_noexport void cg_java_main(ast_node *head) {
// this is verified by the generic code
Invariant(options.file_names_count == 1);
if (!options.java_package_name) {
cql_error("A java package name must be specified (use --java_package_name)\n");
cql_cleanup_and_exit(1);
}
cql_exit_on_semantic_errors(head);
exit_on_validating_schema();
cg_java_init();
// gen java code ....
symtab *frag_extension_interfaces = symtab_new();
symtab *frag_assembly_interfaces = symtab_new();
CHARBUF_OPEN(frag_col_offsets_for_core);
cg_java_context java_context = {
.frag_col_offsets_for_core = &frag_col_offsets_for_core,
.frag_assembly_interfaces = frag_assembly_interfaces,
.frag_extension_interfaces = frag_extension_interfaces,
};
cg_java_stmt_list(head, &java_context);
CHARBUF_CLOSE(frag_col_offsets_for_core);
symtab_delete(frag_assembly_interfaces);
symtab_delete(frag_extension_interfaces);
}
cql_noexport void cg_java_cleanup() {
SYMTAB_CLEANUP( encode_columns );
}
#endif