/* * Copyright (c) 2015, 2020, Oracle and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, as * published by the Free Software Foundation. * * This program is also distributed with certain software (including * but not limited to OpenSSL) that is licensed under separate terms, * as designated in a particular file or component or in included license * documentation. The authors of MySQL hereby grant you an * additional permission to link the program and your derivative works * with the separately licensed software that they have included with * MySQL. * * Without limiting anything contained in the foregoing, this file, * which is part of MySQL Connector/C++, is also subject to the * Universal FOSS Exception, version 1.0, a copy of which can be found at * http://oss.oracle.com/licenses/universal-foss-exception. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License, version 2.0, for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MYSQLX_DOCUMENT_H #define MYSQLX_DOCUMENT_H /** @file Declaration of DbDoc and related classes. */ #include "common.h" #include #include #include #include #include #undef min #undef max namespace mysqlx { MYSQLX_ABI_BEGIN(2,0) class Value; class DbDoc; class DocResult; class SessionSettings; namespace internal{ class Schema_detail; } //internal using Field = std::string; // Document class // ============== /** Represents a collection of key-value pairs where value can be a scalar or another document. @note Internal document implementation is shared among DbDoc instances and thus using DbDoc objects should be cheap. @ingroup devapi_res */ class PUBLIC_API DbDoc : public common::Printable { // TODO: move PUBLIC_API stuff to a detail class class INTERNAL Impl; DLL_WARNINGS_PUSH std::shared_ptr m_impl; DLL_WARNINGS_POP INTERNAL DbDoc(const std::shared_ptr&); const char* get_json() const; public: /** Create null document instance. @note Null document is different from empty document that has no fields. */ DbDoc() {} /** Creates DbDoc instance out of given JSON string description. */ explicit DbDoc(const std::string&); explicit DbDoc(std::string&&); /** Check if document is null */ bool isNull() const { return NULL == m_impl.get(); } operator bool() const { return !isNull(); } /** Check if named field is a top-level field in the document. */ virtual bool hasField(const Field&) const; /** Return Value::XXX constant that identifies type of value stored at given field. */ virtual int fieldType(const Field&) const; /** Return value of given field. */ virtual const Value& operator[](const Field&) const; const Value& operator[](const char *name) const { return this->operator[](Field(name)); } const Value& operator[](const mysqlx::string &name) const { return this->operator[](Field(name)); } /** Print JSON description of the document. */ virtual void print(std::ostream&) const; /** Iterator instance can iterate over (top-level) fields of a document. A new iterator is obtained from begin() method. @note Only one instance of an iterator can be used at a time (not thread safe!). */ class Iterator; virtual Iterator begin(); virtual Iterator end(); friend Impl; friend DocResult; friend Value; friend internal::Schema_detail; }; class PUBLIC_API DbDoc::Iterator { DLL_WARNINGS_PUSH std::shared_ptr m_impl; DLL_WARNINGS_POP bool m_end; public: Iterator& operator++(); bool operator==(const Iterator&) const; bool operator!=(const Iterator &other) const { return !(*this == other); } const Field& operator*(); friend DbDoc; }; // Value class // =========== /** %Value object can store value of scalar type, string, array or document. Implicit conversions to and from corresponding C++ types are defined. If conversion to wrong type is attempted, an error is thrown. If Value object holds an array or document, then array elements or fields of the document can be accessed using operator[]. Array values can be used as STL containers. Only direct conversions of stored value to the corresponding C++ type are supported. There are no implicit number->string conversions etc. Values of type RAW can refer to a region of memory containing raw bytes. Such values are created from `bytes` and can by casted to `bytes` type. @note Value object copies the values it stores. Thus, after storing value in Value object, the original value can be destroyed without invalidating the copy. This includes RAW Values which hold a copy of bytes. @ingroup devapi_res */ class Value : public virtual common::Printable , protected common::Value { public: using string = mysqlx::string; /** Possible types of values. @sa getType() */ enum Type { VNULL, ///< Null value UINT64, ///< Unsigned integer INT64, ///< Signed integer FLOAT, ///< Float number DOUBLE, ///< Double number BOOL, ///< Boolean STRING, ///< String DOCUMENT, ///< Document RAW, ///< Raw bytes ARRAY, ///< Array of values }; typedef std::vector::iterator iterator; typedef std::vector::const_iterator const_iterator; ///@name Value Constructors ///@{ Value(); ///< Constructs Null value. Value(std::nullptr_t); ///< Constructs Null value. Value(const mysqlx::string &str); Value(mysqlx::string &&str); //Value(const char16_t *str) : Value(mysqlx::string(str)) {} Value(const std::string &str); Value(std::string &&str); Value(const char *str) : Value(std::string(str)) {} template Value(const std::basic_string &str) : Value(mysqlx::string(str)) {} template Value(const C *str) : Value(mysqlx::string(str)) {} Value(const bytes&); Value(int64_t); Value(uint64_t); Value(float); Value(double); Value(bool); Value(const DbDoc& doc); Value(const std::initializer_list &list); template Value(Iterator begin_, Iterator end_); ///@} Value(common::Value &&other); Value(const common::Value &other); Value(const Value&) = default; #ifdef HAVE_MOVE_CTORS Value(Value&&) = default; #else // Note move ctor implemented using move assignment defined below. Value(Value &&other) { *this = std::move(other); } #endif /* Note: These templates are needed to disambiguate constructor resolution for integer types. */ template < typename T, typename std::enable_if::value>::type* = nullptr > Value(T x) : Value(static_cast(x)) {} template < typename T, typename std::enable_if::value>::type* = nullptr > Value(T x) : Value(static_cast(x)) {} Value& operator=(const Value&) = default; /* Note: Move assignment is defined explicitly to avoid problems with virtual Printable base. */ Value& operator=(Value&&); /* Assignment is implemented in terms of constructors: first an instance is created from the input data and then move assignment is used to place the result into this instance. */ template Value& operator=(T&& x) { try { *this = Value(std::forward(x)); return *this; } CATCH_AND_WRAP } public: /** @name Conversion to C++ Types Attempt to convert value of non-compatible type throws an error. */ //@{ operator int() const; operator unsigned() const; operator int64_t() const; operator uint64_t() const; operator float() const; operator double() const; explicit operator bool() const; operator mysqlx::string() const; explicit operator std::string() const; template explicit operator std::basic_string() const { return this->operator mysqlx::string(); } explicit operator bytes() const; operator DbDoc() const; template T get() const; //@} bytes getRawBytes() const { try { size_t len; const byte *ptr = get_bytes(&len); return { ptr, len }; } CATCH_AND_WRAP } /** Return type of the value stored in this instance (or VNULL if no value is stored). */ Type getType() const; /// Convenience method for checking if value is null. bool isNull() const { return VNULL == getType(); } /** Check if document value contains given (top-level) field. Throws error if this is not a document value. */ bool hasField(const Field&) const; /** If this value is not a document, throws error. Otherwise returns value of given field of the document. */ const Value& operator[](const Field&) const; const Value& operator[](const char *name) const { return (*this)[Field(name)]; } const Value& operator[](const mysqlx::string &name) const { return (*this)[Field(name)]; } /** Access to elements of an array value. If non-array value is accessed like an array, an error is thrown. */ //@{ iterator begin(); const_iterator begin() const; iterator end(); const_iterator end() const; size_t elementCount() const; const Value& operator[](unsigned) const; const Value& operator[](int pos) const { assert(pos >= 0); return operator[]((unsigned)pos); } //@} /// Print the value to a stream. void print(std::ostream &out) const { switch (m_type) { case DOC: out << m_doc; return; case ARR: { bool first = true; out << "["; for (auto it = m_arr->begin();it!=m_arr->end();++it) { if (!first) { out << ", "; } else { first = false; } switch (it->get_type()) { case common::Value::STRING: case common::Value::USTRING: case common::Value::EXPR: out << R"(")" << *it << R"(")"; break; default: out << *it; break; } } out << "]"; return; } default: common::Value::print(out); return; } } protected: enum { VAL, ARR, DOC } m_type = VAL; void check_type(Type t) const { if (getType() != t) throw Error("Invalid value type"); } bool is_expr() const { return VAL == m_type && common::Value::EXPR == common::Value::get_type(); } void set_as_expr() { common::Value::m_type = common::Value::EXPR; } /* TODO: Instead extend common::Value with array and document types. Requires moving DbDoc code to the common layer. */ typedef std::vector Array; DLL_WARNINGS_PUSH DbDoc m_doc; // Note: shared with other Value instances for the same array. std::shared_ptr m_arr; DLL_WARNINGS_POP public: friend SessionSettings; friend DbDoc; ///@cond IGNORE friend mysqlx::string; ///@endcond IGNORE struct INTERNAL Access; friend Access; }; static const Value nullvalue; inline Value& Value::operator=(Value &&other) { m_type = other.m_type; switch (m_type) { case VAL: common::Value::operator=(std::move(other)); break; case DOC: m_doc = std::move(other.m_doc); break; case ARR: m_arr = std::move(other.m_arr); break; default: break; } return *this; } namespace internal { /* Helper class to identify usage of expressions */ class Expression : public mysqlx::Value { Expression() {} template Expression(V&& val) : Value(std::forward(val)) { set_as_expr(); } template Expression(const V& val) : Value(val) { set_as_expr(); } friend Expression expr(std::string&& s); friend Expression expr(const std::string& s); }; /** Function which indicates that a given string should be treated as expression. If `s` is a string value, then in contexts where values are expected, `expr(s)` treats `s` as a DevAPI expression. For example statement table.select("foo > 1").execute(); returns the string `"foo 1"` for each row in the table while table.select(expr("foo > 1")).execute(); returns true/false, depending on the value of the expression. @ingroup devapi */ inline internal::Expression expr(std::string&& e) { return std::forward(e); } inline internal::Expression expr(const std::string& e) { return e; } } // internal using internal::expr; inline Value::Type Value::getType() const { switch (m_type) { case ARR: return ARRAY; case DOC: return DOCUMENT; case VAL: switch (common::Value::get_type()) { case common::Value::VNULL: return VNULL; case common::Value::UINT64: return UINT64; case common::Value::INT64: return INT64; case common::Value::FLOAT: return FLOAT; case common::Value::DOUBLE: return DOUBLE; case common::Value::BOOL: return BOOL; case common::Value::STRING: return STRING; case common::Value::USTRING: return STRING; case common::Value::RAW: return RAW; case common::Value::EXPR: return STRING; case common::Value::JSON: return DOCUMENT; } } return VNULL; // quiet compiler warning } /* Value type conversions ---------------------- TODO: more informative errors */ inline Value::Value(const std::initializer_list &list) : m_type(ARR) { try { m_arr = std::make_shared(list); } CATCH_AND_WRAP } template inline Value::Value(Iterator begin_, Iterator end_) : m_type(ARR) { try { m_arr = std::make_shared(begin_, end_); } CATCH_AND_WRAP } inline Value::Value(common::Value &&other) try : common::Value(std::move(other)) {} CATCH_AND_WRAP inline Value::Value(const common::Value &other) try : common::Value(other) {} CATCH_AND_WRAP inline Value::Value() {} inline Value::Value(std::nullptr_t) {} inline Value::Value(int64_t val) try : common::Value(val) {} CATCH_AND_WRAP inline Value::Value(uint64_t val) try : common::Value(val) {} CATCH_AND_WRAP template<> inline int Value::get() const { try { int64_t val = get_sint(); if (val > std::numeric_limits::max()) throw Error("Numeric conversion overflow"); if (val < std::numeric_limits::min()) throw Error("Numeric conversion overflow"); return (int)val; } CATCH_AND_WRAP } inline Value::operator int() const { return get(); } template<> inline unsigned Value::get() const { try { uint64_t val = get_uint(); if (val > std::numeric_limits::max()) throw Error("Numeric conversion overflow"); return (unsigned)val; } CATCH_AND_WRAP } inline Value::operator unsigned() const { return get(); } template<> inline int64_t Value::get() const { try { return get_sint(); } CATCH_AND_WRAP } inline Value::operator int64_t() const { return get(); } template<> inline uint64_t Value::get() const { try { return get_uint(); } CATCH_AND_WRAP } inline Value::operator uint64_t() const { return get(); } inline Value::Value(float val) try : common::Value(val) {} CATCH_AND_WRAP template<> inline float Value::get() const { try { return get_float(); } CATCH_AND_WRAP } inline Value::operator float() const { return get(); } inline Value::Value(double val) try : common::Value(val) {} CATCH_AND_WRAP template<> inline double Value::get() const { try { return get_double(); } CATCH_AND_WRAP } inline Value::operator double() const { return get(); } inline Value::Value(bool val) try : common::Value(val) {} CATCH_AND_WRAP template<> inline bool Value::get() const { try { return get_bool(); } CATCH_AND_WRAP } inline Value::operator bool() const { return get(); } inline Value::Value(const DbDoc &doc) try : m_type(DOC) , m_doc(doc) {} CATCH_AND_WRAP inline Value::Value(const mysqlx::string &val) try : common::Value(val) {} CATCH_AND_WRAP inline Value::Value(mysqlx::string &&val) try : common::Value(std::move(val)) {} CATCH_AND_WRAP inline Value::Value(const std::string &val) try : common::Value(val) {} CATCH_AND_WRAP inline Value::Value(std::string &&val) try : common::Value(std::move(val)) {} CATCH_AND_WRAP template<> inline std::wstring Value::get() const { try { return mysqlx::string(this->get_ustring()); } CATCH_AND_WRAP } template<> inline std::string Value::get() const { try { return get_string(); } CATCH_AND_WRAP } inline Value::operator std::string() const { return get(); } template<> inline mysqlx::string Value::get() const { try { return this->get_ustring(); } CATCH_AND_WRAP } inline Value::operator mysqlx::string() const { return get(); } inline Value::Value(const bytes &data) try : common::Value(data.begin(), data.length()) {} CATCH_AND_WRAP template<> inline bytes Value::get() const { return getRawBytes(); } inline Value::operator bytes() const { return get(); } template<> inline DbDoc Value::get() const { check_type(DOCUMENT); return m_doc; } inline Value::operator DbDoc() const { return get(); } inline bool Value::hasField(const Field &fld) const { check_type(DOCUMENT); return m_doc.hasField(fld); } inline const Value& Value::operator[](const Field &fld) const { check_type(DOCUMENT); return m_doc[fld]; } inline int DbDoc::fieldType(const Field &fld) const { return (*this)[fld].getType(); } // Array access inline Value::iterator Value::begin() { if (ARR != m_type) throw Error("Attempt to iterate over non-array value"); return m_arr->begin(); } inline Value::const_iterator Value::begin() const { if (ARR != m_type) throw Error("Attempt to iterate over non-array value"); return m_arr->begin(); } inline Value::iterator Value::end() { if (ARR != m_type) throw Error("Attempt to iterate over non-array value"); return m_arr->end(); } inline Value::const_iterator Value::end() const { if (ARR != m_type) throw Error("Attempt to iterate over non-array value"); return m_arr->end(); } inline const Value& Value::operator[](unsigned pos) const { check_type(ARRAY); return m_arr->at(pos); } inline size_t Value::elementCount() const { check_type(ARRAY); return m_arr->size(); } MYSQLX_ABI_END(2,0) } // mysqlx #endif