devapi/crud.cc (284 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 <mysqlx/xdevapi.h>
PUSH_SYS_WARNINGS
#include <time.h>
#include <forward_list>
#include <list>
POP_SYS_WARNINGS
#include "impl.h"
using namespace ::mysqlx::impl::common;
using namespace ::mysqlx::internal;
using namespace ::mysqlx;
/*
Code in this file defines implementations for various CRUD operations used
by X DevAPI. We use common implementations of these operations.
*/
auto Crud_factory::mk_sql(Session &sess, const mysqlx::string &query)
-> Impl*
{
return new Op_sql(sess.m_impl, query);
}
// --------------------------------------------------------------------
/*
Collection CRUD operations
==========================
*/
auto Crud_factory::mk_add(Collection &coll) -> Impl*
{
return new Op_collection_add(
coll.get_session(), Object_ref(coll)
);
}
auto Crud_factory::mk_remove(
Collection &coll, const mysqlx::string &expr
) -> Impl*
{
return new Op_collection_remove(
coll.get_session(), Object_ref(coll), expr
);
}
auto Crud_factory::mk_find(Collection &coll) -> Impl*
{
return new Op_collection_find(
coll.get_session(), Object_ref(coll)
);
}
auto Crud_factory::mk_find(
Collection &coll, const mysqlx::string &expr
) -> Impl*
{
return new Op_collection_find(
coll.get_session(), Object_ref(coll), expr
);
}
auto Crud_factory::mk_modify(
Collection &coll, const mysqlx::string &expr
) -> Impl*
{
return new Op_collection_modify(
coll.get_session(), Object_ref(coll), expr
);
}
struct Replace_cmd
: public Executable<Result, Replace_cmd>
{
Replace_cmd(
internal::Shared_session_impl sess,
const cdk::api::Object_ref &coll,
const std::string &id,
const cdk::Expression &doc
)
{
reset(new Op_collection_replace(
sess, coll, id, doc
));
}
};
struct Upsert_cmd : public Executable<Result, Upsert_cmd>
{
Upsert_cmd(
internal::Shared_session_impl sess,
const cdk::api::Object_ref &coll,
const std::string &id,
cdk::Expression &doc
)
{
reset(new Op_collection_upsert(
sess, coll, id, doc
));
}
};
/*
A helper class used by Collection_detail::add_or_replace_one().
It is a wrapper around CDK expression m_expr that describes a document.
The wrapper forwards this description to a processor, but at the same
time checks if the value of the (top-level) "_id" field equals the value
given in the constructor.
*/
struct Value_expr_check_id
: cdk::Expression
, cdk::Expression::Processor
, cdk::Expression::Processor::Doc_prc
{
mysqlx::Value_expr &m_expr;
bool m_is_expr;
Processor *m_prc;
Doc_prc *m_doc_prc;
/*
This class defines m_any_prc member which is used below
to check the value of "_id" field as reported by the
source expression. Before using this class, m_id_prc must be
set to point at the sub-processor that was given for processing
the value of the "_id" field (this is done by key_val() callback in
the main class).
Then all callbacks are forwarded to this sub-processor (or its
sub-processors) and in case of calling scalar str() callback that
gives string value of the "_id" field, the check is done first.
*/
struct Any_processor_check
: cdk::Expression::Processor::Doc_prc::Any_prc
, cdk::Expression::Processor::Doc_prc::Any_prc::Scalar_prc
, cdk::Expression::Processor::Doc_prc::Any_prc::Scalar_prc::Value_prc
{
Any_prc *m_id_prc;
Scalar_prc *m_scalar_prc;
Value_prc *m_value_prc;
const std::string &m_id;
Any_processor_check(const std::string& id)
: m_id(id)
{}
// Any processor implementation
Scalar_prc* scalar() override
{
m_scalar_prc = m_id_prc->scalar();
return m_scalar_prc ? this : nullptr;
}
List_prc* arr() override
{
return m_id_prc->arr();
}
Doc_prc* doc() override
{
return m_id_prc->doc();
}
//Scalar processor implementation
Value_prc* val() override
{
m_value_prc = m_scalar_prc->val();
return m_value_prc ? this : nullptr;
}
Args_prc* op(const char *name) override
{
return m_scalar_prc->op(name);
}
Args_prc* call(const Object_ref&obj) override
{
return m_scalar_prc->call(obj);
}
void ref(const Column_ref &col, const Doc_path *path) override
{
return m_scalar_prc->ref(col, path);
}
void ref(const Doc_path &path) override
{
return m_scalar_prc->ref(path);
}
void param(const string &val) override
{
return m_scalar_prc->param(val);
}
void param(uint16_t val) override
{
return m_scalar_prc->param(val);
}
void var(const string &name) override
{
m_scalar_prc->var(name);
}
// Value processor implementation
void null() override { m_value_prc->null();}
void value(cdk::Type_info type,
const cdk::Format_info &format,
cdk::foundation::bytes val) override
{
m_value_prc->value(type, format, val);
}
void str(const string &val) override
{
if (m_id != val)
throw mysqlx::Error(R"(Replacement document has an _id that is different than the matched document.)");
m_value_prc->str(val);
}
void num(int64_t val) override { m_value_prc->num(val); }
void num(uint64_t val) override { m_value_prc->num(val); }
void num(float val) override { m_value_prc->num(val); }
void num(double val) override { m_value_prc->num(val); }
void yesno(bool val) override { m_value_prc->yesno(val); }
};
Any_processor_check m_any_prc;
Value_expr_check_id(mysqlx::Value_expr &expr, bool is_expr, const std::string& id)
: m_expr(expr)
, m_is_expr(is_expr)
, m_any_prc(id)
{}
// Expression implementation
void process(Processor& prc) const override
{
auto self = const_cast<Value_expr_check_id*>(this);
self->m_prc = &prc;
m_expr.process(*self);
}
// Expression processor implementation
Scalar_prc* scalar() override
{
return m_prc->scalar();
}
List_prc* arr() override
{
return m_prc->arr();
}
Doc_prc* doc() override
{
m_doc_prc = m_prc->doc();
return m_doc_prc ? this : nullptr;
}
// Doc_prc implementation
void doc_begin() override
{
m_doc_prc->doc_begin();
}
void doc_end() override
{
m_doc_prc->doc_end();
}
Any_prc* key_val(const string &key) override
{
if (string("_id") == key )
{
if (m_is_expr)
mysqlx::throw_error(
R"(Document "_id" will be replaced by expression "_id")"
);
m_any_prc.m_id_prc = m_doc_prc->key_val(key);
return m_any_prc.m_id_prc ? &m_any_prc : nullptr;
}
return m_doc_prc->key_val(key);
}
};
Result
Collection_detail::add_or_replace_one(
const mysqlx::string &id, mysqlx::Value &&doc, bool replace
)
{
/*
This is implemented by executing Replace_cmd or Upsert_command
which internally use Op_collection_replace or Op_collection_upsert
to perform relevant operation on the server.
*/
Object_ref coll(get_schema().m_name, m_name);
std::string id_str(id);
if (!Value::Access::is_expr(doc) &&
doc.getType() == Value::STRING)
{
doc = DbDoc(doc.get<string>());
}
/*
expr is a CDK expression object which describes the document
to be added.
*/
Value_expr expr(doc, parser::Parser_mode::DOCUMENT);
if (replace)
{
/*
Replace_cmd executes Op_collection_replace which picks a document
with the given id and replaes it with the document given as the last
argument.
The document expression is wrapped in Value_expr_check_id to check
if the "_id" field (if present) stores the correct document id
and throws error if it is not the case (otherwise Replace_cmd
would modify the "_id" field to match the given id).
*/
Value_expr_check_id check_id(expr, Value::Access::is_expr(doc), id_str);
Replace_cmd cmd(m_sess, coll, id_str, check_id);
return cmd.execute();
}
else
{
Upsert_cmd cmd(m_sess, coll, std::string(id), expr);
return cmd.execute();
}
}
void Collection_detail::index_drop(const mysqlx::string &name)
{
Object_ref coll(get_schema().m_name, m_name);
Op_idx_drop cmd(m_sess, coll, name);
cmd.execute();
}
void
Collection_detail::index_create(
const mysqlx::string &name, mysqlx::Value &&spec
)
{
switch (spec.getType())
{
case Value::STRING:
break;
default:
// TODO: support other forms: DbDoc, expr("{...}")?
throw_error("Index specification must be a string.");
}
Object_ref coll(get_schema().m_name, m_name);
Op_idx_create cmd(m_sess, coll, name, (std::string)spec);
cmd.execute();
}
// --------------------------------------------------------------------
/*
Table CRUD operations
=====================
*/
auto Crud_factory::mk_insert(Table &table) -> Impl*
{
return new Op_table_insert<Value>(
table.get_session(), Object_ref(table)
);
}
auto Crud_factory::mk_select(Table &table) -> Impl*
{
return new Op_table_select(
table.get_session(), Object_ref(table)
);
}
auto Crud_factory::mk_update(Table &table) -> Impl*
{
return new Op_table_update(
table.get_session(), Object_ref(table)
);
}
auto Crud_factory::mk_remove(Table &table) -> Impl*
{
return new Op_table_remove(
table.get_session(), Object_ref(table)
);
}