devapi/document.cc (293 lines of code) (raw):
/*
* Copyright (c) 2015, 2024, 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 designed to work 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 either included with
* the program or referenced in the documentation.
*
* Without limiting anything contained in the foregoing, this file,
* which is part of Connector/C++, is also subject to the
* Universal FOSS Exception, version 1.0, a copy of which can be found at
* https://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
*/
#include <mysql/cdk.h>
#include <mysqlx/xdevapi.h>
#include <json_parser.h>
/**
@file
Implementation of DbDoc and related classes.
*/
#include "impl.h"
#include <vector>
#include <sstream>
#include <iomanip>
#include <memory>
using namespace ::mysqlx;
using std::endl;
// Value::get specialization to allow convertion to common::Value type
// --------------------
template <>
common::Value Value::get<common::Value>() const {
if (getType() == DOCUMENT) {
return common::Value::Access::mk_json(m_doc.get_json());
}
return *this;
}
// DbDoc implementation
// --------------------
DbDoc::DbDoc(const std::string &json)
{
try {
m_impl = std::make_shared<Impl::JSONDoc>(json);
}
CATCH_AND_WRAP
}
DbDoc::DbDoc(std::string &&json)
{
try {
m_impl = std::make_shared<Impl::JSONDoc>(std::move(json));
}
CATCH_AND_WRAP
}
DbDoc::DbDoc(const std::shared_ptr<Impl> &impl)
: m_impl(impl)
{}
const char* DbDoc::get_json() const
{
return m_impl ? m_impl->get_json() : "";
}
bool DbDoc::hasField(const Field &fld) const
{
try {
return m_impl && m_impl->has_field(fld);
}
CATCH_AND_WRAP
}
const Value& DbDoc::operator[](const Field &fld) const
{
if (!m_impl)
throw std::out_of_range("empty document");
try {
return m_impl->get(fld);
}
catch (const std::out_of_range&)
{
throw;
}
catch (...)
{
try { throw; }
CATCH_AND_WRAP
}
}
void DbDoc::print(std::ostream &out) const
{
try {
if (m_impl)
m_impl->print(out);
else
out << "{}";
}
CATCH_AND_WRAP
}
// JSON document
// -------------
/*
JSON processor which builds document implementation adding
key-value pairs to document's map.
*/
struct DbDoc::Impl::Builder
: public cdk::JSON::Processor
, public cdk::JSON::Processor::Any_prc
, public cdk::JSON::Processor::Any_prc::Scalar_prc
{
Map &m_map;
mysqlx::string m_key;
public:
Builder(DbDoc::Impl &doc)
: m_map(doc.m_map)
{}
// JSON processor (to build the docuemnt)
void doc_begin()
{
m_map.clear();
}
void doc_end()
{}
cdk::JSON::Processor::Any_prc*
key_val(const cdk::string &key)
{
m_key = key;
// Return itself to process key value
return this;
}
/*
Builder for array values.
*/
struct Arr_builder
: cdk::JSON::Processor::Any_prc
, cdk::JSON::Processor::Any_prc::List_prc
, cdk::JSON::Processor::Any_prc::Scalar_prc
{
Value::Array *m_arr;
// List processor (to build the list)
void list_begin()
{
m_arr->clear();
}
void list_end() {}
Element_prc* list_el()
{
// Return itself to process list element.
return this;
}
// Any processor (to process elements of the list)
// Handle sub-arrray element.
std::unique_ptr<Arr_builder> m_arr_builder;
List_prc* arr()
{
// Create array value.
Value sub;
sub.m_type = Value::ARR;
sub.m_arr = std::make_shared<Value::Array>();
// Create builder for the sub-array.
m_arr_builder.reset(new Arr_builder());
m_arr_builder->m_arr = sub.m_arr.get();
// Append the sub-array to the main array.
m_arr->emplace_back(sub);
return m_arr_builder.get();
}
// Handle a document element
std::unique_ptr<Builder> m_doc_builder;
Doc_prc* doc()
{
// Create document value and append it to the array.
Value sub;
sub.m_type = Value::DOC;
sub.m_doc.m_impl = std::make_shared<DbDoc::Impl>();
m_arr->emplace_back(sub);
// Create builder for the document and return it as the processor.
m_doc_builder.reset(new Builder(*sub.m_doc.m_impl));
return m_doc_builder.get();
}
// Handle scalar values using itself as a processor.
Scalar_prc* scalar()
{ return this; }
// Sclar processor (to store scalar values in the list)
void null() { m_arr->emplace_back(Value()); }
void str(const cdk::string &val)
{
m_arr->emplace_back(mysqlx::string(val));
}
void num(uint64_t val) { m_arr->emplace_back(val); }
void num(int64_t val) { m_arr->emplace_back(val); }
void num(float val) { m_arr->emplace_back(val); }
void num(double val) { m_arr->emplace_back(val); }
void yesno(bool val) { m_arr->emplace_back(val); }
}
m_arr_builder;
cdk::JSON::Processor::Any_prc::List_prc*
arr()
{
using mysqlx::Value;
Value &arr = m_map[m_key];
// Turn the value to one storing an array.
arr.m_type = Value::ARR;
arr.m_arr = std::make_shared<Value::Array>();
// Set up array builder for the new value.
m_arr_builder.m_arr = arr.m_arr.get();
return &m_arr_builder;
}
std::unique_ptr<Builder> m_doc_builder;
cdk::JSON::Processor::Any_prc::Doc_prc*
doc()
{
using mysqlx::Value;
Value &sub = m_map[m_key];
// Turn the value to one storing a document.
sub.m_type = Value::DOC;
sub.m_doc.m_impl = std::make_shared<DbDoc::Impl>();
// Use another builder to build the sub-document.
m_doc_builder.reset(new Builder(*sub.m_doc.m_impl));
return m_doc_builder.get();
}
cdk::JSON::Processor::Any_prc::Scalar_prc*
scalar()
{
return this;
}
/*
Callbacks for scalar values store the value under
key given by m_key.
*/
void null() { m_map.emplace(m_key, Value()); }
void str(const cdk::string &val)
{
m_map.emplace(m_key, mysqlx::string(val));
}
void num(uint64_t val) { m_map.emplace(m_key, val); }
void num(int64_t val) { m_map.emplace(m_key, val); }
void num(float val) { m_map.emplace(m_key, val); }
void num(double val) { m_map.emplace(m_key, val); }
void yesno(bool val) { m_map.emplace(m_key, val); }
};
void DbDoc::Impl::JSONDoc::prepare()
{
if (m_parsed)
return;
cdk::Codec<cdk::TYPE_DOCUMENT> codec;
Builder bld(*this);
codec.from_bytes(cdk::bytes(m_json), bld);
m_parsed = true;
}
/*
Parse JSON string and build a corresponding Value.
*/
Value Value::Access::mk_from_json(const std::string &json)
{
/*
Define builder which acts as JSON value processor and
builds the corresponding Value object.
*/
struct Builder
: public cdk::JSON::Processor
, cdk::JSON_processor
, cdk::JSON::Processor::Any_prc
{
Value *m_val = NULL;
// JSON::Processor
Any_prc* key_val(const string&) override
{
return this;
}
// Any_prc
Scalar_prc *scalar() override
{ return this; }
std::unique_ptr<DbDoc::Impl::Builder> m_doc_builder;
Doc_prc *doc() override
{
m_val->m_type = DOC;
m_doc_builder.reset(new DbDoc::Impl::Builder(*m_val->m_doc.m_impl));
return m_doc_builder.get();
}
DbDoc::Impl::Builder::Arr_builder m_arr_builder;
List_prc *arr() override
{
m_val->m_type = ARR;
m_val->m_arr = std::make_shared<Array>();
m_arr_builder.m_arr = m_val->m_arr.get();
return &m_arr_builder;
}
// JSON_processor
void null() override
{}
void str(const cdk::string &val) override
{
*m_val = (mysqlx::string)val;
}
void num(uint64_t val) override
{ *m_val = val; }
void num(int64_t val) override
{ *m_val = val; }
void num(float val) override
{ *m_val = val; }
void num(double val) override
{ *m_val = val; }
void yesno(bool val) override
{ *m_val = val; }
}
builder;
// Invoke parser to build the value.
Value val;
builder.m_val = &val;
/*
Note: json can be not only an object, but also scalar or array. Since
JSON_parser can parse only documents, we do a trick of parsing document
of the form { "doc": json } and builder ignores the top-level "doc"
field.
*/
parser::JSON_parser parser(std::string("{ \"doc\":") + json + "}");
parser.process(builder);
return val;
}
/*
Iterating over document fields
------------------------------
Iterator functionality is implemented by document implementation
object in forms of these methods:
- reset() - restart iteration form the beginning,
- next() - move to next document field,
- at_end() - true if all fields have been enumerated,
- get_current_fld() - return current field in the sequence.
Note: Since document implementation acts as an iterator, only one
iterator can be used at a time. Creating new iterator will invalidate
other iterators.
Note: Iterator takes shared ownership of document implementation
so it can be used even if original document was destroyed.
TODO: Use common::Iterator<> template instead?
*/
DbDoc::Iterator DbDoc::begin()
{
try {
Iterator it;
m_impl->reset();
it.m_impl = m_impl;
it.m_end = false;
return it;
}
CATCH_AND_WRAP
}
DbDoc::Iterator DbDoc::end()
{
try {
/*
Iterator that points one-past-the-end-of-sequence has no
real representation - we simply set m_end flag in it.
*/
Iterator it;
it.m_end = true;
return it;
}
CATCH_AND_WRAP
}
const Field& DbDoc::Iterator::operator*()
{
if (m_end)
THROW("dereferencing past-the-end iterator");
try {
return m_impl->get_current_fld();
}
CATCH_AND_WRAP
}
DbDoc::Iterator& DbDoc::Iterator::operator++()
{
try {
// only non-end iterator can be incremented.
if (!m_end)
m_impl->next();
return *this;
}
CATCH_AND_WRAP
}
bool DbDoc::Iterator::operator==(const Iterator &other) const
{
try {
/*
if this is end iterator, other is equal if it is also end
iterator or it is at the end of sequence. And vice-versa.
*/
if (m_end)
return other.m_end || other.m_impl->at_end();
if (other.m_end)
return m_end || m_impl->at_end();
/*
Otherwise two iterators are equal if they use the same
document implementation (but such two iterators should not
be used at the same time).
*/
return m_impl == other.m_impl;
}
CATCH_AND_WRAP
}