in sources/cqlrt_common.c [3004:3270]
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;
}