diff options
Diffstat (limited to 'ext/librethinkdbxx/src/datum.cc')
-rw-r--r-- | ext/librethinkdbxx/src/datum.cc | 449 |
1 files changed, 449 insertions, 0 deletions
diff --git a/ext/librethinkdbxx/src/datum.cc b/ext/librethinkdbxx/src/datum.cc new file mode 100644 index 00000000..e4dbc8dc --- /dev/null +++ b/ext/librethinkdbxx/src/datum.cc @@ -0,0 +1,449 @@ +#include <float.h> +#include <cmath> + +#include "datum.h" +#include "json_p.h" +#include "utils.h" +#include "cursor.h" + +#include "rapidjson-config.h" +#include "rapidjson/prettywriter.h" +#include "rapidjson/stringbuffer.h" + +namespace RethinkDB { + +using TT = Protocol::Term::TermType; + +bool Datum::is_nil() const { + return type == Type::NIL; +} + +bool Datum::is_boolean() const { + return type == Type::BOOLEAN; +} + +bool Datum::is_number() const { + return type == Type::NUMBER; +} + +bool Datum::is_string() const { + return type == Type::STRING; +} + +bool Datum::is_object() const { + return type == Type::OBJECT; +} + +bool Datum::is_array() const { + return type == Type::ARRAY; +} + +bool Datum::is_binary() const { + return type == Type::BINARY; +} + +bool Datum::is_time() const { + return type == Type::TIME; +} + +bool* Datum::get_boolean() { + if (type == Type::BOOLEAN) { + return &value.boolean; + } else { + return NULL; + } +} + +const bool* Datum::get_boolean() const { + if (type == Type::BOOLEAN) { + return &value.boolean; + } else { + return NULL; + } +} + +double* Datum::get_number() { + if (type == Type::NUMBER) { + return &value.number; + } else { + return NULL; + } +} + +const double* Datum::get_number() const { + if (type == Type::NUMBER) { + return &value.number; + } else { + return NULL; + } +} + +std::string* Datum::get_string() { + if (type == Type::STRING) { + return &value.string; + } else { + return NULL; + } +} + +const std::string* Datum::get_string() const { + if (type == Type::STRING) { + return &value.string; + } else { + return NULL; + } +} + +Datum* Datum::get_field(std::string key) { + if (type != Type::OBJECT) { + return NULL; + } + auto it = value.object.find(key); + if (it == value.object.end()) { + return NULL; + } + return &it->second; +} + +const Datum* Datum::get_field(std::string key) const { + if (type != Type::OBJECT) { + return NULL; + } + auto it = value.object.find(key); + if (it == value.object.end()) { + return NULL; + } + return &it->second; +} + +Datum* Datum::get_nth(size_t i) { + if (type != Type::ARRAY) { + return NULL; + } + if (i >= value.array.size()) { + return NULL; + } + return &value.array[i]; +} + +const Datum* Datum::get_nth(size_t i) const { + if (type != Type::ARRAY) { + return NULL; + } + if (i >= value.array.size()) { + return NULL; + } + return &value.array[i]; +} + +Object* Datum::get_object() { + if (type == Type::OBJECT) { + return &value.object; + } else { + return NULL; + } +} + +const Object* Datum::get_object() const { + if (type == Type::OBJECT) { + return &value.object; + } else { + return NULL; + } +} + +Array* Datum::get_array() { + if (type == Type::ARRAY) { + return &value.array; + } else { + return NULL; + } +} + +const Array* Datum::get_array() const { + if (type == Type::ARRAY) { + return &value.array; + } else { + return NULL; + } +} + +Binary* Datum::get_binary() { + if (type == Type::BINARY) { + return &value.binary; + } else { + return NULL; + } +} + +const Binary* Datum::get_binary() const { + if (type == Type::BINARY) { + return &value.binary; + } else { + return NULL; + } +} + +Time* Datum::get_time() { + if (type == Type::TIME) { + return &value.time; + } else { + return NULL; + } +} + +const Time* Datum::get_time() const { + if (type == Type::TIME) { + return &value.time; + } else { + return NULL; + } +} + +bool& Datum::extract_boolean() { + if (type != Type::BOOLEAN) { + throw Error("extract_bool: Not a boolean"); + } + return value.boolean; +} + +double& Datum::extract_number() { + if (type != Type::NUMBER) { + throw Error("extract_number: Not a number: %s", write_datum(*this).c_str()); + } + return value.number; +} + +std::string& Datum::extract_string() { + if (type != Type::STRING) { + throw Error("extract_string: Not a string"); + } + return value.string; +} + +Object& Datum::extract_object() { + if (type != Type::OBJECT) { + throw Error("extract_object: Not an object"); + } + return value.object; +} + +Datum& Datum::extract_field(std::string key) { + if (type != Type::OBJECT) { + throw Error("extract_field: Not an object"); + } + auto it = value.object.find(key); + if (it == value.object.end()) { + throw Error("extract_field: No such key in object"); + } + return it->second; +} + +Datum& Datum::extract_nth(size_t i) { + if (type != Type::ARRAY) { + throw Error("extract_nth: Not an array"); + } + if (i >= value.array.size()) { + throw Error("extract_nth: index too large"); + } + return value.array[i]; +} + +Array& Datum::extract_array() { + if (type != Type::ARRAY) { + throw Error("get_array: Not an array"); + } + return value.array; +} + +Binary& Datum::extract_binary() { + if (type != Type::BINARY) { + throw Error("get_binary: Not a binary"); + } + return value.binary; +} + +Time& Datum::extract_time() { + if (type != Type::TIME) { + throw Error("get_time: Not a time"); + } + return value.time; +} + +int Datum::compare(const Datum& other) const { +#define COMPARE(a, b) do { \ + if (a < b) { return -1; } \ + if (a > b) { return 1; } } while(0) +#define COMPARE_OTHER(x) COMPARE(x, other.x) + + COMPARE_OTHER(type); + int c; + switch (type) { + case Type::NIL: case Type::INVALID: break; + case Type::BOOLEAN: COMPARE_OTHER(value.boolean); break; + case Type::NUMBER: COMPARE_OTHER(value.number); break; + case Type::STRING: + c = value.string.compare(other.value.string); + COMPARE(c, 0); + break; + case Type::BINARY: + c = value.binary.data.compare(other.value.binary.data); + COMPARE(c, 0); + break; + case Type::TIME: + COMPARE(value.time.epoch_time, other.value.time.epoch_time); + COMPARE(value.time.utc_offset, other.value.time.utc_offset); + break; + case Type::ARRAY: + COMPARE_OTHER(value.array.size()); + for (size_t i = 0; i < value.array.size(); i++) { + c = value.array[i].compare(other.value.array[i]); + COMPARE(c, 0); + } + break; + case Type::OBJECT: + COMPARE_OTHER(value.object.size()); + for (Object::const_iterator l = value.object.begin(), + r = other.value.object.begin(); + l != value.object.end(); + ++l, ++r) { + COMPARE(l->first, r->first); + c = l->second.compare(r->second); + COMPARE(c, 0); + } + break; + default: + throw Error("cannot compare invalid datum"); + } + return 0; +#undef COMPARE_OTHER +#undef COMPARE +} + +bool Datum::operator== (const Datum& other) const { + return compare(other) == 0; +} + +Datum Datum::from_raw() const { + do { + const Datum* type_field = get_field("$reql_type$"); + if (!type_field) break; + const std::string* type = type_field->get_string(); + if (!type) break;; + if (!strcmp(type->c_str(), "BINARY")) { + const Datum* data_field = get_field("data"); + if (!data_field) break; + const std::string* encoded_data = data_field->get_string(); + if (!encoded_data) break; + Binary binary(""); + if (base64_decode(*encoded_data, binary.data)) { + return binary; + } + } else if (!strcmp(type->c_str(), "TIME")) { + const Datum* epoch_field = get_field("epoch_time"); + if (!epoch_field) break; + const Datum* tz_field = get_field("timezone"); + if (!tz_field) break; + const double* epoch_time = epoch_field->get_number(); + if (!epoch_time) break; + const std::string* tz = tz_field->get_string(); + if (!tz) break; + double offset; + if (!Time::parse_utc_offset(*tz, &offset)) break; + return Time(*epoch_time, offset); + } + } while (0); + return *this; +} + +Datum Datum::to_raw() const { + if (type == Type::BINARY) { + return Object{ + {"$reql_type$", "BINARY"}, + {"data", base64_encode(value.binary.data)}}; + } else if (type == Type::TIME) { + return Object{ + {"$reql_type$", "TIME"}, + {"epoch_time", value.time.epoch_time}, + {"timezone", Time::utc_offset_string(value.time.utc_offset)}}; + } + return *this; +} + +Datum::Datum(Cursor&& cursor) : Datum(cursor.to_datum()) { } +Datum::Datum(const Cursor& cursor) : Datum(cursor.to_datum()) { } + +static const double max_dbl_int = 0x1LL << DBL_MANT_DIG; +static const double min_dbl_int = max_dbl_int * -1; +bool number_as_integer(double d, int64_t *i_out) { + static_assert(DBL_MANT_DIG == 53, "Doubles are wrong size."); + + if (min_dbl_int <= d && d <= max_dbl_int) { + int64_t i = d; + if (static_cast<double>(i) == d) { + *i_out = i; + return true; + } + } + return false; +} + +template void Datum::write_json( + rapidjson::Writer<rapidjson::StringBuffer> *writer) const; +template void Datum::write_json( + rapidjson::PrettyWriter<rapidjson::StringBuffer> *writer) const; + +template <class json_writer_t> +void Datum::write_json(json_writer_t *writer) const { + switch (type) { + case Type::NIL: writer->Null(); break; + case Type::BOOLEAN: writer->Bool(value.boolean); break; + case Type::NUMBER: { + const double d = value.number; + // Always print -0.0 as a double since integers cannot represent -0. + // Otherwise check if the number is an integer and print it as such. + int64_t i; + if (!(d == 0.0 && std::signbit(d)) && number_as_integer(d, &i)) { + writer->Int64(i); + } else { + writer->Double(d); + } + } break; + case Type::STRING: writer->String(value.string.data(), value.string.size()); break; + case Type::ARRAY: { + writer->StartArray(); + for (auto it : value.array) { + it.write_json(writer); + } + writer->EndArray(); + } break; + case Type::OBJECT: { + writer->StartObject(); + for (auto it : value.object) { + writer->Key(it.first.data(), it.first.size()); + it.second.write_json(writer); + } + writer->EndObject(); + } break; + + case Type::BINARY: + case Type::TIME: + to_raw().write_json(writer); + break; + default: + throw Error("cannot write invalid datum"); + } +} + +std::string Datum::as_json() const { + rapidjson::StringBuffer buffer; + rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); + write_json(&writer); + return std::string(buffer.GetString(), buffer.GetSize()); +} + +Datum Datum::from_json(const std::string& json) { + return read_datum(json); +} + +} // namespace RethinkDB |