cql_code cql_deserialize_from_blob()

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;
}