sources/cqlrt_common.c (2,465 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.
*/
// Note: the terms "rowset" and "result set" are used pretty much interchangebly to mean the same thing.
#include <stdlib.h>
#if defined(TARGET_OS_LINUX) && TARGET_OS_LINUX
#include <alloca.h>
#endif // TARGET_OS_LINUX
#ifndef STACK_BYTES_ALLOC
#if defined(TARGET_OS_WIN32) && TARGET_OS_WIN32
#define STACK_BYTES_ALLOC(N, C) char *N = (char *)_alloca(C)
#elif defined(TARGET_OS_LINUX) && TARGET_OS_LINUX
#define STACK_BYTES_ALLOC(N, C) char *N = (char *)alloca(C)
#else // TARGET_OS_WIN32
#define STACK_BYTES_ALLOC(N, C) char N[C]
#endif // TARGET_OS_WIN32
#endif // STACK_BYTES_ALLLOC
// This code is used in the event of a THROW inside a stored proc. When that happens
// we want to keep the result code we have if there was a recent error. If we recently
// got a success, then use SQLITE_ERROR as the thrown error instead.
cql_code cql_best_error(cql_code rc) {
if (rc == SQLITE_OK || rc == SQLITE_DONE || rc == SQLITE_ROW) {
return SQLITE_ERROR;
}
return rc;
}
// This code overrides the CQL_DATA_TYPE_ENCODED bit in a result_set's data_type. It's used
// indirectly by app at runtime to control the encoding and decoding of the column value.
void cql_set_encoding(uint8_t *_Nonnull data_types, cql_int32 count, cql_int32 col, cql_bool encode) {
cql_contract(col < count);
if (encode) {
data_types[col] |= CQL_DATA_TYPE_ENCODED;
} else {
data_types[col] &= ~CQL_DATA_TYPE_ENCODED;
}
}
// The indicated statement should be immediately finalized out latest result was not SQLITE_OK
// This code is used during binding (which is now always done with multibind)
// in order to ensure that the statement exits finalized in the event of any binding failure.
void cql_finalize_on_error(cql_code rc, sqlite3_stmt *_Nullable *_Nonnull pstmt) {
cql_contract(pstmt && *pstmt);
if (rc != SQLITE_OK) {
cql_finalize_stmt(pstmt);
}
}
// This method is used when handling CQL cursors; the cursor local variable may already
// contain a statement. When preparing a new statement, we want to finalize any statement
// the cursor used to hold. This lets us do simple preparation in a loop without added
// conditionals in the generated code.
cql_code cql_prepare(sqlite3 *_Nonnull db, sqlite3_stmt *_Nullable *_Nonnull pstmt, const char *_Nonnull sql) {
cql_finalize_stmt(pstmt);
return cql_sqlite3_prepare_v2(db, sql, -1, pstmt, NULL);
}
// create a single string from the varargs and count provided
static char *_Nonnull cql_vconcat(cql_int32 count, const char *_Nullable preds, va_list *_Nonnull args) {
va_list pass1, pass2;
va_copy(pass1, *args);
va_copy(pass2, *args);
cql_int32 bytes = 0;
// first we have to figure out how much to allocate
for (cql_int32 istr = 0; istr < count; istr++) {
const char *str = va_arg(pass1, const char *);
if (!preds || preds[istr]) {
bytes += strlen(str);
}
}
char *result = malloc(bytes + 1);
cql_int32 offset = 0;
for (cql_int32 istr = 0; istr < count; istr++) {
const char *str = va_arg(pass2, const char *);
if (!preds || preds[istr]) {
size_t len = strlen(str);
memcpy(result + offset, str, len+1); // copies the trailing null byte
offset += len;
}
}
va_end(pass1);
va_end(pass2);
return result;
}
// This method is used when handling CQL cursors; the cursor local variable may already
// contain a statement. When preparing a new statement, we want to finalize any statement
// the cursor used to hold. This lets us do simple preparation in a loop without added
// conditionals in the generated code. This is the varargs version
cql_code cql_prepare_var(
sqlite3 *_Nonnull db,
sqlite3_stmt *_Nullable *_Nonnull pstmt,
cql_int32 count,
const char *_Nullable preds, ...)
{
cql_finalize_stmt(pstmt);
va_list args;
va_start(args, preds);
char *sql = cql_vconcat(count, preds, &args);
cql_code result = cql_sqlite3_prepare_v2(db, sql, -1, pstmt, NULL);
va_end(args);
free(sql);
return result;
}
// This is a simple wrapper for the sqlite3_exec method with the usual extra arguments.
// This code is here just to reduce the code size of exec calls in the generated code.
// There are a lot of such calls.
cql_code cql_exec(sqlite3 *_Nonnull db, const char *_Nonnull sql) {
return cql_sqlite3_exec(db, sql);
}
// This is a simple wrapper for the sqlite3_exec method with the usual extra arguments.
// This code is here just to reduce the code size of exec calls in the generated code.
// There are a lot of such calls.
cql_code cql_exec_var(sqlite3 *_Nonnull db, cql_int32 count, const char *_Nullable preds,...) {
va_list args;
va_start(args, preds);
char *sql = cql_vconcat(count, preds, &args);
cql_code result = cql_sqlite3_exec(db, sql);
va_end(args);
free(sql);
return result;
}
// This version of exec takes a string variable and is therefore more dangerous. It is
// only intended to be used in the context of schema maintenance or other cases where
// there are highly compressible patterns (like DROP TRIGGER %s for 1000s of triggers).
// All we do is convert the incoming string reference into a C string and then exec it.
CQL_WARN_UNUSED cql_code cql_exec_internal(sqlite3 *_Nonnull db, cql_string_ref _Nonnull str_ref) {
cql_alloc_cstr(temp, str_ref);
cql_code rc = cql_sqlite3_exec(db, temp);
cql_free_cstr(temp, str_ref);
return rc;
}
char *_Nonnull cql_address_of_col(
cql_result_set_ref _Nonnull result_set,
cql_int32 row,
cql_int32 col,
cql_int32 *_Nonnull type);
// The variable byte encoding is little endian, you stop when you reach
// a byte that does not have the high bit set. This is good enough for 2^28 bits
// in four bytes which is more than enough for sql strings...
static const char *_Nonnull cql_decode(const char *_Nonnull data, int32_t *_Nonnull result) {
int32_t out = 0;
int32_t byte;
int32_t offset = 0;
do {
byte = *data++;
out |= (byte & 0x7f) << offset;
offset += 7;
} while (byte & 0x80);
*result = out;
return data;
}
// The base pointer contains the address of the string part
// Each fragment is variable length encoded as above with a +1 on the offset
// If an offset of 0 is encountered, that means stop.
// Since the fragements are represented as a string, that means the normal
// null terminator in the string is the stop signal.
static void cql_expand_frags(
char *_Nonnull result,
const char *_Nonnull base,
const char *_Nonnull frags)
{
int32_t offset;
for (;;) {
frags = cql_decode(frags, &offset);
if (offset == 0) {
break;
}
const char *src = base + offset - 1;
while (*src) {
*result++ = *src++;
}
}
*result = 0;
}
// To keep the contract as simple as possible we encode everything we
// need into the fragment array. Including the size of the output
// and fragment terminator. See above. This also makes the code
// gen as simple as possible.
cql_code cql_prepare_frags(
sqlite3 *_Nonnull db,
sqlite3_stmt *_Nullable *_Nonnull pstmt,
const char *_Nonnull base,
const char *_Nonnull frags)
{
// NOTE: len is the allocation size (includes trailing \0)
cql_finalize_stmt(pstmt);
int32_t len;
frags = cql_decode(frags, &len);
STACK_BYTES_ALLOC(sql, len);
cql_expand_frags(sql, base, frags);
return cql_sqlite3_prepare_v2(db, sql, len, pstmt, NULL);
}
// To keep the contract as simple as possible we encode everything we
// need into the fragment array. Including the size of the output
// and fragment terminator. See above. This also makes the code
// gen as simple as possible.
cql_code cql_exec_frags(
sqlite3 *_Nonnull db,
const char *_Nonnull base,
const char *_Nonnull frags)
{
// NOTE: len is the allocation size (includes trailing \0)
int32_t len;
frags = cql_decode(frags, &len);
STACK_BYTES_ALLOC(sql, len);
cql_expand_frags(sql, base, frags);
return cql_sqlite3_exec(db, sql);
}
// Finalizes the statement if it is not null. Note that the statement pointer
// must be not null but the statement it holds may or may not be initialized.
// Also note that ALL CQL STATEMENTS ARE INITIALIZED TO NULL!!
void cql_finalize_stmt(sqlite3_stmt *_Nullable *_Nonnull pstmt) {
cql_contract(pstmt);
if (*pstmt) {
cql_sqlite3_finalize(*pstmt);
*pstmt = NULL;
}
}
// Read a nullable bool from the statement at the indicated index.
// If the column is null then return null.
// If not null then return the value.
// This is used in the general purpose column readers cql_multifetch and cql_multifetch_meta.
// to get column access to bools without having to open code the null check every time.
void cql_column_nullable_bool(
sqlite3_stmt *_Nonnull stmt,
cql_int32 index,
cql_nullable_bool *_Nonnull data)
{
if (sqlite3_column_type(stmt, index) == SQLITE_NULL) {
cql_set_null(*data);
}
else {
cql_set_notnull(*data, !!sqlite3_column_int(stmt, index));
}
}
// Read a nullable int32 from the statement at the indicated index.
// If the column is null then return null.
// If not null then return the value.
// This is used in the general purpose column readers cql_multifetch and cql_multifetch_meta.
// to get column access to int32s without having to open code the null check every time.
void cql_column_nullable_int32(
sqlite3_stmt *_Nonnull stmt,
cql_int32 index,
cql_nullable_int32 *_Nonnull data)
{
if (sqlite3_column_type(stmt, index) == SQLITE_NULL) {
cql_set_null(*data);
}
else {
cql_set_notnull(*data, sqlite3_column_int(stmt, index));
}
}
// Read a nullable int64 from the statement at the indicated index.
// If the column is null then return null.
// If not null then return the value.
// This is used in the general purpose column readers cql_multifetch and cql_multifetch_meta.
// to get column access to int64s without having to open code the null check every time.
void cql_column_nullable_int64(
sqlite3_stmt *_Nonnull stmt,
cql_int32 index,
cql_nullable_int64 *_Nonnull data)
{
if (sqlite3_column_type(stmt, index) == SQLITE_NULL) {
cql_set_null(*data);
}
else {
cql_set_notnull(*data, sqlite3_column_int64(stmt, index));
}
}
// Read a nullable double from the statement at the indicated index.
// If the column is null then return null.
// If not null then return the value.
// This is used in the general purpose column readers cql_multifetch and cql_multifetch_meta.
// to get column access to doubles without having to open code the null check every time.
void cql_column_nullable_double(
sqlite3_stmt *_Nonnull stmt,
cql_int32 index,
cql_nullable_double *_Nonnull data)
{
if (sqlite3_column_type(stmt, index) == SQLITE_NULL) {
cql_set_null(*data);
}
else {
cql_set_notnull(*data, sqlite3_column_double(stmt, index));
}
}
// Read a nullable string reference from the statement at the indicated index.
// If the column is null then return null.
// If not null then return the value.
// This is used in the general purpose column readers cql_multifetch and cql_multifetch_meta.
// to get column access to strings without having to open code the null check every time.
void cql_column_nullable_string_ref(
sqlite3_stmt *_Nonnull stmt,
cql_int32 index,
cql_string_ref _Nullable *_Nonnull data)
{
// the target may already have data, release it if it does
cql_string_release(*data);
if (sqlite3_column_type(stmt, index) == SQLITE_NULL) {
*data = NULL;
}
else {
*data = cql_string_ref_new((const char *)sqlite3_column_text(stmt, index));
}
}
// Read a string reference from the statement at the indicated index.
// This is used in the general purpose column readers cql_multifetch and cql_multifetch_meta.
void cql_column_string_ref(
sqlite3_stmt *_Nonnull stmt,
cql_int32 index,
cql_string_ref _Nonnull *_Nonnull data)
{
// the target may already have data, release it if it does
cql_string_release(*data);
*data = cql_string_ref_new((const char *)sqlite3_column_text(stmt, index));
}
// Read a nullable blob reference from the statement at the indicated index.
// If the column is null then return null.
// If not null then return the value.
// This is used in the general purpose column readers cql_multifetch and cql_multifetch_meta.
// to get column access to blobs without having to open code the null check every time.
void cql_column_nullable_blob_ref(
sqlite3_stmt *_Nonnull stmt,
cql_int32 index,
cql_blob_ref _Nullable *_Nonnull data)
{
// the target may already have data, release it if it does
cql_blob_release(*data);
if (sqlite3_column_type(stmt, index) == SQLITE_NULL) {
*data = NULL;
}
else {
const void *bytes = sqlite3_column_blob(stmt, index);
cql_uint32 size = sqlite3_column_bytes(stmt, index);
*data = cql_blob_ref_new(bytes, size);
}
}
// Read a blob reference from the statement at the indicated index.
// This is used in the general purpose column readers cql_multifetch and cql_multifetch_meta.
void cql_column_blob_ref(
sqlite3_stmt *_Nonnull stmt,
cql_int32 index,
cql_blob_ref _Nonnull *_Nonnull data)
{
// the target may already have data, release it if it does
cql_blob_release(*data);
const void *bytes = sqlite3_column_blob(stmt, index);
cql_uint32 size = sqlite3_column_bytes(stmt, index);
*data = cql_blob_ref_new(bytes, size);
}
// This helper is used by CQL to set an object reference. It does the primitive retain/release operations.
// For now all the reference types are the same in this regard but there are different helpers for
// additional type safety in the generated code and readability (and breakpoints).
void cql_set_object_ref(
cql_object_ref _Nullable *_Nonnull target,
cql_object_ref _Nullable source)
{
// upcount first in case source is an alias for target
cql_object_retain(source);
cql_object_release(*target);
*target = source;
}
// This helper is used by CQL to set a string reference. It does the primitive retain/release operations.
// For now all the reference types are the same in this regard but there are different helpers for
// additional type safety in the generated code and readability (and breakpoints).
void cql_set_string_ref(
cql_string_ref _Nullable *_Nonnull target,
cql_string_ref _Nullable source)
{
// upcount first in case source is an alias for target
cql_string_retain(source);
cql_string_release(*target);
*target = source;
}
// This helper is used by CQL to set a blob reference. It does the primitive retain/release operations.
// For now all the reference types are the same in this regard but there are different helpers for
// additional type safety in the generated code and readability (and breakpoints).
void cql_set_blob_ref(
cql_blob_ref _Nullable *_Nonnull target,
cql_blob_ref _Nullable source)
{
// upcount first in case source is an alias for target
cql_blob_retain(source);
cql_blob_release(*target);
*target = source;
}
#ifdef CQL_RUN_TEST
jmp_buf *_Nullable cql_contract_argument_notnull_tripwire_jmp_buf;
#endif
// Wraps calls to `cql_tripwire` to allow us to longjmp, if required. This is
// called for both the argument itself and, in the case of an INOUT NOT NULL
// reference type argument, what the argument points to as well.
static void cql_contract_argument_notnull_tripwire(void *_Nullable ptr, cql_uint32 position) {
#ifdef CQL_RUN_TEST
if (cql_contract_argument_notnull_tripwire_jmp_buf && !ptr) {
longjmp(*cql_contract_argument_notnull_tripwire_jmp_buf, position);
}
#endif
cql_tripwire(ptr);
}
// This will be called in the case of an INOUT NOT NULL reference type argument
// to ensure that `argument` does not point to NULL. This function does not need
// per-position variants (as `DEFINE_ARGUMENT_AT_POSITION_N_MUST_NOT_BE_NULL`
// enables) as such a function will always be above this in the stack.
// `__attribute__((optnone))` is used to ensure we actually see this in stack
// traces and it doesn't get inlined or merged away.
CQL_OPT_NONE static void cql_inout_reference_type_notnull_argument_must_not_point_to_null(
void *_Nullable *_Nonnull argument,
cql_int32 position)
{
cql_contract_argument_notnull_tripwire(*argument, position);
}
// This helps us generate variants of nonnull argument enforcement for each of
// the first eight arguments. As above, `__attribute__((optnone))` prevents
// these from getting inlined or merged.
#define DEFINE_ARGUMENT_AT_POSITION_N_MUST_NOT_BE_NULL(N) \
CQL_OPT_NONE \
static void cql_argument_at_position_ ## N ## _must_not_be_null(void *_Nullable argument, cql_bool inout_notnull) { \
cql_contract_argument_notnull_tripwire(argument, N); \
if (inout_notnull) { \
cql_inout_reference_type_notnull_argument_must_not_point_to_null(argument, N); \
} \
}
DEFINE_ARGUMENT_AT_POSITION_N_MUST_NOT_BE_NULL(1);
DEFINE_ARGUMENT_AT_POSITION_N_MUST_NOT_BE_NULL(2);
DEFINE_ARGUMENT_AT_POSITION_N_MUST_NOT_BE_NULL(3);
DEFINE_ARGUMENT_AT_POSITION_N_MUST_NOT_BE_NULL(4);
DEFINE_ARGUMENT_AT_POSITION_N_MUST_NOT_BE_NULL(5);
DEFINE_ARGUMENT_AT_POSITION_N_MUST_NOT_BE_NULL(6);
DEFINE_ARGUMENT_AT_POSITION_N_MUST_NOT_BE_NULL(7);
DEFINE_ARGUMENT_AT_POSITION_N_MUST_NOT_BE_NULL(8);
CQL_OPT_NONE static void cql_argument_at_position_9_or_greater_must_not_be_null(
void *_Nullable argument,
cql_uint32 position,
cql_bool deref)
{
cql_contract_argument_notnull_tripwire(argument, position);
if (deref) {
cql_inout_reference_type_notnull_argument_must_not_point_to_null(argument, position);
}
}
// Calls a position-specific function that will call `cql_tripwire(argument)`
// (and `cql_tripwire(*argument)` when `deref` is true, as in the case of `INOUT
// arg R NOT NULL`, where `R` is some reference type). This is done so that a
// maximally informative function name will appear in stack traces.
//
// NOTE: This function takes a `position` starting from 1 instead of an `index`
// starting from 0 so that, when someone is debugging a crash, `position` will
// line up with the name of the position-specific function and not cause
// confusion. Having the "first argument" be "position 1", as opposed to "index
// 0", seems to be the most intuitive. It also makes things a bit cleaner when
// performing a longjmp during testing (because jumping with 0 is
// indistinguishable from jumping with 1).
static void cql_contract_argument_notnull_with_optional_dereference_check(
void *_Nullable argument,
cql_uint32 position,
cql_bool deref)
{
switch (position) {
case 1:
return cql_argument_at_position_1_must_not_be_null(argument, deref);
case 2:
return cql_argument_at_position_2_must_not_be_null(argument, deref);
case 3:
return cql_argument_at_position_3_must_not_be_null(argument, deref);
case 4:
return cql_argument_at_position_4_must_not_be_null(argument, deref);
case 5:
return cql_argument_at_position_5_must_not_be_null(argument, deref);
case 6:
return cql_argument_at_position_6_must_not_be_null(argument, deref);
case 7:
return cql_argument_at_position_7_must_not_be_null(argument, deref);
case 8:
return cql_argument_at_position_8_must_not_be_null(argument, deref);
default:
return cql_argument_at_position_9_or_greater_must_not_be_null(argument, position, deref);
}
}
void cql_contract_argument_notnull(void * _Nullable argument, cql_uint32 position) {
cql_contract_argument_notnull_with_optional_dereference_check(argument, position, false);
}
void cql_contract_argument_notnull_when_dereferenced(void * _Nullable argument, cql_uint32 position) {
cql_contract_argument_notnull_with_optional_dereference_check(argument, position, true);
}
// Creates a growable byte-buffer. This code is used in the creation of the data blob for a result set.
// The buffer will double in size when it would otherwise overflow resulting in at most 2N data operations
// for N rows.
void cql_bytebuf_open(cql_bytebuf *_Nonnull b) {
b->max = BYTEBUF_GROWTH_SIZE;
b->ptr = malloc(b->max);
b->used = 0;
}
// Dispenses the buffer's memory when it is closed.
void cql_bytebuf_close(cql_bytebuf *_Nonnull b) {
free(b->ptr);
b->max = 0;
b->ptr = NULL;
}
// Get more memory from the byte buffer. This will be used to get memory for each new row
// in a result set.
// Note: the data is assumed to be location independent and reference count invariant.
// (i.e. you can memcpy it safely if you then also destroy the old copy)
void *_Nonnull cql_bytebuf_alloc(cql_bytebuf *_Nonnull b, int needed) {
int avail = b->max - b->used;
if (needed > avail) {
if (b->max > BYTEBUF_EXP_GROWTH_CAP) {
b->max = needed + BYTEBUF_GROWTH_SIZE_AFTER_CAP + b->max;
} else {
b->max = needed + 2 * b->max;
}
char *newptr = malloc(b->max);
memcpy(newptr, b->ptr, b->used);
free(b->ptr);
b->ptr = newptr;
}
void *result = b->ptr + b->used;
b->used += needed;
return result;
}
// simple helper to append into a byte buffer
void cql_bytebuf_append(cql_bytebuf *_Nonnull buffer, const void *_Nonnull data, int32_t bytes) {
void *pv = cql_bytebuf_alloc(buffer, bytes);
memcpy(pv, data, bytes);
}
// This helper is used to give us a db handle we can use when faking the LIKE operator
// in the event that sqlite3_strlike is not available in the version we are using.
static cql_code _cql_get_compat_db(sqlite3 *_Nonnull *_Nonnull db) {
static sqlite3 *shared_db = NULL;
cql_code rc = SQLITE_OK;
if (!shared_db) {
rc = sqlite3_open(":memory:", &shared_db);
}
*db = shared_db;
return rc;
}
// The return value for this compat function is 0 or 1, where 0 is a match. The sqlite3_strlike function specifies
// that it returns 0 or non-0, but does not specify significance for any non-0 values, so we have the ability to just
// return 1 and still be good. A return of -1 indicates an error in executing the query (which is also a failure).
static int _cql_compat_sqlite3_strlike(
const char *_Nonnull zGlob,
const char *_Nonnull zStr,
unsigned int cEsc)
{
// The escape char is bound as a string, but matching the sqlite3_strlike signature makes this an unsigned int.
const char *zEsc = (const char *)&cEsc;
int result = -1; // failure
sqlite3 *db;
sqlite3_stmt *stmt = NULL;
int rc = _cql_get_compat_db(&db);
const char *expr = cEsc ? "SELECT like(?, ?, ?);" : "SELECT like(?,?);";
if (rc == SQLITE_OK) rc = cql_sqlite3_prepare_v2(db, expr, -1, &stmt, NULL);
if (rc == SQLITE_OK) rc = sqlite3_bind_text(stmt, 1, zGlob, -1, SQLITE_TRANSIENT);
if (rc == SQLITE_OK) rc = sqlite3_bind_text(stmt, 2, zStr, -1, SQLITE_TRANSIENT);
if (rc == SQLITE_OK && cEsc) rc = sqlite3_bind_text(stmt, 3, zEsc, 1, SQLITE_TRANSIENT);
if (rc == SQLITE_OK) rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) result = !sqlite3_column_int(stmt, 0);
cql_finalize_stmt(&stmt);
return result;
}
// If the Sqlite sqlite3_strlike method is not available we can round trip to
// sqlite with a LIKE expression instead.
int cql_compat_sqlite3_strlike(
const char *_Nonnull zGlob,
const char *_Nonnull zStr,
unsigned int cEsc)
{
// sqlite3_strlike is available in 3.10.0 and greater
int (*strlike_impl)(const char *, const char *, unsigned int) =
#if SQLITE_VERSION_NUMBER >= 3010000
(cql_sqlite3_libversion_number() >= 3010000 ?
sqlite3_strlike :
_cql_compat_sqlite3_strlike);
#else
_cql_compat_sqlite3_strlike;
#endif
return strlike_impl(zGlob, zStr, cEsc);
}
// If there is no row available we can use this helper to ensure that
// the output data is put into a known state.
static void cql_multinull(cql_int32 count, va_list *_Nonnull args) {
for (cql_int32 column = 0; column < count; column++) {
cql_int32 type = va_arg(*args, cql_int32);
cql_int32 core_data_type = CQL_CORE_DATA_TYPE_OF(type);
if (type & CQL_DATA_TYPE_NOT_NULL) {
switch (core_data_type) {
case CQL_DATA_TYPE_INT32: {
cql_int32 *int32_data = va_arg(*args, cql_int32 *);
*int32_data = 0;
break;
}
case CQL_DATA_TYPE_INT64: {
cql_int64 *int64_data = va_arg(*args, cql_int64 *);
*int64_data = 0;
break;
}
case CQL_DATA_TYPE_DOUBLE: {
cql_double *double_data = va_arg(*args, cql_double *);
*double_data = 0;
break;
}
case CQL_DATA_TYPE_BOOL: {
cql_bool *bool_data = va_arg(*args, cql_bool *);
*bool_data = 0;
break;
}
case CQL_DATA_TYPE_STRING: {
cql_string_ref *str_ref = va_arg(*args, cql_string_ref *);
cql_set_string_ref(str_ref, NULL);
break;
}
case CQL_DATA_TYPE_BLOB: {
cql_blob_ref *blob_ref = va_arg(*args, cql_blob_ref *);
cql_set_blob_ref(blob_ref, NULL);
break;
}
}
}
else {
switch (core_data_type) {
case CQL_DATA_TYPE_INT32: {
cql_nullable_int32 *_Nonnull int32p = va_arg(*args, cql_nullable_int32 *_Nonnull);
cql_set_null(*int32p);
break;
}
case CQL_DATA_TYPE_INT64: {
cql_nullable_int64 *_Nonnull int64p = va_arg(*args, cql_nullable_int64 *_Nonnull);
cql_set_null(*int64p);
break;
}
case CQL_DATA_TYPE_DOUBLE: {
cql_nullable_double *_Nonnull doublep = va_arg(*args, cql_nullable_double *_Nonnull);
cql_set_null(*doublep);
break;
}
case CQL_DATA_TYPE_BOOL: {
cql_nullable_bool *_Nonnull boolp = va_arg(*args, cql_nullable_bool *_Nonnull);
cql_set_null(*boolp);
break;
}
case CQL_DATA_TYPE_STRING: {
cql_string_ref *str_ref = va_arg(*args, cql_string_ref *);
cql_set_string_ref(str_ref, NULL);
break;
}
case CQL_DATA_TYPE_BLOB: {
cql_blob_ref *blob_ref = va_arg(*args, cql_blob_ref *);
cql_set_blob_ref(blob_ref, NULL);
break;
}
}
}
}
}
// This helper fetch a column value from sqlite and store it in the holder.
// CQL encode column value with flag CQL_DATA_TYPE_ENCODED as soon as they're
// read from db like that only encoded value is accessible. This means the
// proc creating result_set should decode explicitely sensitive column value
// to get the real value.
static void cql_fetch_field(
cql_int32 type,
cql_int32 column,
sqlite3 *_Nonnull db,
sqlite3_stmt *_Nullable stmt,
char *_Nonnull field,
cql_bool enable_encoding,
cql_int32 encode_context_type,
char *_Nullable encode_context_field,
cql_object_ref _Nullable encoder)
{
bool is_encoded = (type & CQL_DATA_TYPE_ENCODED) && enable_encoding;
cql_int32 core_data_type_and_not_null = type & ~CQL_DATA_TYPE_ENCODED;
switch (core_data_type_and_not_null) {
case CQL_DATA_TYPE_INT32 | CQL_DATA_TYPE_NOT_NULL: {
cql_int32 *int32_data = (cql_int32 *)field;
*int32_data = sqlite3_column_int(stmt, column);
if (is_encoded) {
*int32_data = cql_encode_int32(encoder, *int32_data, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_INT64 | CQL_DATA_TYPE_NOT_NULL: {
cql_int64 *int64_data = (cql_int64 *)field;
*int64_data = sqlite3_column_int64(stmt, column);
if (is_encoded) {
*int64_data = cql_encode_int64(encoder, *int64_data, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_DOUBLE | CQL_DATA_TYPE_NOT_NULL: {
cql_double *double_data = (cql_double *)field;
*double_data = sqlite3_column_double(stmt, column);
if (is_encoded) {
*double_data = cql_encode_double(encoder, *double_data, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_BOOL | CQL_DATA_TYPE_NOT_NULL: {
cql_bool *bool_data = (cql_bool *)field;
*bool_data = !!sqlite3_column_int(stmt, column);
if (is_encoded) {
*bool_data = cql_encode_bool(encoder, *bool_data, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_STRING | CQL_DATA_TYPE_NOT_NULL: {
cql_string_ref *str_ref = (cql_string_ref *)field;
cql_column_string_ref(stmt, column, str_ref);
if (is_encoded) {
cql_string_ref new_str_ref = cql_encode_string_ref_new(encoder, *str_ref, encode_context_type, encode_context_field);
cql_set_string_ref(str_ref, new_str_ref);
cql_string_release(new_str_ref);
}
break;
}
case CQL_DATA_TYPE_BLOB | CQL_DATA_TYPE_NOT_NULL: {
cql_blob_ref *blob_ref = (cql_blob_ref *)field;
cql_column_blob_ref(stmt, column, blob_ref);
if (is_encoded) {
cql_blob_ref new_blob_ref = cql_encode_blob_ref_new(encoder, *blob_ref, encode_context_type, encode_context_field);
cql_set_blob_ref(blob_ref, new_blob_ref);
cql_blob_release(new_blob_ref);
}
break;
}
case CQL_DATA_TYPE_INT32: {
cql_nullable_int32 *_Nonnull int32p = (cql_nullable_int32 *)field;
cql_column_nullable_int32(stmt, column, int32p);
if (is_encoded && !int32p->is_null) {
int32p->value = cql_encode_int32(encoder, int32p->value, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_INT64: {
cql_nullable_int64 *_Nonnull int64p = (cql_nullable_int64 *)field;
cql_column_nullable_int64(stmt, column, int64p);
if (is_encoded && !int64p->is_null) {
int64p->value = cql_encode_int64(encoder, int64p->value, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_DOUBLE: {
cql_nullable_double *_Nonnull doublep = (cql_nullable_double *)field;
cql_column_nullable_double(stmt, column, doublep);
if (is_encoded && !doublep->is_null) {
doublep->value = cql_encode_double(encoder, doublep->value, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_BOOL: {
cql_nullable_bool *_Nonnull boolp = (cql_nullable_bool *)field;
cql_column_nullable_bool(stmt, column, boolp);
if (is_encoded && !boolp->is_null) {
boolp->value = cql_encode_bool(encoder, boolp->value, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_STRING: {
cql_string_ref *str_ref = (cql_string_ref *)field;
cql_column_nullable_string_ref(stmt, column, str_ref);
if (is_encoded && *str_ref) {
cql_string_ref new_str_ref = cql_encode_string_ref_new(encoder, *str_ref, encode_context_type, encode_context_field);
cql_set_string_ref(str_ref, new_str_ref);
cql_string_release(new_str_ref);
}
break;
}
case CQL_DATA_TYPE_BLOB: {
cql_blob_ref *blob_ref = (cql_blob_ref *)field;
cql_column_nullable_blob_ref(stmt, column, blob_ref);
if (is_encoded && *blob_ref) {
cql_blob_ref new_blob_ref = cql_encode_blob_ref_new(encoder, *blob_ref, encode_context_type, encode_context_field);
cql_set_blob_ref(blob_ref, new_blob_ref);
cql_blob_release(new_blob_ref);
}
break;
}
}
}
// This method lets us get lots of columns out of a statement with one call
// in the generated code saving us a lot of error management and reducing the
// generated code cost to just the offsets and types. This version does
// the fetch based on the "fetch info" which includes, among other things
// an array of types and an array of offsets.
void cql_multifetch_meta(char *_Nonnull data, cql_fetch_info *_Nonnull info) {
cql_contract(info->stmt);
cql_contract(info->db);
sqlite3_stmt *stmt = info->stmt;
sqlite3 *db = info->db;
uint8_t *_Nonnull data_types = info->data_types;
uint16_t *_Nonnull col_offsets = info->col_offsets;
uint32_t count = col_offsets[0];
col_offsets++;
// If vault context column is specified, we fetch it first with the column index
cql_int32 encode_context_type = -1;
char *encode_context_field = NULL;
if (info->encode_context_index >= 0) {
encode_context_type = data_types[info->encode_context_index];
encode_context_field = data + col_offsets[info->encode_context_index];
cql_fetch_field(encode_context_type,
info->encode_context_index,
db,
stmt,
encode_context_field,
false /* enable_encoding */,
-1 /* encode_context_type */,
NULL /* encode_context_field */,
info->encoder);
}
for (cql_int32 column = 0; column < count; column++) {
if (column == info->encode_context_index) {
// This vault context column has been fetched already, skip
continue;
}
uint8_t type = data_types[column];
char *field = data + col_offsets[column];
// We're fetching column values from db to store in a result_set. Therefore we
// need to encode those values because it's the result_set output of the proc.
// Because of that we set enable_encoding = TRUE. The value true means if the
// field has the CQL_DATA_TYPE_ENCODED bit then encode otherwise don't
cql_fetch_field(type,
column,
db,
stmt,
field,
true /* enable_encoding */,
encode_context_type,
encode_context_field,
info->encoder);
}
}
// This method lets us get lots of columns out of a statement with one call
// in the generated code saving us a lot of error management and reducing the
// generated code cost to just the offsets and types. This version does the
// fetching using varargs with types and addresses. This is the most flexible
// as it allows writing into local variables and out parameters.
void cql_multifetch(cql_code rc, sqlite3_stmt *_Nullable stmt, cql_int32 count, ...) {
va_list args;
va_start(args, count);
if (rc != SQLITE_ROW) {
cql_multinull(count, &args);
va_end(args);
return;
}
cql_contract(stmt);
sqlite3 *db = sqlite3_db_handle(stmt);
for (cql_int32 column = 0; column < count; column++) {
cql_int32 type = va_arg(args, cql_int32);
void *field = va_arg(args, void *);
// We're fetching column values from db to store in variable. Therefore we
// don't need to encode it because it's not a result_set output of the proc.
// Because of that we set enable_encoding = FALSE. The value false means
// do not encode even if the field has the CQL_DATA_TYPE_ENCODED bit.
cql_fetch_field(type,
column,
db,
stmt,
field,
false /* enable_encoding */,
-1 /* encode_context_type */,
NULL /* encode_context_field */,
NULL /* encoder */);
}
va_end(args);
}
// This method lets us get lots of columns out of a statement with one call
// in the generated code saving us a lot of error management and reducing the
// generated code cost to just the offsets and types. This version does the
// fetching using varargs with types and addresses. This is the most flexible
// as it allows writing into local variables and out parameters.
void cql_copyoutrow(
sqlite3 *_Nullable db,
cql_result_set_ref _Nonnull result_set,
cql_int32 row,
cql_int32 count, ...)
{
cql_contract(result_set);
va_list args;
va_start(args, count);
cql_int32 row_count = cql_result_set_get_count(result_set);
if (row >= row_count || row < 0) {
cql_multinull(count, &args);
va_end(args);
return;
}
bool got_decoder = false;
cql_object_ref _Nullable encoder = NULL;
// Find vault context column
cql_result_set_meta *meta = cql_result_set_get_meta(result_set);
cql_int32 encode_context_type = -1;
char *encode_context_field = NULL;
if (meta->encodeContextIndex >= 0) {
encode_context_type = meta->dataTypes[meta->encodeContextIndex];
encode_context_field = cql_address_of_col(result_set, row, meta->encodeContextIndex, &encode_context_type);
}
// sometimes the below usages resolve to do-noting macros and thus this gets
// considered as unused. So to always give it a "usage" cast it to void here.
(void)encode_context_field;
for (cql_int32 column = 0; column < count; column++) {
cql_int32 type = va_arg(args, cql_int32);
cql_int32 core_data_type_and_not_null = CQL_CORE_DATA_TYPE_OF(type) | (type & CQL_DATA_TYPE_NOT_NULL);
// This is important to document should_decode because it needs some clarification
// that impact how the vault feature work in CQL.
// This function copy raw values from a result set (out union). Therefore if we
// detect that some of the fields read are encoded (should_decode == TRUE), then we
// have to decode the value copied.
// We never encode values read from result_set even though the type flag
// is CQL_DATA_TYPE_ENCODED. The flag CQL_DATA_TYPE_ENCODED is used to encode fields
// read from db (see cql_multifetch(...)) or to decode fields read from result_set (out union).
bool should_decode = db && cql_result_set_get_is_encoded_col(result_set, column);
if (should_decode && !got_decoder) {
encoder = cql_copy_encoder(db);
got_decoder = true; // note the decoder might be null so we need a flag
}
switch (core_data_type_and_not_null) {
case CQL_DATA_TYPE_INT32 | CQL_DATA_TYPE_NOT_NULL: {
cql_int32 *int32_data = va_arg(args, cql_int32 *);
*int32_data = cql_result_set_get_int32_col(result_set, row, column);
if (should_decode) {
*int32_data = cql_decode_int32(encoder, *int32_data, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_INT64 | CQL_DATA_TYPE_NOT_NULL: {
cql_int64 *int64_data = va_arg(args, cql_int64 *);
*int64_data = cql_result_set_get_int64_col(result_set, row, column);
if (should_decode) {
*int64_data = cql_decode_int64(encoder, *int64_data, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_DOUBLE | CQL_DATA_TYPE_NOT_NULL: {
cql_double *double_data = va_arg(args, cql_double *);
*double_data = cql_result_set_get_double_col(result_set, row, column);
if (should_decode) {
*double_data = cql_decode_double(encoder, *double_data, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_BOOL | CQL_DATA_TYPE_NOT_NULL: {
cql_bool *bool_data = va_arg(args, cql_bool *);
*bool_data = cql_result_set_get_bool_col(result_set, row, column);
if (should_decode) {
*bool_data = cql_decode_bool(encoder, *bool_data, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_STRING | CQL_DATA_TYPE_NOT_NULL: {
cql_string_ref *str_ref = va_arg(args, cql_string_ref *);
cql_set_string_ref(str_ref, cql_result_set_get_string_col(result_set, row, column));
cql_string_ref new_str_ref = NULL;
if (should_decode) {
new_str_ref = cql_decode_string_ref_new(encoder, *str_ref, encode_context_type, encode_context_field);
cql_set_string_ref(str_ref, new_str_ref);
cql_string_release(new_str_ref);
}
break;
}
case CQL_DATA_TYPE_BLOB | CQL_DATA_TYPE_NOT_NULL: {
cql_blob_ref *blob_ref = va_arg(args, cql_blob_ref *);
cql_set_blob_ref(blob_ref, cql_result_set_get_blob_col(result_set, row, column));
cql_blob_ref new_blob_ref = NULL;
if (should_decode) {
new_blob_ref = cql_decode_blob_ref_new(encoder, *blob_ref, encode_context_type, encode_context_field);
cql_set_blob_ref(blob_ref, new_blob_ref);
cql_blob_release(new_blob_ref);
}
break;
}
case CQL_DATA_TYPE_OBJECT | CQL_DATA_TYPE_NOT_NULL: {
cql_object_ref *obj_ref = va_arg(args, cql_object_ref *);
cql_set_object_ref(obj_ref, cql_result_set_get_object_col(result_set, row, column));
break;
}
case CQL_DATA_TYPE_INT32: {
cql_nullable_int32 *_Nonnull int32p = va_arg(args, cql_nullable_int32 *_Nonnull);
if (cql_result_set_get_is_null_col(result_set, row, column)) {
cql_set_null(*int32p);
}
else {
cql_set_notnull(*int32p, cql_result_set_get_int32_col(result_set, row, column));
}
if (!int32p->is_null && should_decode) {
int32p->value = cql_decode_int32(encoder, int32p->value, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_INT64: {
cql_nullable_int64 *_Nonnull int64p = va_arg(args, cql_nullable_int64 *_Nonnull);
if (cql_result_set_get_is_null_col(result_set, row, column)) {
cql_set_null(*int64p);
}
else {
cql_set_notnull(*int64p, cql_result_set_get_int64_col(result_set, row, column));
}
if (!int64p->is_null && should_decode) {
int64p->value = cql_decode_int64(encoder, int64p->value, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_DOUBLE: {
cql_nullable_double *_Nonnull doublep = va_arg(args, cql_nullable_double *_Nonnull);
if (cql_result_set_get_is_null_col(result_set, row, column)) {
cql_set_null(*doublep);
}
else {
cql_set_notnull(*doublep, cql_result_set_get_double_col(result_set, row, column));
}
if (!doublep->is_null && should_decode) {
doublep->value = cql_decode_double(encoder, doublep->value, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_BOOL: {
cql_nullable_bool *_Nonnull boolp = va_arg(args, cql_nullable_bool *_Nonnull);
if (cql_result_set_get_is_null_col(result_set, row, column)) {
cql_set_null(*boolp);
}
else {
cql_set_notnull(*boolp, cql_result_set_get_bool_col(result_set, row, column));
}
if (!boolp->is_null && should_decode) {
boolp->value = cql_decode_bool(encoder, boolp->value, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_STRING: {
cql_string_ref *str_ref = va_arg(args, cql_string_ref *);
cql_set_string_ref(str_ref, cql_result_set_get_string_col(result_set, row, column));
cql_string_ref new_str_ref = NULL;
if (*str_ref && should_decode) {
new_str_ref = cql_decode_string_ref_new(encoder, *str_ref, encode_context_type, encode_context_field);
cql_set_string_ref(str_ref, new_str_ref);
cql_string_release(new_str_ref);
}
break;
}
case CQL_DATA_TYPE_BLOB: {
cql_blob_ref *blob_ref = va_arg(args, cql_blob_ref *);
cql_set_blob_ref(blob_ref, cql_result_set_get_blob_col(result_set, row, column));
cql_blob_ref new_blob_ref = NULL;
if (*blob_ref && should_decode) {
new_blob_ref = cql_decode_blob_ref_new(encoder, *blob_ref, encode_context_type, encode_context_field);
cql_set_blob_ref(blob_ref, new_blob_ref);
cql_blob_release(new_blob_ref);
}
break;
}
case CQL_DATA_TYPE_OBJECT: {
cql_object_ref *obj_ref = va_arg(args, cql_object_ref *);
cql_set_object_ref(obj_ref, cql_result_set_get_object_col(result_set, row, column));
break;
}
}
}
cql_object_release(encoder);
va_end(args);
}
// This is just the helper to ignore the indicated arg
// because the predicates array tell us it is to be skipped
static void cql_skip_arg(cql_int32 type, va_list *_Nonnull args)
{
cql_int32 core_data_type = CQL_CORE_DATA_TYPE_OF(type);
if (type & CQL_DATA_TYPE_NOT_NULL) {
switch (core_data_type) {
case CQL_DATA_TYPE_INT32:
(void)va_arg(*args, cql_int32);
break;
case CQL_DATA_TYPE_INT64:
(void)va_arg(*args, cql_int64);
break;
case CQL_DATA_TYPE_DOUBLE:
(void)va_arg(*args, cql_double);
break;
case CQL_DATA_TYPE_BOOL:
(void)va_arg(*args, cql_int32);
break;
case CQL_DATA_TYPE_STRING:
(void)va_arg(*args, cql_string_ref);
break;
case CQL_DATA_TYPE_BLOB:
(void)va_arg(*args, cql_blob_ref);
break;
case CQL_DATA_TYPE_OBJECT:
(void)va_arg(*args, cql_object_ref);
break;
}
}
else {
switch (core_data_type) {
case CQL_DATA_TYPE_INT32:
(void)va_arg(*args, const cql_nullable_int32 *_Nonnull);
break;
case CQL_DATA_TYPE_INT64:
(void)va_arg(*args, const cql_nullable_int64 *_Nonnull);
break;
case CQL_DATA_TYPE_DOUBLE:
(void)va_arg(*args, const cql_nullable_double *_Nonnull);
break;
case CQL_DATA_TYPE_BOOL:
(void)va_arg(*args, const cql_nullable_bool *_Nonnull);
break;
case CQL_DATA_TYPE_STRING:
(void)va_arg(*args, cql_string_ref);
break;
case CQL_DATA_TYPE_BLOB:
(void)va_arg(*args, cql_blob_ref);
break;
case CQL_DATA_TYPE_OBJECT:
(void)va_arg(*args, cql_object_ref);
break;
}
}
}
// This helper lets us bind many variables to a statement with one call. The
// resulting code gen can be a lot smaller as there is only the one error check
// needed and you need only provide the values to bind and the offsets for
// each of the variables. The resulting code is much more economical.
static void cql_multibind_v(
cql_code *_Nonnull prc,
sqlite3 *_Nonnull db,
sqlite3_stmt *_Nullable *_Nonnull pstmt,
cql_int32 count,
const char *_Nullable vpreds,
va_list *_Nonnull args)
{
cql_int32 column = 1;
for (cql_int32 i = 0; *prc == SQLITE_OK && i < count; i++) {
cql_contract(pstmt && *pstmt);
cql_int32 type = va_arg(*args, cql_int32);
cql_int32 core_data_type = CQL_CORE_DATA_TYPE_OF(type);
if (vpreds && !vpreds[i]) {
cql_skip_arg(type, args);
continue;
}
if (type & CQL_DATA_TYPE_NOT_NULL) {
switch (core_data_type) {
case CQL_DATA_TYPE_INT32: {
cql_int32 int32_data = va_arg(*args, cql_int32);
*prc = sqlite3_bind_int(*pstmt, column, int32_data);
column++;
break;
}
case CQL_DATA_TYPE_INT64: {
cql_int64 int64_data = va_arg(*args, cql_int64);
*prc = sqlite3_bind_int64(*pstmt, column, int64_data);
column++;
break;
}
case CQL_DATA_TYPE_DOUBLE: {
cql_double double_data = va_arg(*args, cql_double);
*prc = sqlite3_bind_double(*pstmt, column, double_data);
column++;
break;
}
case CQL_DATA_TYPE_BOOL: {
cql_bool bool_data = !!(cql_bool)va_arg(*args, cql_int32);
*prc = sqlite3_bind_int(*pstmt, column, bool_data);
column++;
break;
}
case CQL_DATA_TYPE_STRING: {
cql_string_ref str_ref = va_arg(*args, cql_string_ref);
cql_alloc_cstr(temp, str_ref);
*prc = sqlite3_bind_text(*pstmt, column, temp, -1, SQLITE_TRANSIENT);
cql_free_cstr(temp, str_ref);
column++;
break;
}
case CQL_DATA_TYPE_BLOB: {
cql_blob_ref blob_ref = va_arg(*args, cql_blob_ref);
const void *bytes = cql_get_blob_bytes(blob_ref);
cql_uint32 size = cql_get_blob_size(blob_ref);
*prc = sqlite3_bind_blob(*pstmt, column, bytes, size, SQLITE_TRANSIENT);
column++;
break;
}
case CQL_DATA_TYPE_OBJECT: {
cql_object_ref obj_ref = va_arg(*args, cql_object_ref);
*prc = sqlite3_bind_int64(*pstmt, column, (int64_t)obj_ref);
column++;
break;
}
}
}
else {
switch (core_data_type) {
case CQL_DATA_TYPE_INT32: {
const cql_nullable_int32 *_Nonnull int32p = va_arg(*args, const cql_nullable_int32 *_Nonnull);
*prc = int32p->is_null ? sqlite3_bind_null(*pstmt, column) :
sqlite3_bind_int(*pstmt, column, int32p->value);
column++;
break;
}
case CQL_DATA_TYPE_INT64: {
const cql_nullable_int64 *_Nonnull int64p = va_arg(*args, const cql_nullable_int64 *_Nonnull);
*prc =int64p->is_null ? sqlite3_bind_null(*pstmt, column) :
sqlite3_bind_int64(*pstmt, column, int64p->value);
column++;
break;
}
case CQL_DATA_TYPE_DOUBLE: {
const cql_nullable_double *_Nonnull doublep = va_arg(*args, const cql_nullable_double *_Nonnull);
*prc = doublep->is_null ? sqlite3_bind_null(*pstmt, column) :
sqlite3_bind_double(*pstmt, column, doublep->value);
column++;
break;
}
case CQL_DATA_TYPE_BOOL: {
const cql_nullable_bool *_Nonnull boolp = va_arg(*args, const cql_nullable_bool *_Nonnull);
*prc =boolp->is_null ? sqlite3_bind_null(*pstmt, column) :
sqlite3_bind_int(*pstmt, column, !!boolp->value);
column++;
break;
}
case CQL_DATA_TYPE_STRING: {
cql_string_ref _Nullable nullable_str_ref = va_arg(*args, cql_string_ref);
if (!nullable_str_ref) {
*prc = sqlite3_bind_null(*pstmt, column);
}
else {
cql_alloc_cstr(temp, nullable_str_ref);
*prc = sqlite3_bind_text(*pstmt, column, temp, -1, SQLITE_TRANSIENT);
cql_free_cstr(temp, nullable_str_ref);
}
column++;
break;
}
case CQL_DATA_TYPE_BLOB: {
cql_blob_ref _Nullable nullable_blob_ref = va_arg(*args, cql_blob_ref);
if (!nullable_blob_ref) {
*prc = sqlite3_bind_null(*pstmt, column);
}
else {
const void *bytes = cql_get_blob_bytes(nullable_blob_ref);
cql_uint32 size = cql_get_blob_size(nullable_blob_ref);
*prc = sqlite3_bind_blob(*pstmt, column, bytes, size, SQLITE_TRANSIENT);
}
column++;
break;
}
case CQL_DATA_TYPE_OBJECT: {
cql_object_ref _Nullable nullable_obj_ref = va_arg(*args, cql_object_ref);
*prc = sqlite3_bind_int64(*pstmt, column, (int64_t)nullable_obj_ref);
column++;
break;
}
}
}
cql_finalize_on_error(*prc, pstmt);
}
}
// This wraps the underlying varargs worker, with no variable predicates
void cql_multibind(
cql_code *_Nonnull prc,
sqlite3 *_Nonnull db,
sqlite3_stmt *_Nullable *_Nonnull pstmt,
cql_int32 count, ...)
{
va_list args;
va_start(args, count);
cql_multibind_v(prc, db, pstmt, count, NULL, &args);
va_end(args);
}
// This wraps the underlying varargs worker, with variable predicates
void cql_multibind_var(
cql_code *_Nonnull prc,
sqlite3 *_Nonnull db,
sqlite3_stmt *_Nullable *_Nonnull pstmt,
cql_int32 count,
const char *_Nullable vpreds, ...)
{
va_list args;
va_start(args, vpreds);
cql_multibind_v(prc, db, pstmt, count, vpreds, &args);
va_end(args);
}
// In a single row of a result set or a single auto-cursor, release all the references in that row
// Note that all the references are together and they begin at refs_offset.
void cql_release_offsets(void *_Nonnull pv, cql_uint16 refs_count, cql_uint16 refs_offset) {
if (refs_count) {
// first entry in the array is the count
char *base = pv;
// each entry then tells us the offset of an embedded pointer
for (cql_int32 i = 0; i < refs_count; i++) {
cql_release(*(cql_type_ref *)(base + refs_offset));
*(cql_type_ref *)(base + refs_offset) = NULL;
refs_offset += sizeof(cql_type_ref);
}
}
}
// In a single row of a result set or a single auto-cursor, retain all the references in that row
// Note that all the references are together and they begin at refs_offset.
void cql_retain_offsets(void *_Nonnull pv, cql_uint16 refs_count, cql_uint16 refs_offset) {
if (refs_count) {
char *base = pv;
// each entry then tells us the offset of an embedded pointer
for (cql_int32 i = 0; i < refs_count; i++) {
cql_retain(*(cql_type_ref *)(base + refs_offset));
refs_offset += sizeof(cql_type_ref);
}
}
}
// Teardown an entire result set by iterating the rows and then releasing
// all of the references in each row using cql_release_offsets. Once that
// is done, it's safe to free the entire blob of storage.
void cql_result_set_teardown(cql_result_set_ref _Nonnull result_set) {
cql_result_set_meta *meta = cql_result_set_get_meta(result_set);
size_t row_size = meta->rowsize;
cql_int32 count = cql_result_set_get_count(result_set);
cql_uint16 refs_count = meta->refsCount;
cql_uint16 refs_offset = meta->refsOffset;
char *_Nullable data = (char *)cql_result_set_get_data(result_set);
char *_Nullable row = data;
if (refs_count && count) {
for (cql_int32 i = 0; i < count; i++) {
cql_release_offsets(row, refs_count, refs_offset);
row += row_size;
}
}
free(data);
}
// Hash the indicated row using a general purpose hash method and the reference
// type hashers.
// * the non-reference data is at the start of the row until the refs_offset
// * the references follow and there are refs_count of them.
// * these values are available in the metadata
// This single function can hash any row of any result set, thereby saving a lot
// of code generation.
cql_hash_code cql_row_hash(cql_result_set_ref _Nonnull result_set, cql_int32 row) {
int32_t count = cql_result_set_get_count(result_set);
cql_contract(row < count);
cql_result_set_meta *meta = cql_result_set_get_meta(result_set);
cql_uint16 refs_count = meta->refsCount;
cql_uint16 refs_offset = meta->refsOffset;
size_t row_size = meta->rowsize;
char *data = ((char *)cql_result_set_get_data(result_set)) + row * row_size;
// we'll do a normal hash on everything up to the first reference type
// note: the refs are all guaranteed to be at the end AND the padding
// is guaranteed to be zero-filled. These are important invariants that
// let us do a much simpler/faster/smaller hash.
size_t size = row_size;
if (refs_count) {
size = refs_offset;
}
cql_hash_code hash = 0;
unsigned char *bytes = (unsigned char *)data;
hash = 5381; // djb2
while (size--) {
hash = ((hash << 5) + hash) + *bytes++; /* hash * 33 + c */
}
if (refs_count) {
// first entry is the count, then there are count more entries hence loop <= count
for (int32_t i = 0; i < refs_count; i++) {
cql_hash_code ref_hash = cql_ref_hash(*(cql_type_ref *)(data + refs_offset));
hash = ((hash << 5) + hash) + ref_hash;
refs_offset += sizeof(cql_type_ref);
}
}
return hash;
}
// Check for equality of rows using the metadata to drive the comparison.
// Similar to hashing about we compare the non-references part of the rows
// by checking the leading part and doing a bytewise comparison. Note that
// any padding is always carefully zeroed out so we can memcmp that as well.
// If that bit matches then we can use the reference equality helper on
// each reference type. Again we have this general helper so that the
// codegen for result sets can be more economical. All result sets can use this one
// function.
cql_bool cql_rows_equal(
cql_result_set_ref _Nonnull rs1,
cql_int32 row1,
cql_result_set_ref _Nonnull rs2,
cql_int32 row2)
{
int32_t count1 = cql_result_set_get_count(rs1);
int32_t count2 = cql_result_set_get_count(rs2);
cql_contract(row1 < count1);
cql_contract(row2 < count2);
// get offsets and verify this is the SAME metadata
cql_result_set_meta *meta1 = cql_result_set_get_meta(rs1);
cql_result_set_meta *meta2 = cql_result_set_get_meta(rs2);
cql_uint16 refs_count = meta1->refsCount;
cql_uint16 refs_offset = meta1->refsOffset;
cql_contract(meta2->refsCount == refs_count);
cql_contract(meta2->refsOffset == refs_offset);
size_t row_size = meta1->rowsize;
char *data1 = ((char *)cql_result_set_get_data(rs1)) + row1 * row_size;
char *data2 = ((char *)cql_result_set_get_data(rs2)) + row2 * row_size;
// We'll do a normal memory comparison on everything up to the first reference type
// note: the refs are all guaranteed to be at the end AND the padding
// is guaranteed to be zero-filled. These are important invariants that
// let us do a much simpler/faster/smaller comparison.
size_t size = row_size;
if (refs_count) {
size = refs_offset;
}
if (memcmp(data1, data2, size)) {
return false;
}
if (refs_count) {
// first entry is the count, then there are count more entries hence loop <= count
for (int32_t i = 0; i < refs_count; i++) {
if (!cql_ref_equal(*(cql_type_ref *)(data1 + refs_offset),
*(cql_type_ref *)(data2 + refs_offset))) {
return false;
}
refs_offset += sizeof(cql_type_ref);
}
}
return true;
}
// sizes for the various data types (not null)
static cql_int32 normal_datasizes[] = {
sizeof(cql_int32),
sizeof(cql_int64),
sizeof(double),
sizeof(cql_bool),
};
// sizes for the various data types (nullable)
static cql_int32 nullable_datasizes[] = {
sizeof(cql_nullable_int32),
sizeof(cql_nullable_int64),
sizeof(cql_nullable_double),
sizeof(cql_nullable_bool),
};
// This helper is a little trickier than the strict equality. "Sameness"
// is defined by a set of columns that correspond to the rows identity.
// CQL doesn't know what that means but the columns can be specified and
// presumably it's meaningful. So for instance the "keys" of a row
// might need to be compared. Note that the two result sets must have
// exactly the same shape as defined by the metadata in order to be comparable.
// To do the comparison we have to check each identity column. If it's
// a reference type then we use the reference type comparison helper and
// otherwise we use strict memory comparison. There's more decoding because
// you can skip columns and column order is not guaranteed to be offset order.
cql_bool cql_rows_same(
cql_result_set_ref _Nonnull rs1,
cql_int32 row1,
cql_result_set_ref _Nonnull rs2,
cql_int32 row2)
{
int32_t count1 = cql_result_set_get_count(rs1);
int32_t count2 = cql_result_set_get_count(rs2);
cql_contract(row1 < count1);
cql_contract(row2 < count2);
cql_result_set_meta *meta1 = cql_result_set_get_meta(rs1);
cql_result_set_meta *meta2 = cql_result_set_get_meta(rs2);
cql_contract(memcmp(meta1, meta2, sizeof(cql_result_set_meta)) == 0);
cql_contract(meta1->identityColumns);
uint16_t identityColumnCount = meta1->identityColumns[0];
cql_contract(identityColumnCount > 0);
uint16_t *identityColumns = &(meta1->identityColumns[1]);
uint16_t *columnOffsets = &(meta1->columnOffsets[1]);
size_t row_size = meta1->rowsize;
char *data1 = ((char *)cql_result_set_get_data(rs1)) + row1 * row_size;
char *data2 = ((char *)cql_result_set_get_data(rs2)) + row2 * row_size;
for (uint16_t i = 0; i < identityColumnCount; i++) {
uint16_t col = identityColumns[i];
uint16_t offset = columnOffsets[col];
// note: the refs are all guaranteed to be at the end AND the padding
// is guaranteed to be zero-filled. These are important invariants that
// let us do a much simpler/faster/smaller comparison.
if (offset < meta1->refsOffset) {
// note: the column offsets are not in order because all refs are moved to the end
// so we compute the size using the datatype (there is a small lookup table for our few types)
uint8_t type = meta1->dataTypes[col];
cql_bool notnull = !!(type & CQL_DATA_TYPE_NOT_NULL);
type &= CQL_DATA_TYPE_CORE;
size_t size = notnull ? normal_datasizes[type] : nullable_datasizes[type];
if (memcmp(data1 + offset, data2 + offset, size)) {
return false;
}
} else {
// this is a ref type
if (!cql_ref_equal(*(cql_type_ref *)(data1 + offset), *(cql_type_ref *)(data2 + offset))) {
return false;
}
}
}
return true;
}
// This helper allows you to copy out some of the rows of a result set to make a new result set.
// The helper uses only metadata to do its job so as with the others codegen
// for this is very economical. The result set includes in it already all the
// metadata necessary to do the column.
// * allocate data for the row count times rowsize
// * memcpy the old data into the new
// * add 1 to the retain count of all the references in the new data
// * wrap it all in a result set object
// * profit :D
void cql_rowset_copy(
cql_result_set_ref _Nonnull result_set,
cql_result_set_ref _Nonnull *_Nonnull to_result_set,
int32_t from,
cql_int32 count)
{
cql_contract(from >= 0);
cql_contract(from + count <= cql_result_set_get_count(result_set));
// get offsets and rowsize metadata
cql_result_set_meta *meta = cql_result_set_get_meta(result_set);
cql_uint16 refs_count = meta->refsCount;
cql_uint16 refs_offset = meta->refsOffset;
size_t row_size = cql_result_set_get_meta(result_set)->rowsize;
char *new_data = calloc(count, row_size);
char *old_data = ((char *)cql_result_set_get_data(result_set))+ row_size * from;
memcpy(new_data, old_data, count * row_size);
char *row = new_data;
for (int32_t i = 0; i < count; i++, row += row_size) {
cql_retain_offsets(row, refs_count, refs_offset);
}
*to_result_set = cql_result_set_create(new_data, count, *meta);
}
// This method is the workhorse of result set reading, the contract is a bit
// unusual again to allow for economy in the generated code. Most of the error
// checking of result set access actually happens here in a generic fashion.
// The checks needed are as follows:
// * the row requested must be in range
// * the column requested must be in range
// * the data type of the column must be the requested type
// * but it could be the nullable version of the same type
// * the exact data type (including nullability) is stored in "type"
// * so type is an in/out parameter, it begins with the base type like "int32"
// * its result is the exact type like "int32" or "nullable int32"
// * the return value is the addresss of the indicated column
//
// If one of the contracts fails it means:
// * the provided row/column value is bogus, or uninitialized
// * the result set object is bogus, it's not a result set at all for instance
// * the result set object has been previously freed
// * the result set provided is actually the wrong one
// * maybe there are several in play
// * the code that is accessing the result set was recompiled but the code
// that creates the result set was not, now they disagree as to how many
// columns there are and what type they are.
// You can use the "meta" object below to debug these situations.
// * does the meta object look reasonable
// * number of columns is not negative, or huge
// * data types of each of the columns is one of the legal values
// * see (e.g.) CQL_DATA_TYPE_INT32 in cqlrt_common.h
// * rowsize seems reasonabe (e.g. not negative or massive)
// * if the rowset looks reasonable then see if you're passing the right one in
// * if the rowset looks unreasonable, maybe it's been freed and you're looking at stale memory
// * if the rowset pointer looks insane, maybe its value was never initialized or something like that.
//
// If one of the contracts does fail, look a few frames up the stack for the source of the problem.
// This helper code is pretty stupid and it's unlikely there is a problem actually in this code.
char *_Nonnull cql_address_of_col(
cql_result_set_ref _Nonnull result_set,
cql_int32 row,
cql_int32 col,
cql_int32 *_Nonnull type)
{
// Check to make sure the requested row is a valid row
// See above for reasons why this might fail.
cql_int32 count = cql_result_set_get_count(result_set);
cql_contract(row < count);
// Check to make sure the meta data has column data
// See above for reasons why this might fail.
cql_result_set_meta *meta = cql_result_set_get_meta(result_set);
cql_contract(meta->columnOffsets != NULL);
// Check to make sure the requested column is a valid column
// See above for reasons why this might fail.
int32_t columnCount = meta->columnCount;
cql_contract(col < columnCount);
// Check to make sure the requested column is of the correct type
// See above for reasons why this might fail.
uint8_t data_type = meta->dataTypes[col];
cql_contract(CQL_CORE_DATA_TYPE_OF(data_type) == *type);
*type = data_type;
// We have a valid row and column so it's safe to do the real work
// Get the column offset, and rowsize and do the math to compute the data pointer.
cql_uint16 offset = meta->columnOffsets[col + 1];
size_t row_size = meta->rowsize;
return ((char *)cql_result_set_get_data(result_set)) + row * row_size + offset;
}
// This is the helper method that reads an int32 out of a rowset at a particular row and column.
// The same helper is used for reading the value from a nullable or not nullable value, so the address helper
// has to report which kind of datum it is. All the error checking is in cql_address_of_col.
cql_int32 cql_result_set_get_int32_col(
cql_result_set_ref _Nonnull result_set,
cql_int32 row, cql_int32 col)
{
cql_int32 data_type = CQL_DATA_TYPE_INT32;
char *data = cql_address_of_col(result_set, row, col, &data_type);
if (data_type & CQL_DATA_TYPE_NOT_NULL) {
return *(cql_int32 *)data;
}
return ((cql_nullable_int32 *)data)->value;
}
// This is the helper method that write an int32 into a rowset at a particular row and column.
// The same helper is used for writing the value from a nullable or not nullable value, so the address helper
// has to report which kind of datum it is. All the error checking is in cql_address_of_col.
void cql_result_set_set_int32_col(
cql_result_set_ref _Nonnull result_set,
cql_int32 row,
cql_int32 col,
cql_nullable_int32 new_value)
{
cql_int32 data_type = CQL_DATA_TYPE_INT32;
char *data = cql_address_of_col(result_set, row, col, &data_type);
if (data_type & CQL_DATA_TYPE_NOT_NULL) {
*(cql_int32 *)data = new_value.value;
} else {
((cql_nullable_int32 *)data)->value = new_value.value;
((cql_nullable_int32 *)data)->is_null = new_value.is_null;
}
}
// This is the helper method that writes a cql_int32 into a rowset at a particular row and column.
// This helper wraps the new int32 in to a cql_nullable_int32 then we can forward
// the set to cql_result_set_set_int32_col
void cql_result_set_set_int32_col_not_null(
cql_result_set_ref _Nonnull result_set,
cql_int32 row,
cql_int32 col,
cql_int32 new_value)
{
cql_nullable_int32 new_value_;
cql_set_notnull(new_value_, new_value);
cql_result_set_set_int32_col(result_set, row, col, new_value_);
}
// This is the helper method that reads an int64 out of a rowset at a particular row and column.
// The same helper is used for reading the value from a nullable or not nullable value, so the address helper
// has to report which kind of datum it is. All the error checking is in cql_address_of_col.
cql_int64 cql_result_set_get_int64_col(
cql_result_set_ref _Nonnull result_set,
cql_int32 row,
cql_int32 col)
{
cql_int32 data_type = CQL_DATA_TYPE_INT64;
char *data = cql_address_of_col(result_set, row, col, &data_type);
if (data_type & CQL_DATA_TYPE_NOT_NULL) {
return *(cql_int64 *)data;
}
return ((cql_nullable_int64 *)data)->value;
}
// This is the helper method that write an int64 into a rowset at a particular row and column.
// The same helper is used for writing the value from a nullable or not nullable value, so the address helper
// has to report which kind of datum it is. All the error checking is in cql_address_of_col.
void cql_result_set_set_int64_col(
cql_result_set_ref _Nonnull result_set,
cql_int32 row,
cql_int32 col,
cql_nullable_int64 new_value)
{
cql_int32 data_type = CQL_DATA_TYPE_INT64;
char *data = cql_address_of_col(result_set, row, col, &data_type);
if (data_type & CQL_DATA_TYPE_NOT_NULL) {
*(cql_int64 *)data = new_value.value;
} else {
((cql_nullable_int64 *)data)->value = new_value.value;
((cql_nullable_int64 *)data)->is_null = new_value.is_null;
}
}
// This is the helper method that writes a cql_int64 into a rowset at a particular row and column.
// This helper wraps the new int64 in to a cql_nullable_int64 then we can forward
// the set to cql_result_set_set_int64_col
void cql_result_set_set_int64_col_not_null(
cql_result_set_ref _Nonnull result_set,
cql_int32 row,
cql_int32 col,
cql_int64 new_value)
{
cql_nullable_int64 new_value_;
cql_set_notnull(new_value_, new_value);
cql_result_set_set_int64_col(result_set, row, col, new_value_);
}
// This is the helper method that reads a double out of a rowset at a particular row and column.
// The same helper is used for reading the value from a nullable or not nullable value, so the address helper
// has to report which kind of datum it is. All the error checking is in cql_address_of_col.
cql_double cql_result_set_get_double_col(
cql_result_set_ref _Nonnull result_set,
cql_int32 row,
cql_int32 col)
{
cql_int32 data_type = CQL_DATA_TYPE_DOUBLE;
char *data = cql_address_of_col(result_set, row, col, &data_type);
if (data_type & CQL_DATA_TYPE_NOT_NULL) {
return *(cql_double *)data;
}
return ((cql_nullable_double *)data)->value;
}
// This is the helper method that write an double into a rowset at a particular row and column.
// The same helper is used for writing the value from a nullable or not nullable value, so the address helper
// has to report which kind of datum it is. All the error checking is in cql_address_of_col.
void cql_result_set_set_double_col(
cql_result_set_ref _Nonnull result_set,
cql_int32 row,
cql_int32 col,
cql_nullable_double new_value)
{
cql_int32 data_type = CQL_DATA_TYPE_DOUBLE;
char *data = cql_address_of_col(result_set, row, col, &data_type);
if (data_type & CQL_DATA_TYPE_NOT_NULL) {
*(cql_double *)data = new_value.value;
} else {
((cql_nullable_double *)data)->value = new_value.value;
((cql_nullable_double *)data)->is_null = new_value.is_null;
}
}
// This is the helper method that writes a double into a rowset at a particular row and column.
// This helper wraps the new double in to a cql_nullable_double then we can forward
// the set to cql_result_set_set_double_col
void cql_result_set_set_double_col_not_null(
cql_result_set_ref _Nonnull result_set,
cql_int32 row,
cql_int32 col,
cql_double new_value)
{
cql_nullable_double new_value_;
cql_set_notnull(new_value_, new_value);
cql_result_set_set_double_col(result_set, row, col, new_value_);
}
// This is the helper method that reads an bool out of a rowset at a particular row and column.
// The same helper is used for reading the value from a nullable or not nullable value, so the address helper
// has to report which kind of datum it is. All the error checking is in cql_address_of_col.
cql_bool cql_result_set_get_bool_col(
cql_result_set_ref _Nonnull result_set,
cql_int32 row, cql_int32 col)
{
cql_int32 data_type = CQL_DATA_TYPE_BOOL;
char *data = cql_address_of_col(result_set, row, col, &data_type);
if (data_type & CQL_DATA_TYPE_NOT_NULL) {
return *(cql_bool *)data;
}
return ((cql_nullable_bool *)data)->value;
}
// This is the helper method that write an bool into a rowset at a particular row and column.
// The same helper is used for writing the value from a nullable or not nullable value, so the address helper
// has to report which kind of datum it is. All the error checking is in cql_address_of_col.
void cql_result_set_set_bool_col(
cql_result_set_ref _Nonnull result_set,
cql_int32 row,
cql_int32 col,
cql_nullable_bool new_value)
{
cql_int32 data_type = CQL_DATA_TYPE_BOOL;
char *data = cql_address_of_col(result_set, row, col, &data_type);
if (data_type & CQL_DATA_TYPE_NOT_NULL) {
*(cql_bool *)data = new_value.value;
} else {
((cql_nullable_bool *)data)->value = new_value.value;
((cql_nullable_bool *)data)->is_null = new_value.is_null;
}
}
// This is the helper method that writes a bool into a rowset at a particular row and column.
// This helper wraps the new cql_bool in to a cql_nullable_bool then we can forward
// the set to cql_result_set_set_bool_col
void cql_result_set_set_bool_col_not_null(
cql_result_set_ref _Nonnull result_set,
cql_int32 row,
cql_int32 col,
cql_bool new_value)
{
cql_nullable_bool new_value_;
cql_set_notnull(new_value_, new_value);
cql_result_set_set_bool_col(result_set, row, col, new_value_);
}
// This is the helper method that reads a string out of a rowset at a particular row and column.
// The same helper is used for reading the value from a nullable or not nullable value, so the address helper
// has to report which kind of datum it is. All the error checking is in cql_address_of_col.
cql_string_ref _Nullable cql_result_set_get_string_col(
cql_result_set_ref _Nonnull result_set,
cql_int32 row, cql_int32 col)
{
cql_int32 data_type = CQL_DATA_TYPE_STRING;
char *data = cql_address_of_col(result_set, row, col, &data_type);
return *(cql_string_ref *)data;
}
// This is the helper method that write an string into a rowset at a particular row and column.
// The same helper is used for writing the value from a nullable or not nullable value, so the address helper
// has to report which kind of datum it is. All the error checking is in cql_address_of_col.
void cql_result_set_set_string_col(
cql_result_set_ref _Nonnull result_set,
cql_int32 row,
cql_int32 col,
cql_string_ref _Nullable new_value)
{
cql_int32 data_type = CQL_DATA_TYPE_STRING;
char *data = cql_address_of_col(result_set, row, col, &data_type);
cql_set_string_ref((cql_string_ref *)data, new_value);
}
// This is the helper method that reads a object out of a rowset at a particular row and column.
// The same helper is used for reading the value from a nullable or not nullable value, so the address helper
// has to report which kind of datum it is. All the error checking is in cql_address_of_col.
cql_object_ref _Nullable cql_result_set_get_object_col(
cql_result_set_ref _Nonnull result_set,
cql_int32 row,
cql_int32 col)
{
cql_int32 data_type = CQL_DATA_TYPE_OBJECT;
char *data = cql_address_of_col(result_set, row, col, &data_type);
return *(cql_object_ref *)data;
}
// This is the helper method that write an object into a rowset at a particular row and column.
// The same helper is used for writing the value from a nullable or not nullable value, so the address helper
// has to report which kind of datum it is. All the error checking is in cql_address_of_col.
void cql_result_set_set_object_col(
cql_result_set_ref _Nonnull result_set,
cql_int32 row,
cql_int32 col,
cql_object_ref _Nullable new_value)
{
cql_int32 data_type = CQL_DATA_TYPE_OBJECT;
char *data = cql_address_of_col(result_set, row, col, &data_type);
cql_set_object_ref((cql_object_ref *)data, new_value);
}
// This is the helper method that reads a blob out of a rowset at a particular row and column.
// The same helper is used for reading the value from a nullable or not nullable value, so the address helper
// has to report which kind of datum it is. All the error checking is in cql_address_of_col.
cql_blob_ref _Nullable cql_result_set_get_blob_col(
cql_result_set_ref _Nonnull result_set,
cql_int32 row,
cql_int32 col)
{
cql_int32 data_type = CQL_DATA_TYPE_BLOB;
char *data = cql_address_of_col(result_set, row, col, &data_type);
return *(cql_blob_ref *)data;
}
// This is the helper method that write an blob into a rowset at a particular row and column.
// The same helper is used for writing the value from a nullable or not nullable value, so the address helper
// has to report which kind of datum it is. All the error checking is in cql_address_of_col.
void cql_result_set_set_blob_col(
cql_result_set_ref _Nonnull result_set,
cql_int32 row,
cql_int32 col,
cql_blob_ref _Nullable new_value)
{
cql_int32 data_type = CQL_DATA_TYPE_BLOB;
char *data = cql_address_of_col(result_set, row, col, &data_type);
cql_set_blob_ref((cql_blob_ref *)data, new_value);
}
// This is the helper method that determines if a nullable column column is null or not.
// If the data type of the column is string or blob then we look for a null value for the pointer in question
// If the data type is not nullable, we return false.
// If the data type is nullable then we read the is_null value out of the row
cql_bool cql_result_set_get_is_null_col(
cql_result_set_ref _Nonnull result_set,
cql_int32 row, cql_int32 col)
{
// Check to make sure the requested row is a valid row
// See cql_address_of_col for reasons why this might fail.
cql_int32 count = cql_result_set_get_count(result_set);
cql_contract(row < count);
// Check to make sure the meta data has column data
// See cql_address_of_col for reasons why this might fail.
cql_result_set_meta *meta = cql_result_set_get_meta(result_set);
cql_contract(meta->columnOffsets != NULL);
// Check to make sure the requested column is a valid column
// See cql_address_of_col for reasons why this might fail.
int32_t columnCount = meta->columnCount;
cql_contract(col < columnCount);
uint8_t data_type = meta->dataTypes[col];
cql_uint16 offset = meta->columnOffsets[col + 1];
size_t row_size = meta->rowsize;
char *data =((char *)cql_result_set_get_data(result_set)) + row * row_size + offset;
int32_t core_data_type = CQL_CORE_DATA_TYPE_OF(data_type);
if (core_data_type == CQL_DATA_TYPE_BLOB
|| core_data_type == CQL_DATA_TYPE_STRING
|| core_data_type == CQL_DATA_TYPE_OBJECT) {
return !*(void **)data;
}
if (data_type & CQL_DATA_TYPE_NOT_NULL) {
return false;
}
cql_bool is_null = 1;
switch (core_data_type) {
case CQL_DATA_TYPE_BOOL:
is_null = ((cql_nullable_bool *)data)->is_null;
break;
case CQL_DATA_TYPE_INT32:
is_null = ((cql_nullable_int32 *)data)->is_null;
break;
case CQL_DATA_TYPE_INT64:
is_null = ((cql_nullable_int64 *)data)->is_null;
break;
default:
// nothing else left
cql_contract(core_data_type == CQL_DATA_TYPE_DOUBLE);
is_null = ((cql_nullable_double *)data)->is_null;
break;
}
return is_null;
}
// This is the helper method that determines if a column is encoded
// return TRUE if the data type value has the flag CQL_DATA_TYPE_ENCODED
cql_bool cql_result_set_get_is_encoded_col(
cql_result_set_ref _Nonnull result_set,
cql_int32 col)
{
cql_result_set_meta *meta = cql_result_set_get_meta(result_set);
// Check to make sure the requested column is a valid column
// See cql_address_of_col for reasons why this might fail.
int32_t columnCount = meta->columnCount;
cql_contract(col < columnCount);
return !!(meta->dataTypes[col] & CQL_DATA_TYPE_ENCODED);
}
// Tables contains a list of tables we need to drop. The format is
// table1\0table2\0table3\0\0
// We try to drop all those tables.
static void cql_autodrop_tables(sqlite3 *_Nullable db, const char *_Nullable tables) {
if (!tables) {
return;
}
// semantic analysis prevents any autodrop tables in cases where there is no db pointer
cql_contract(db);
const char *drop_table = "DROP TABLE IF EXISTS ";
const char *p = tables;
int32_t max_len = 0;
int32_t drop_len = (int32_t)strlen(drop_table);
// find the longest table name so we can make a suitable buffer
for (;;) {
// stop when we find the zero length table name
int32_t len = (int32_t)strlen(p);
if (!len) {
break;
}
if (len > max_len) {
max_len = len;
}
p += len + 1;
}
// we need enough room for the drop command plus the longest table name
// plus the ";" and the null.
STACK_BYTES_ALLOC(sql, drop_len + max_len + 2);
// this part will be constant for all the iterations
strcpy(sql, drop_table);
p = tables;
for (;;) {
// stop when we find the zero length table name
int32_t len = (int32_t)strlen(p);
if (!len) {
break;
}
// form the drop command from the fragments
strcpy(sql + drop_len, p);
strcpy(sql + drop_len + len, ";");
// Try to drop the table, if it fails we disregard the failure code
// there's nothing we could do to recover anyway.
cql_exec(db, sql);
p += len + 1;
}
}
void cql_initialize_meta(cql_result_set_meta *_Nonnull meta, cql_fetch_info *_Nonnull info) {
memset(meta, 0, sizeof(*meta));
meta->teardown = cql_result_set_teardown;
meta->rowsize = info->rowsize;
meta->rowHash = cql_row_hash;
meta->rowsEqual = cql_rows_equal;
meta->rowsSame = cql_rows_same;
meta->refsCount = info->refs_count;
meta->refsOffset = info->refs_offset;
meta->columnOffsets = info->col_offsets;
meta->columnCount = info->col_offsets[0];
meta->identityColumns = info->identity_columns;
meta->dataTypes = info->data_types;
meta->encodeContextIndex = info->encode_context_index;
meta->copy = cql_rowset_copy;
#ifndef CQL_NO_GETTERS
meta->getBoolean = cql_result_set_get_bool_col;
meta->getDouble = cql_result_set_get_double_col;
meta->getInt32 = cql_result_set_get_int32_col;
meta->getInt64 = cql_result_set_get_int64_col;
meta->getString = cql_result_set_get_string_col;
meta->getObject = cql_result_set_get_object_col;
meta->getBlob = cql_result_set_get_blob_col;
meta->getIsNull = cql_result_set_get_is_null_col;
meta->getIsEncoded = cql_result_set_get_is_encoded_col;
meta->setBoolean = cql_result_set_set_bool_col_not_null;
meta->setDouble = cql_result_set_set_double_col_not_null;
meta->setInt32 = cql_result_set_set_int32_col_not_null;
meta->setInt64 = cql_result_set_set_int64_col_not_null;
meta->setString = cql_result_set_set_string_col;
meta->setObject = cql_result_set_set_object_col;
meta->setBlob = cql_result_set_set_blob_col;
#endif
}
// true if any of the columns of this result set are to be encoded
// all we have to do is check the encoded bit on the data types
static cql_bool cql_are_any_encoded(cql_fetch_info *_Nonnull info) {
uint8_t *_Nonnull data_types = info->data_types;
uint16_t *_Nonnull col_offsets = info->col_offsets;
uint32_t count = col_offsets[0];
for (cql_int32 column = 0; column < count; column++) {
uint8_t type = data_types[column];
if (type & CQL_DATA_TYPE_ENCODED) {
return true;
}
}
return false;
}
// By the time we get here, a CQL stored proc has completed execution and there is
// now a statement (or an error result). This function iterates the rows that
// come out of the statement using the fetch info to describe the shape of the
// expected results. All of this code is shared so that the cost of any given
// stored procedure is minimized. Even the error handling is consolidated.
cql_code cql_fetch_all_results(
cql_fetch_info *_Nonnull info,
cql_result_set_ref _Nullable *_Nonnull result_set)
{
*result_set = NULL;
int32_t count = 0;
cql_bytebuf b;
cql_bytebuf_open(&b);
sqlite3_stmt *stmt = info->stmt;
int32_t rowsize = info->rowsize;
char *row;
cql_code rc = info->rc;
if (rc != SQLITE_OK) goto cql_error;
if (cql_are_any_encoded(info)) {
info->encoder = cql_copy_encoder(info->db);
}
for (;;) {
rc = sqlite3_step(stmt);
if (rc == SQLITE_DONE) break;
if (rc != SQLITE_ROW) goto cql_error;
count++;
row = cql_bytebuf_alloc(&b, rowsize);
memset(row, 0, rowsize);
cql_multifetch_meta((char *)row, info);
}
// If all is well, we close the statement and we're done with OK result.
// If anything went wrong we free all the memory and we're outta here.
cql_finalize_stmt(&stmt);
cql_result_set_meta meta;
cql_initialize_meta(&meta, info);
cql_object_release(info->encoder); // nullsafe
info->encoder = NULL;
*result_set = cql_result_set_create(b.ptr, count, meta);
cql_autodrop_tables(info->db, info->autodrop_tables);
cql_profile_stop(info->crc, info->perf_index);
return SQLITE_OK;
cql_error:
// If we have allocated any rows, and they need cleanup, clean them up now
if (info->refs_count) {
row = b.ptr;
for (cql_int32 i = 0; i < count ; i++, row += rowsize) {
cql_release_offsets(row, info->refs_count, info->refs_offset);
}
}
cql_bytebuf_close(&b);
cql_finalize_stmt(&stmt);
cql_log_database_error(info->db, "cql", "database error");
cql_autodrop_tables(info->db, info->autodrop_tables);
cql_object_release(info->encoder); // nullsafe
info->encoder = NULL;
cql_profile_stop(info->crc, info->perf_index);
return rc;
}
// As soon as a new result_set is created. The result_set's field needs
// to be encoded if they're sensitive and has the bit CQL_DATA_TYPE_ENCODED.
// We only encode result_set's field when creating the result_set for:
// - [OUT C] statement: @see cql_one_row_result(...)
// - [OUT UNION C] statement: @see cql_results_from_data(...)
// We also decode when reading fields from a result set (see cql_copyoutrow(...))
// If applicable this helper encode the result_set rows of the newly created result_set.
static void cql_encode_new_result_set_data(
cql_fetch_info *_Nonnull info,
char *_Nullable data,
cql_int32 rows)
{
sqlite3 *db = info->db;
if (!db) {
// DB pointer is only set if the result_set is a came from the database.
// We also only encode/decode if the result_set came from the database.
// Non database result sets (e.g. out union) are never encoded/decoded
// therefore we should end here.
return;
}
int32_t rowsize = info->rowsize;
uint8_t *_Nonnull data_types = info->data_types;
uint16_t *_Nonnull col_offsets = info->col_offsets;
uint32_t count = col_offsets[0];
col_offsets++;
cql_bool got_encoder = false;
cql_object_ref encoder = NULL;
cql_int32 encode_context_type = -1;
if (info->encode_context_index >= 0) {
encode_context_type = data_types[info->encode_context_index];
}
char *encode_context_field = NULL;
char *row = data;
for (cql_int32 i = 0; i < rows ; i++, row += rowsize) {
if (info->encode_context_index >= 0) {
encode_context_field = row + col_offsets[info->encode_context_index];
}
for (cql_int32 column = 0; column < count; column++) {
uint8_t type = data_types[column];
char *field = row + col_offsets[column];
if (!got_encoder && type & CQL_DATA_TYPE_ENCODED) {
encoder = cql_copy_encoder(db);
got_encoder = true;
}
switch (type) {
case CQL_DATA_TYPE_INT32 | CQL_DATA_TYPE_ENCODED | CQL_DATA_TYPE_NOT_NULL: {
cql_int32 *int32_data = (cql_int32 *)field;
*int32_data = cql_encode_int32(encoder, *int32_data, encode_context_type, encode_context_field);
break;
}
case CQL_DATA_TYPE_INT64 | CQL_DATA_TYPE_ENCODED | CQL_DATA_TYPE_NOT_NULL: {
cql_int64 *int64_data = (cql_int64 *)field;
*int64_data = cql_encode_int64(encoder, *int64_data, encode_context_type, encode_context_field);
break;
}
case CQL_DATA_TYPE_DOUBLE | CQL_DATA_TYPE_ENCODED | CQL_DATA_TYPE_NOT_NULL: {
cql_double *double_data = (cql_double *)field;
*double_data = cql_encode_double(encoder, *double_data, encode_context_type, encode_context_field);
break;
}
case CQL_DATA_TYPE_BOOL | CQL_DATA_TYPE_ENCODED | CQL_DATA_TYPE_NOT_NULL: {
cql_bool *bool_data = (cql_bool *)field;
*bool_data = cql_encode_bool(encoder, *bool_data, encode_context_type, encode_context_field);
break;
}
case CQL_DATA_TYPE_STRING | CQL_DATA_TYPE_ENCODED | CQL_DATA_TYPE_NOT_NULL: {
cql_string_ref *str_ref = (cql_string_ref *)field;
cql_string_ref new_str_ref = cql_encode_string_ref_new(encoder, *str_ref, encode_context_type, encode_context_field);
cql_set_string_ref(str_ref, new_str_ref);
cql_string_release(new_str_ref);
break;
}
case CQL_DATA_TYPE_BLOB | CQL_DATA_TYPE_ENCODED | CQL_DATA_TYPE_NOT_NULL: {
cql_blob_ref *blob_ref = (cql_blob_ref *)field;
cql_blob_ref new_blob_ref = cql_encode_blob_ref_new(encoder, *blob_ref, encode_context_type, encode_context_field);
cql_set_blob_ref(blob_ref, new_blob_ref);
cql_blob_release(new_blob_ref);
break;
}
case CQL_DATA_TYPE_INT32 | CQL_DATA_TYPE_ENCODED: {
cql_nullable_int32 *_Nonnull int32p = (cql_nullable_int32 *_Nonnull)field;
if (!int32p->is_null) {
int32p->value = cql_encode_int32(encoder, int32p->value, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_INT64 | CQL_DATA_TYPE_ENCODED: {
cql_nullable_int64 *_Nonnull int64p = (cql_nullable_int64 *_Nonnull)field;
if (!int64p->is_null) {
int64p->value = cql_encode_int64(encoder, int64p->value, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_DOUBLE | CQL_DATA_TYPE_ENCODED: {
cql_nullable_double *_Nonnull doublep = (cql_nullable_double *_Nonnull)field;
if (!doublep->is_null) {
doublep->value = cql_encode_double(encoder, doublep->value, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_BOOL | CQL_DATA_TYPE_ENCODED: {
cql_nullable_bool *_Nonnull boolp = (cql_nullable_bool *_Nonnull)field;
if (!boolp->is_null) {
boolp->value = cql_encode_bool(encoder, boolp->value, encode_context_type, encode_context_field);
}
break;
}
case CQL_DATA_TYPE_STRING | CQL_DATA_TYPE_ENCODED: {
cql_string_ref *str_ref = (cql_string_ref *)field;
if (*str_ref) {
cql_string_ref new_str_ref = cql_encode_string_ref_new(encoder, *str_ref, encode_context_type, encode_context_field);
cql_set_string_ref(str_ref, new_str_ref);
cql_string_release(new_str_ref);
}
break;
}
case CQL_DATA_TYPE_BLOB | CQL_DATA_TYPE_ENCODED: {
cql_blob_ref *blob_ref = (cql_blob_ref *)field;
if (*blob_ref) {
cql_blob_ref new_blob_ref = cql_encode_blob_ref_new(encoder, *blob_ref, encode_context_type, encode_context_field);
cql_set_blob_ref(blob_ref, new_blob_ref);
cql_blob_release(new_blob_ref);
}
break;
}
}
}
}
cql_object_release(encoder);
}
// In this result set creator, the rows are sitting pretty in a buffer we've already
// constructed. The return code tells us if we're exiting clean or not. If we're
// not clean then the buffer should be disposed, there will be no result set returned.
void cql_results_from_data(
cql_code rc,
cql_bytebuf *_Nonnull buffer,
cql_fetch_info *_Nonnull info,
cql_result_set_ref _Nullable *_Nonnull result_set)
{
*result_set = NULL;
int32_t rowsize = info->rowsize;
int32_t count = buffer->used / rowsize;
if (rc == SQLITE_OK) {
cql_result_set_meta meta;
cql_initialize_meta(&meta, info);
// We need to encode the column value of the new result_set's data. We're only
// encoding result_set because it's the final output of a stored proc.
cql_encode_new_result_set_data(info, buffer->ptr, count);
*result_set = cql_result_set_create(buffer->ptr, count, meta);
}
else {
if (info->refs_count) {
char *row = buffer->ptr;
for (cql_int32 i = 0; i < count ; i++, row += rowsize) {
cql_release_offsets(row, info->refs_count, info->refs_offset);
}
}
cql_bytebuf_close(buffer);
}
cql_autodrop_tables(info->db, info->autodrop_tables);
cql_profile_stop(info->crc, info->perf_index);
}
// Just like cql_fetch_all_results but for the "one row result" case
// In that case the data has already been fetched. Its shape is
// described just like the above. All we need to do is wrap the row
// in a result set and we're done. As above the error cases are also
// handled here.
cql_code cql_one_row_result(
cql_fetch_info *_Nonnull info,
char *_Nullable data,
int32_t count,
cql_result_set_ref _Nullable *_Nonnull result_set)
{
cql_code rc = info->rc;
*result_set = NULL;
if (rc != SQLITE_OK) goto cql_error;
cql_result_set_meta meta;
cql_initialize_meta(&meta, info);
// We need to encode the column value of the new result_set's data. We're only
// encoding result_set because it's the final output of a stored proc.
cql_encode_new_result_set_data(info, data, count);
*result_set = cql_result_set_create(data, count, meta);
cql_autodrop_tables(info->db, info->autodrop_tables);
cql_profile_stop(info->crc, info->perf_index);
return SQLITE_OK;
cql_error:
cql_release_offsets(data, info->refs_count, info->refs_offset);
free(data);
cql_log_database_error(info->db, "cql", "database error");
cql_autodrop_tables(info->db, info->autodrop_tables);
cql_profile_stop(info->crc, info->perf_index);
return rc;
}
// these are some structures we need so that we can make an empty result set
// it has a canonical shape (1 column) but there are no rows
// so no column getter will ever succeed not matter the shape that was
// expected.
typedef struct cql_no_rows_row {
cql_int32 x;
} cql_no_rows_row;
static int32_t cql_no_rows_row_perf_index;
uint8_t cql_no_rows_row_data_types[] = {
CQL_DATA_TYPE_INT32 | CQL_DATA_TYPE_NOT_NULL, // x
};
static cql_uint16 cql_no_rows_row_col_offsets[] = { 1,
cql_offsetof(cql_no_rows_row, x)
};
cql_fetch_info cql_no_rows_row_info = {
.rc = SQLITE_OK,
.data_types = cql_no_rows_row_data_types,
.col_offsets = cql_no_rows_row_col_offsets,
.rowsize = sizeof(cql_no_rows_row),
.crc = 0,
.perf_index = &cql_no_rows_row_perf_index,
};
// The most trivial empty result set that still looks like a result set
cql_result_set_ref _Nonnull cql_no_rows_result_set() {
cql_result_set_meta meta;
cql_initialize_meta(&meta, &cql_no_rows_row_info);
return cql_result_set_create(malloc(1), 0, meta);
}
// This statement for sure has no rows in it
cql_code cql_no_rows_stmt(sqlite3 *_Nonnull db, sqlite3_stmt *_Nullable *_Nonnull pstmt) {
cql_finalize_stmt(pstmt);
return cql_sqlite3_prepare_v2(db, "select 0 where 0", -1, pstmt, NULL);
}
// basic closed hash table, small initial size with doubling
#define HASHTAB_INIT_SIZE 4
#define HASHTAB_LOAD_FACTOR .75
// helper to set the payload array, used at init time and during rehash
static void cql_hashtab_set_payload(cql_hashtab *_Nonnull ht) {
ht->payload = (cql_hashtab_entry *)calloc(ht->capacity, sizeof(cql_hashtab_entry));
}
// Rehash to a bigger size, all the items are re-inserted.
// Note we have to release the old values because the new values
// are retained upon insertion. This keeps the reference counting correct.
static void cql_hashtab_rehash(cql_hashtab *_Nonnull ht) {
uint32_t old_capacity = ht->capacity;
cql_hashtab_entry *old_payload = ht->payload;
ht->count = 0;
ht->capacity *= 2;
cql_hashtab_set_payload(ht);
for (uint32_t i = 0; i < old_capacity; i++) {
cql_string_ref key = old_payload[i].key;
if (key) {
cql_hashtab_add(ht, key, old_payload[i].val);
cql_string_release(key);
}
}
free(old_payload);
}
// Making a new hash table, initial size
cql_hashtab *_Nonnull cql_hashtab_new() {
cql_hashtab *ht = malloc(sizeof(cql_hashtab));
ht->count = 0;
ht->capacity = HASHTAB_INIT_SIZE;
cql_hashtab_set_payload(ht);
return ht;
}
// release the memory for the hash table including
// releasing all the strings stored as keys.
void cql_hashtab_delete(cql_hashtab *_Nonnull ht) {
for (int32_t i = 0; i < ht->capacity; i++) {
cql_string_ref key = ht->payload[i].key;
if (key) {
cql_string_release(key);
}
}
free(ht->payload);
free(ht);
}
// Add a new key to the hash table
// * if the key is addred return true
// * if the key exists return false and do nothing
cql_bool cql_hashtab_add(
cql_hashtab *_Nonnull ht,
cql_string_ref _Nonnull key_new,
cql_int64 val_new)
{
uint32_t hash = (uint32_t)cql_string_hash(key_new);
uint32_t offset = hash % ht->capacity;
cql_hashtab_entry *payload = ht->payload;
for (;;) {
cql_string_ref key = payload[offset].key;
if (!key) {
cql_string_retain(key_new);
payload[offset].key = key_new;
payload[offset].val = val_new;
ht->count++;
if (ht->count > ht->capacity * HASHTAB_LOAD_FACTOR) {
cql_hashtab_rehash(ht);
}
return true;
}
if (cql_string_equal(key, key_new)) {
return false;
}
offset++;
if (offset >= ht->capacity) {
offset = 0;
}
}
}
// returns the payload item for the indicated key (allowing mutation)
// if the key is not found returns null
cql_hashtab_entry *_Nullable cql_hashtab_find(
cql_hashtab *_Nonnull ht,
cql_string_ref _Nonnull key_needed)
{
uint32_t hash = (uint32_t)cql_string_hash(key_needed);
uint32_t offset = hash % ht->capacity;
cql_hashtab_entry *payload = ht->payload;
for (;;) {
cql_string_ref key = ht->payload[offset].key;
if (!key) {
return NULL;
}
if (cql_string_equal(key, key_needed)) {
return &payload[offset];
}
offset++;
if (offset >= ht->capacity) {
offset = 0;
}
}
}
// These are CQL friendly versions of the hashtable, these signatures are directly callable from CQL
// create the facets storage using the hashtable
cql_int64 cql_facets_new(void) {
return (cql_int64)cql_hashtab_new();
}
// cleanup the facet storage if facets is valid
void cql_facets_delete(cql_int64 facets){
if (facets) {
cql_hashtab_delete((cql_hashtab*)facets);
}
}
// add a facet value to the hash table
cql_bool cql_facet_add(cql_int64 facets, cql_string_ref _Nonnull name, cql_int64 crc) {
return cql_hashtab_add((cql_hashtab *)facets, name, crc);
}
// Search for the facet value in the hash table, if not found return -1
cql_int64 cql_facet_find(cql_int64 facets, cql_string_ref _Nonnull name) {
cql_hashtab_entry *payload = cql_hashtab_find((cql_hashtab *)facets, name);
if (!payload) {
return -1;
}
return payload->val;
}
#define cql_append_value(b, var) cql_bytebuf_append(&b, &var, sizeof(var))
#define cql_append_nullable_value(b, var) \
if (!var.is_null) { \
cql_setbit(bits, nullable_index); \
cql_append_value(b, var.value); \
}
static void cql_setbit(uint8_t *_Nonnull bytes, uint16_t index) {
bytes[index / 8] |= (1 << (index % 8));
}
static cql_bool cql_getbit(const uint8_t *_Nonnull bytes, uint16_t index) {
return !!(bytes[index / 8] & (1 << (index % 8)));
}
typedef struct cql_input_buf {
const unsigned char *_Nonnull data;
uint32_t remaining;
} cql_input_buf;
static bool cql_input_read(cql_input_buf *_Nonnull buf, void *_Nonnull dest, uint32_t bytes) {
if (bytes > buf->remaining) {
return false;
}
memcpy(dest, buf->data, bytes);
buf->remaining -= bytes;
buf->data += bytes;
return true;
}
static bool cql_input_inline_str(
cql_input_buf *_Nonnull buf,
const char *_Nonnull *_Nonnull dest)
{
unsigned char *nullchar = memchr(buf->data, 0, buf->remaining);
if (nullchar) {
uint32_t bytes = (uint32_t)(nullchar - buf->data) + 1;
*dest = (const char *)buf->data;
buf->remaining -= bytes;
buf->data += bytes;
return true;
}
return false;
}
static bool cql_input_inline_bytes(
cql_input_buf *_Nonnull buf,
const uint8_t *_Nonnull *_Nonnull dest,
uint32_t bytes)
{
if (bytes <= buf->remaining) {
*dest = buf->data;
buf->remaining -= bytes;
buf->data += bytes;
return true;
}
return false;
}
static uint32_t cql_zigzag_encode_32 (cql_int32 i) {
return (i >> 31) ^ (i << 1);
}
static cql_int32 cql_zigzag_decode_32 (uint32_t i) {
return (i >> 1) ^ -(i & 1);
}
static uint64_t cql_zigzag_encode_64 (cql_int64 i) {
return (i >> 63) ^ (i << 1);
}
static cql_int64 cql_zigzag_decode_64 (uint64_t i) {
return (i >> 1) ^ -(i & 1);
}
// variable length encoding using zigzag and 7 bits with extension
// note that this also takes care of any endian issues
static bool cql_read_varint_32(cql_input_buf *_Nonnull buf, cql_int32 *_Nonnull out) {
uint32_t result = 0;
uint8_t byte;
uint8_t i = 0;
uint8_t offset = 0;
while (i < 5) {
if (!cql_input_read(buf, &byte, 1)) {
return false;
}
result |= ((uint32_t)(byte & 0x7f)) << offset;
if (!(byte & 0x80)) {
*out = cql_zigzag_decode_32(result);
return true;
}
offset += 7;
i++;
}
// badly formed buffer, 5 bytes is the most we need for a 32 bit varint
return false;
}
// variable length encoding using zigzag and 7 bits with extension
// note that this also takes care of any endian issues
static bool cql_read_varint_64(cql_input_buf *_Nonnull buf, cql_int64 *_Nonnull out) {
uint64_t result = 0;
uint8_t byte;
uint8_t i = 0;
uint8_t offset = 0;
while (i < 10) {
if (!cql_input_read(buf, &byte, 1)) {
return false;
}
result |= ((uint64_t)(byte & 0x7f)) << offset;
if (!(byte & 0x80)) {
*out = cql_zigzag_decode_64(result);
return true;
}
offset += 7;
i++;
}
// badly formed buffer, 10 bytes is the most we need for a 64 bit varint
return false;
}
// variable length encoding using zigzag and 7 bits with extension
// note that this also takes care of any endian issues
static void cql_write_varint_32(cql_bytebuf *_Nonnull buf, int32_t si) {
uint32_t i = cql_zigzag_encode_32(si);
do {
uint8_t byte = i & 0x7f;
i >>= 7;
if (i) {
byte |= 0x80;
}
cql_append_value(*buf, byte);
} while (i);
}
// variable length encoding using zigzag and 7 bits with extension
// note that this also takes care of any endian issues
static void cql_write_varint_64(cql_bytebuf *_Nonnull buf, int64_t si) {
uint64_t i = cql_zigzag_encode_64(si);
do {
uint8_t byte = i & 0x7f;
i >>= 7;
if (i) {
byte |= 0x80;
}
cql_append_value(*buf, byte);
} while (i);
}
cql_code cql_serialize_to_blob(
cql_blob_ref _Nullable *_Nonnull blob,
void *_Nonnull cursor_raw,
cql_bool has_row,
uint16_t *_Nonnull offsets,
uint8_t *_Nonnull types)
{
if (!has_row) {
return SQLITE_ERROR;
}
uint16_t count = offsets[0]; // the first index is the count of fields
cql_bytebuf b;
cql_bytebuf_open(&b);
uint8_t code = 0;
uint16_t nullable_count = 0;
uint16_t bool_count = 0;
uint8_t *cursor = cursor_raw; // we will be using char offsets
for (uint16_t i = 0; i < count; i++) {
uint8_t type = types[i];
cql_bool nullable = !(type & CQL_DATA_TYPE_NOT_NULL);
int8_t core_data_type = CQL_CORE_DATA_TYPE_OF(type);
code = 0;
if (nullable) {
nullable_count++;
code = 'a' - 'A'; // lower case for nullable
}
// this makes upper or lower case depending on nullable
switch (core_data_type) {
case CQL_DATA_TYPE_INT32: code += 'I'; break;
case CQL_DATA_TYPE_INT64: code += 'L'; break;
case CQL_DATA_TYPE_DOUBLE: code += 'D'; break;
case CQL_DATA_TYPE_BOOL: code += 'F'; bool_count++; break;
case CQL_DATA_TYPE_STRING: code += 'S'; break;
case CQL_DATA_TYPE_BLOB: code += 'B'; break;
}
// verifies that we set code
cql_invariant(code != 0 && code != 'a' - 'A');
cql_append_value(b, code);
}
// null terminate the type info
code = 0;
cql_append_value(b, code);
uint16_t bitvector_bytes_needed = (nullable_count + bool_count + 7) / 8;
uint8_t *bits = cql_bytebuf_alloc(&b, bitvector_bytes_needed);
memset(bits, 0, bitvector_bytes_needed);
uint16_t nullable_index = 0;
uint16_t bool_index = 0;
for (uint16_t i = 0; i < count; i++) {
uint16_t offset = offsets[i+1];
uint8_t type = types[i];
int8_t core_data_type = CQL_CORE_DATA_TYPE_OF(type);
if (type & CQL_DATA_TYPE_NOT_NULL) {
switch (core_data_type) {
case CQL_DATA_TYPE_INT32: {
cql_int32 int32_data = *(cql_int32 *)(cursor + offset);
cql_write_varint_32(&b, int32_data);
break;
}
case CQL_DATA_TYPE_INT64: {
cql_int64 int64_data = *(cql_int64 *)(cursor + offset);
cql_write_varint_64(&b, int64_data);
break;
}
case CQL_DATA_TYPE_DOUBLE: {
// IEEE 754 big endian seems to be everywhere we need it to be
// it's good enough for SQLite so it's good enough for us.
// We're punting on their ARM7 mixed endian support, we don't care about ARM7
cql_double double_data = *(cql_double *)(cursor + offset);
cql_append_value(b, double_data);
break;
}
case CQL_DATA_TYPE_BOOL: {
cql_bool bool_data = *(cql_bool *)(cursor + offset);
if (bool_data) {
cql_setbit(bits, nullable_count + bool_index);
}
bool_index++;
break;
}
case CQL_DATA_TYPE_STRING: {
cql_string_ref str_ref = *(cql_string_ref *)(cursor + offset);
cql_alloc_cstr(temp, str_ref);
cql_bytebuf_append(&b, temp, (uint32_t)(strlen(temp) + 1));
cql_free_cstr(temp, str_ref);
break;
}
case CQL_DATA_TYPE_BLOB: {
cql_blob_ref blob_ref = *(cql_blob_ref *)(cursor + offset);
const void *bytes = cql_get_blob_bytes(blob_ref);
cql_uint32 size = cql_get_blob_size(blob_ref);
cql_append_value(b, size);
cql_bytebuf_append(&b, bytes, size);
break;
}
}
}
else {
switch (core_data_type) {
case CQL_DATA_TYPE_INT32: {
cql_nullable_int32 int32_data = *(cql_nullable_int32 *)(cursor + offset);
if (!int32_data.is_null) {
cql_setbit(bits, nullable_index);
cql_write_varint_32(&b, int32_data.value);
}
break;
}
case CQL_DATA_TYPE_INT64: {
cql_nullable_int64 int64_data = *(cql_nullable_int64 *)(cursor + offset);
if (!int64_data.is_null) {
cql_setbit(bits, nullable_index);
cql_write_varint_64(&b, int64_data.value);
}
break;
}
case CQL_DATA_TYPE_DOUBLE: {
// IEEE 754 big endian seems to be everywhere we need it to be
// it's good enough for SQLite so it's good enough for us.
// We're punting on their ARM7 mixed endian support, we don't care about ARM7
cql_nullable_double double_data = *(cql_nullable_double *)(cursor + offset);
cql_append_nullable_value(b, double_data);
break;
}
case CQL_DATA_TYPE_BOOL: {
cql_nullable_bool bool_data = *(cql_nullable_bool *)(cursor + offset);
if (!bool_data.is_null) {
cql_setbit(bits, nullable_index);
if (bool_data.value) {
cql_setbit(bits, nullable_count + bool_index);
}
}
bool_index++;
break;
}
case CQL_DATA_TYPE_STRING: {
cql_string_ref str_ref = *(cql_string_ref *)(cursor + offset);
if (str_ref) {
cql_setbit(bits, nullable_index);
cql_alloc_cstr(temp, str_ref);
cql_bytebuf_append(&b, temp, (uint32_t)(strlen(temp) + 1));
cql_free_cstr(temp, str_ref);
}
break;
}
case CQL_DATA_TYPE_BLOB: {
cql_blob_ref blob_ref = *(cql_blob_ref *)(cursor + offset);
if (blob_ref) {
cql_setbit(bits, nullable_index);
const void *bytes = cql_get_blob_bytes(blob_ref);
uint32_t size = cql_get_blob_size(blob_ref);
cql_append_value(b, size);
cql_bytebuf_append(&b, bytes, size);
}
break;
}
}
nullable_index++;
}
}
cql_invariant(nullable_index == nullable_count);
cql_blob_ref new_blob = cql_blob_ref_new((const uint8_t *)b.ptr, b.used);
cql_blob_release(*blob);
*blob = new_blob;
cql_bytebuf_close(&b);
return SQLITE_OK;
}
// release the references in a cursor using the types and offsets info
static void cql_clear_references_before_deserialization(
unsigned char *_Nonnull cursor,
uint16_t *_Nonnull offsets,
uint8_t *_Nonnull types)
{
uint16_t count = offsets[0];
for (uint16_t i = 0; i < count; i++) {
uint16_t offset = offsets[i+1];
uint8_t type = types[i];
uint8_t core_data_type = CQL_CORE_DATA_TYPE_OF(type);
if (core_data_type == CQL_DATA_TYPE_STRING || core_data_type == CQL_DATA_TYPE_BLOB) {
cql_release(*(cql_type_ref *)(cursor + offset));
*(cql_type_ref *)(cursor + offset) = NULL;
}
}
}
#define cql_read_var(buf, var) \
if (!cql_input_read(buf, &var, sizeof(var))) { \
goto error; \
}
cql_code cql_deserialize_from_blob(
cql_blob_ref _Nullable b,
void *_Nonnull cursor_raw,
cql_bool *_Nonnull has_row,
uint16_t *_Nonnull offsets,
uint8_t *_Nonnull types)
{
// we have to release the existing cursor before we start
// we'll be clobbering the field while we build it.
*has_row = false;
cql_clear_references_before_deserialization(cursor_raw, offsets, types);
if (!b) {
goto error;
}
const uint8_t *bytes = (const uint8_t *)cql_get_blob_bytes(b);
cql_input_buf input;
input.data = bytes;
input.remaining = cql_get_blob_size(b);
uint16_t needed_count = offsets[0]; // the first index is the count of fields
uint16_t nullable_count = 0;
uint16_t bool_count = 0;
uint16_t actual_count = 0;
uint8_t *cursor = cursor_raw; // we will be using char offsets
uint16_t i = 0;
for (;;) {
char code;
cql_read_var(&input, code);
if (!code) {
break;
}
bool nullable_code = (code >= 'a' && code <= 'z');
nullable_count += nullable_code;
actual_count++;
if (code == 'f' || code == 'F') {
bool_count++;
}
// Extra fields do not have to match, the assumption is that this is
// a future version of the type talking to a past version. The past
// version sees only what it expects to see. However, we did have
// to compute the nullable_count and bool_count to get the bit vector
// size correct.
if (actual_count <= needed_count) {
uint8_t type = types[i++];
bool nullable_type = !(type & CQL_DATA_TYPE_NOT_NULL);
uint8_t core_data_type = CQL_CORE_DATA_TYPE_OF(type);
// it's ok if we need a nullable but we're getting a non-nullable
if (!nullable_type && nullable_code) {
// nullability must match
goto error;
}
// normalize to the not null type, we've already checked nullability match
code = nullable_code ? code - ('a' - 'A') : code;
// ensure that what we have is what we need for all of what we have
bool code_ok = false;
switch (core_data_type) {
case CQL_DATA_TYPE_INT32: code_ok = code == 'I'; break;
case CQL_DATA_TYPE_INT64: code_ok = code == 'L'; break;
case CQL_DATA_TYPE_DOUBLE: code_ok = code == 'D'; break;
case CQL_DATA_TYPE_BOOL: code_ok = code == 'F'; break;
case CQL_DATA_TYPE_STRING: code_ok = code == 'S'; break;
case CQL_DATA_TYPE_BLOB: code_ok = code == 'B'; break;
}
if (!code_ok) {
goto error;
}
}
}
// if we have too few fields we can use null fillers, this is the versioning
// policy, we will check that any missing fields are nullable.
while (i < needed_count) {
uint8_t type = types[i++];
if (type & CQL_DATA_TYPE_NOT_NULL) {
goto error;
}
}
// get the bool bits we need
const uint8_t *bits;
uint16_t bytes_needed = (nullable_count + bool_count + 7) / 8;
if (!cql_input_inline_bytes(&input, &bits, bytes_needed)) {
goto error;
}
uint16_t nullable_index = 0;
uint16_t bool_index = 0;
// The types are compatible and we have enough of them, we can start
// trying to decode.
for (i = 0; i < needed_count; i++) {
uint16_t offset = offsets[i+1];
uint8_t type = types[i];
cql_int32 core_data_type = CQL_CORE_DATA_TYPE_OF(type);
bool fetch_data = false;
bool needed_notnull = !!(type & CQL_DATA_TYPE_NOT_NULL);
if (i >= actual_count) {
// we don't have this field
fetch_data = false;
}
else {
bool actual_notnull = bytes[i] >= 'A' && bytes[i] <= 'Z';
if (actual_notnull) {
// marked not null in the metadata means it is always present
fetch_data = true;
}
else {
// fetch any nullable field if and only if its not null bit is set
fetch_data = cql_getbit(bits, nullable_index++);
}
}
if (fetch_data) {
switch (core_data_type) {
case CQL_DATA_TYPE_INT32: {
cql_int32 *result;
if (needed_notnull) {
result = (cql_int32 *)(cursor + offset);
}
else {
cql_nullable_int32 *nullable_storage = (cql_nullable_int32 *)(cursor+offset);
nullable_storage->is_null = false;
result = &nullable_storage->value;
}
if (!cql_read_varint_32(&input, result)) {
goto error;
}
break;
}
case CQL_DATA_TYPE_INT64: {
cql_int64 *result;
if (needed_notnull) {
result = (cql_int64 *)(cursor + offset);
}
else {
cql_nullable_int64 *nullable_storage = (cql_nullable_int64 *)(cursor+offset);
nullable_storage->is_null = false;
result = &nullable_storage->value;
}
if (!cql_read_varint_64(&input, result)) {
goto error;
}
break;
}
case CQL_DATA_TYPE_DOUBLE: {
// IEEE 754 big endian seems to be everywhere we need it to be
// it's good enough for SQLite so it's good enough for us.
// We're punting on their ARM7 mixed endian support, we don't care about ARM7
cql_double *result;
if (needed_notnull) {
result = (cql_double *)(cursor + offset);
}
else {
cql_nullable_double *nullable_storage = (cql_nullable_double *)(cursor+offset);
nullable_storage->is_null = false;
result = &nullable_storage->value;
}
cql_read_var(&input, *result);
break;
}
case CQL_DATA_TYPE_BOOL: {
cql_bool *result;
if (needed_notnull) {
result = (cql_bool *)(cursor + offset);
}
else {
cql_nullable_bool *nullable_storage = (cql_nullable_bool *)(cursor+offset);
nullable_storage->is_null = false;
result = &nullable_storage->value;
}
*result = cql_getbit(bits, nullable_count + bool_index);
bool_index++;
break;
}
case CQL_DATA_TYPE_STRING: {
cql_string_ref *str_ref = (cql_string_ref *)(cursor + offset);
const char *result;
if (!cql_input_inline_str(&input, &result)) {
goto error;
}
*str_ref = cql_string_ref_new(result);
break;
}
case CQL_DATA_TYPE_BLOB: {
cql_blob_ref *blob_ref = (cql_blob_ref *)(cursor + offset);
uint32_t byte_count;
cql_read_var(&input, byte_count);
const uint8_t *result;
if (!cql_input_inline_bytes(&input, &result, byte_count)) {
goto error;
}
*blob_ref = cql_blob_ref_new(result, byte_count);
break;
}
}
}
else {
switch (core_data_type) {
case CQL_DATA_TYPE_INT32: {
cql_nullable_int32 *int32_data = (cql_nullable_int32 *)(cursor + offset);
int32_data->value = 0;
int32_data->is_null = true;
break;
}
case CQL_DATA_TYPE_INT64: {
cql_nullable_int64 *int64_data = (cql_nullable_int64 *)(cursor + offset);
int64_data->value = 0;
int64_data->is_null = true;
break;
}
case CQL_DATA_TYPE_DOUBLE: {
cql_nullable_double *double_data = (cql_nullable_double *)(cursor + offset);
double_data->value = 0;
double_data->is_null = true;
break;
}
case CQL_DATA_TYPE_BOOL: {
cql_nullable_bool *bool_data = (cql_nullable_bool *)(cursor + offset);
bool_data->value = 0;
bool_data->is_null = true;
break;
}
case CQL_DATA_TYPE_STRING: {
cql_string_ref *str_ref = (cql_string_ref *)(cursor + offset);
*str_ref = NULL;
break;
}
case CQL_DATA_TYPE_BLOB: {
cql_blob_ref *blob_ref = (cql_blob_ref *)(cursor + offset);
*blob_ref = NULL;
break;
}
}
}
}
*has_row = true;
return SQLITE_OK;
error:
*has_row = false;
cql_clear_references_before_deserialization(cursor_raw, offsets, types);
return SQLITE_ERROR;
}