BuildDarkFlame/_deps/mysql-src/include/mysqlx/xdevapi.h

2140 lines
50 KiB
C
Raw Permalink Normal View History

2022-01-02 18:29:32 -06:00
/*
* 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 MYSQL_DEVAPI_H
#define MYSQL_DEVAPI_H
#ifndef __cplusplus
#error This header can be only used with C++ code
#endif
/**
@defgroup devapi X DevAPI Classes
X DevAPI Classes and types. See @ref devapi_ref for introduction.
@defgroup devapi_op Database operations
@ingroup devapi
Classes representing yet-to-be-executed database operations.
Such operations are created by various methods of
@link mysqlx::abi2::r0::Collection `Collection`@endlink or
@link mysqlx::abi2::r0::Table `Table`@endlink classes. Database operation
classes define methods that specify additional operation characteristics
before it gets executed with `execute()` method. The latter
returns a @link mysqlx::abi2::r0::Result `Result`@endlink,
@link mysqlx::abi2::r0::DocResult `DocResult`@endlink or
@link mysqlx::abi2::r0::RowResult `RowResult`@endlink object,
depending on the type of the operation.
@defgroup devapi_res Classes for result processing
@ingroup devapi
Classes used to examine results of a statement and documents or
rows contained in a result.
@defgroup devapi_aux Auxiliary types
@ingroup devapi
*/
/**
@file
The main header for MySQL Connector/C++ DevAPI.
This header should be included by C++ code which uses the DevAPI implemented
by MySQL Connector/C++.
@sa result.h, document.h
@ingroup devapi
*/
/*
X DevAPI public classes are declared in this and other headers included from
devapi/ folder. The main public API classes, such as Session below, contain
declarations of public interface methods. Any obscure details of the public
API, which must be defined in the public header, are factored out
to Session_detail class from which the main Session class inherits.
Among other things, Session_detail declares the implementation class for
Session. This implementation class is opaque and its details are not defined
in the public headers - only in the implementation part. Definitions of
XXX_detail classes can be found in devapi/detail/ sub-folder.
*/
#include "devapi/common.h"
#include "devapi/result.h"
#include "devapi/collection_crud.h"
#include "devapi/table_crud.h"
#include "devapi/settings.h"
#include "devapi/detail/session.h"
namespace mysqlx {
MYSQLX_ABI_BEGIN(2,0)
class Session;
namespace internal {
template <class Base> class Sch_object;
} // internal
/// Collection create/modify Validation options
class CollectionOptions;
/**
The CollectionValidation class defines collection schema and level of
validation.
*/
class CollectionValidation
{
public:
#define COLLECTION_VALIDATION_ENUM(x,y) x=y,
/**
Collection validation level options
\anchor CollectionValidation_Level
*/
enum Level
{
COLLECTION_VALIDATION_LEVEL(COLLECTION_VALIDATION_ENUM)
};
/**
\anchor CollectionValidation_Option
Collection validation options
*/
enum Option
{
COLLECTION_VALIDATION_OPTION(COLLECTION_VALIDATION_ENUM)
LAST
};
private:
struct Data
{
std::string validation_level;
DbDoc validation_schema;
std::bitset<LAST> used;
};
public:
CollectionValidation()
{}
CollectionValidation(const char* json_doc)
: CollectionValidation(DbDoc(json_doc))
{}
/**
Constructor using a document.
Document example:
~~~~~~
{
"level": "Strict",
"schema":
{
"id": "http://json-schema.org/geo",
"$schema": "http://json-schema.org/draft-06/schema#",
"description": "A geographical coordinate",
"type": "object",
"properties":
{
"latitude":
{
"type": "number"
},
"longitude":
{
"type": "number"
}
},
"required": ["latitude", "longitude"]
}
}
}
~~~~~~
Document keys:
- `level`: See CollectionValidation::LEVEL;
- `schema`: See CollectionValidation::SCHEMA;
*/
CollectionValidation(DbDoc doc)
{
for(auto el : doc)
{
if(el == "level")
{
try {
_set(LEVEL, doc[el].get<std::string>());
} catch (const Error& e)
{
std::string err("Unexpected level type: ");
err+=e.what();
throw Error(err.c_str());
}
}
else if(el == "schema")
{
_set(SCHEMA,doc[el].get<DbDoc>());
}
else {
std::string err("Unexpected schema validation field ");
err+=el;
throw Error(err.c_str());
}
}
}
/**
Construct CollectionValidation from list of Option and value pairs.
See @ref CollectionValidation_Option "CollectionValidation::Option"
for possible options.
*/
template<typename... Rest>
CollectionValidation(Option opt, Rest&&... rest)
{
set(opt, std::forward<Rest>(rest)...);
}
/**
Set list of Option and value pairs.
@see CollectionValidation::CollectionValidation
*/
template<typename... Rest>
void set(Rest&&... options)
{
Data tmp_data(m_data);
try {
_set(std::forward<Rest>(options)...);
} catch (...) {
m_data = tmp_data;
throw;
}
}
protected:
/// @cond DISABLED
// Note: Doxygen gets confused here and renders docs incorrectly.
template<typename T,typename... Rest>
void _set(Option opt, T&& v, Rest&&... options)
{
#define SCHEMA_VALIDATION_SET(x,y) case CollectionValidation::x:\
do_set<CollectionValidation::x>(std::forward<T>(v)); break;
switch (opt)
{
COLLECTION_VALIDATION_OPTION(SCHEMA_VALIDATION_SET)
case CollectionValidation::LAST: throw_error("Invalid option."); ; break;
}
_set(std::forward<Rest>(options)...);
}
/// @endcond
void _set() {}
template<CollectionValidation::Option, typename T>
void do_set(T)
{
throw_error("Invalid option value type.");
}
Data m_data;
friend CollectionOptions;
friend Schema;
friend mysqlx::internal::Schema_detail;
};
/**
The CollectionOptions class defines collection create/modify options.
*/
class CollectionOptions
{
public:
#define COLLECTION_OPTIONS_ENUM(x,y) x=y,
/**
\anchor CollectionOptions_Option
Collection options
*/
enum Option
{
COLLECTION_OPTIONS_OPTION(COLLECTION_OPTIONS_ENUM)
LAST
};
private:
struct Data{
CollectionValidation validation;
std::bitset<LAST> used;
bool reuse = false;
};
public:
CollectionOptions()
{}
CollectionOptions(const char* options)
: CollectionOptions(DbDoc(options))
{}
CollectionOptions(const std::string& options)
: CollectionOptions(DbDoc(options))
{}
/**
Constructor using a document.
Document example:
~~~~~~
{
"reuseExisting": true,
"validation":
{
"level": "Strict",
"schema":
{
"id": "http://json-schema.org/geo",
"$schema": "http://json-schema.org/draft-06/schema#",
"description": "A geographical coordinate",
"type": "object",
"properties":
{
"latitude":
{
"type": "number"
},
"longitude":
{
"type": "number"
}
},
"required": ["latitude", "longitude"]
}
}
}
}
~~~~~~
Document keys:
- `reuseExisting` : Same as CollectionOptions::REUSE;
- `validation` : Same as CollectionOptions::VALIDATION;
*/
CollectionOptions(DbDoc options)
{
for(auto el : options)
{
if(el == "reuseExisting")
{
try {
_set(REUSE, options["reuseExisting"].get<bool>());
} catch (const Error& e)
{
std::string err("Wrong value for reuseExisting option: ");
err+=e.what();
throw Error(err.c_str());
}
}
else if(el == "validation")
{
_set(VALIDATION, CollectionValidation(options["validation"].get<DbDoc>()));
}
else {
std::string err("Unexpected collection option ");
err+=el;
throw Error(err.c_str());
}
}
}
CollectionOptions(CollectionValidation validation)
{
set(VALIDATION, validation);
}
/**
Construct CollectionOptions from list of Option and value pairs.
@ref CollectionOptions_Option "CollectionOptions::Option" and
@ref CollectionValidation_Option "CollectionValidation::Option" can both be
used.
Example:
~~~~
schema.createCollection(
"collection_test",
CollectionValidation::LEVEL, CollectionValidation::STRICT,
CollectionOptions::REUSE, true,
CollectionValidation::SCHEMA,
R"(
{
"id": "http://json-schema.org/geo",
"$schema": "http://json-schema.org/draft-06/schema#",
"description": "A geographical coordinate",
"type": "object",
"properties":
{
"latitude": {
"type": "number"
},
"longitude": {
"type": "number"
}
},
"required": ["latitude", "longitude"]
})"
);
~~~~
*/
template<typename... Rest>
CollectionOptions(Option opt, Rest&&... rest)
{
set(opt, std::forward<Rest>(rest)...);
}
template<typename... Rest>
CollectionOptions(CollectionValidation::Option opt, Rest&&... rest)
{
set(opt, std::forward<Rest>(rest)...);
}
/**
Set list of option and value pairs.
@see CollectionOptions::CollectionOptions
*/
template<typename... Rest>
void set(Rest&&... rest)
{
Data tmp_data(m_data);
try {
_set(std::forward<Rest>(rest)...);
} catch (...) {
m_data = std::move(tmp_data);
throw;
}
}
protected:
/// @cond DISABLED
// Note: Doxygen gets confused here and renders docs incorrectly.
template<typename T,typename... Rest>
void _set(Option opt, T&& v, Rest&&... rest)
{
#define COLLECTION_OPTIONS_SET(x,y) case x:\
do_set<x>(std::forward<T>(v)); break;
switch (opt)
{
COLLECTION_OPTIONS_OPTION(COLLECTION_OPTIONS_SET)
case LAST: throw_error("Invalid option."); ; break;
}
_set(std::forward<Rest>(rest)...);
}
/// @endcond
template<typename T,typename... Rest>
void _set(CollectionValidation::Option opt, T&& v, Rest&&... rest)
{
m_data.validation._set(opt, std::forward<T>(v));
_set(std::forward<Rest>(rest)...);
}
void _set(){}
template<CollectionOptions::Option O,typename T>
void do_set(T)
{
throw_error("Invalid option value type.");
}
Data m_data;
friend mysqlx::internal::Schema_detail;
};
/**
Represents a database schema.
A `Schema` instance can be obtained from `Session::getSchema()`
method:
~~~~~~
Session session;
Schema mySchema;
mySchema= session.getSchema("My Schema");
~~~~~~
or it can be directly constructed as follows:
~~~~~~
Session session;
Schema mySchema(session, "My Schema");
~~~~~~
Each `Schema` instance is tied to a particular session and all
the operations on the schema and its objects are performed using
that session. If the session is destroyed, an attempt to use a schema of
that session yields an error.
When creating a `Schema` object, by default no checks are made that
it actually exists in the database. An operation that is executed
on the server and involves such a non-existent schema throws
an error.
@note A `Schema` object should be used by at most one thread at a time. It is
not safe to call its methods by several threads simultaneously. It is
responsibility of the user to ensure this using a synchronization mechanism
such as mutexes.
@ingroup devapi
*/
class Schema
: protected internal::Schema_detail
{
Session *m_sess;
public:
/**
Construct an object representing the named schema.
*/
Schema(Session &sess, const string &name);
/**
Construct an object representing the default schema of the session.
The default schema is the one specified by session creation options.
*/
Schema(Session&);
/**
Get schema name
*/
const string& getName() const
{
return m_name;
}
/**
Get Session object
*/
Session& getSession()
{
return *m_sess;
}
const Session& getSession() const
{
return const_cast<Schema*>(this)->getSession();
}
/**
Check if this schema exists in the database.
@note Involves communication with the server.
*/
bool existsInDatabase() const
{
try {
/*
Note: We get from server a list of schemata filtered by the name of
this schema - if the schema exists the list should not be empty.
*/
internal::Session_detail::Name_src list(*m_sess, m_name);
list.iterator_start();
return list.iterator_next();
}
CATCH_AND_WRAP
}
/**
Create a new collection in the schema.
Returns the created collection. Set `reuse` flag to true to return
an already existing collection with the same name. Otherwise, an attempt
to create a collection which already exists throws an error.
*/
Collection createCollection(const string &name);
Collection createCollection(const string &name, bool);
/**
Create a new collection in the schema, optionally specifying creation
options. Arguments following `name`, if any, are used to construct
CollectionOptions object. See CollectionOptions for possible ways of
specifying the options.
Returns the created collection.
*/
template<typename... Rest>
Collection createCollection(const string &name,
Rest&&... rest);
/**
Modify a collection in the schema specifying modify options. Arguments
following `name` are used to construct CollectionOptions object. See
CollectionOptions for possible ways of specifying the options.
@note CollectionOptions::REUSE is not allowed and, if used, will throw
error.
*/
template<typename... Rest>
void modifyCollection(const string &name, Rest&&... options);
/**
Return an object representing a collection with the given name.
To check if the collection actually exists in the database set
`check_existence` flag to true. Otherwise the returned object can refer to
a non-existing collection. An attempt to use such a non-existing collection
in a database operation throws an error.
@note Checking the existence of a collection involves communication with
the server. If `check_exists` is false, on the other hand, no I/O is
involved when creating a `Collection` object.
*/
Collection getCollection(const string &name, bool check_exists = false);
/**
Return an object representing a table or a view with the given name.
To check if the table actually exists in the database set
`check_existence` flag to true. Otherwise the returned object can refer to
a non-existing table. An attempt to use such a non-existing table
in a database operation throws an error.
@note The returned `Table` object can represent a plain table or
a view. See `Table` class description.
@note Checking the existence of a table involves communication with
the server. If `check_exists` is false, on the other hand, no I/O is
involved when creating a `Table` object.
*/
Table getTable(const string &name, bool check_exists = false);
/**
Get a list of all collections in the schema.
The returned value can be stored in a container that holds `Collection`
objects, such as `std::vector<Collection>`.
*/
CollectionList getCollections()
{
try {
return Collection_src(*this, "%");
}
CATCH_AND_WRAP
}
/**
Get a list of names of all collections in the schema.
The returned value can be stored in a container that holds strings, such as
`std::vector<string>`.
*/
StringList getCollectionNames()
{
try {
return Name_src(*this, COLLECTION, "%");
}
CATCH_AND_WRAP
}
/**
Get a list of all tables and views in the schema.
The returned value can be stored in a container that holds `Table`
objects, such as `std::vector<Table>`.
@note The list also contains views which are represented by `Table` objects
- see `Table` class description.
*/
TableList getTables()
{
try {
return Table_src(*this, "%");
}
CATCH_AND_WRAP
}
/**
Get a list of names of all tables and views in the schema.
The returned value can be stored in a container that holds strings, such as
`std::vector<string>`.
@note The list also contains names of views as views are represented
by `Table` objects - see `Table` class description.
*/
StringList getTableNames()
{
try {
return Name_src(*this, TABLE, "%");
}
CATCH_AND_WRAP
}
/**
Return a table corresponding to the given collection.
The table has two columns: `_id` and `doc`. For each document in
the collection there is one row in the table with `doc` filed holding
the document as a JSON value and `_id` field holding document's identifier.
To check if the collection actually exists in the database set
`check_existence` flag to true. Otherwise the returned table can refer to
a non-existing collection. An attempt to use such a non-existing collection
table throws an error.
@note Checking the existence of a collection involves communication with
the server. If `check_exists` is false, on the other hand, no I/O is
involved when creating the `Table` object.
*/
Table getCollectionAsTable(const string &name, bool check_exists = true);
/**
Drop the given collection from the schema.
This method silently succeeds if a collection with the given name does
not exist.
@note If a table name is passed to the method, it behaves like
dropTable().
*/
void dropCollection(const mysqlx::string& name)
{
try {
Schema_detail::drop_collection(name);
}
CATCH_AND_WRAP
}
friend Collection;
friend Table;
///@cond IGNORE
friend internal::Schema_detail::Name_src;
template <class Base> friend class internal::Sch_object;
///@endcond
};
/*
Database objects that belong to some schema
===========================================
*/
namespace internal {
// Common base for schema objects defining the common API methods.
template <class Base = Db_obj_base>
class Sch_object
: public Base
{
protected:
Schema m_schema;
Sch_object(const Schema &sch, const string &name);
public:
using string = mysqlx::string;
/**
Get database object name
*/
const string& getName() const
{
return Base::m_name;
}
/**
Get Session object
*/
Session& getSession()
{
return m_schema.getSession();
}
/**
Get schema object
*/
const Schema& getSchema() const
{
return m_schema;
}
protected:
std::shared_ptr<common::Session_impl> get_session();
Schema_detail& get_schema()
{
return m_schema;
}
};
} // internal
/**
Represents a collection of documents in a schema.
A collection object can be obtained from `Schema::getCollection()`
method:
~~~~~~
Schema db;
Collection myColl;
myColl= db.getCollection("My Collection");
~~~~~~
or directly constructed as follows:
~~~~~~
Schema db;
Collection myColl(db, "My Collection");
~~~~~~
When creating a `Collection` object, by default no checks are made that
it actually exists in the database. An operation that is executed
on the server and involves such a non-existent collection throws
an error. Call `existsInDatabase()` to check the existence of the collection.
@note A `Collection` object should be used by at most one thread at a time.
It is not safe to call its methods by several threads simultaneously. It is
responsibility of the user to ensure this using a synchronization mechanism
such as mutexes.
@ingroup devapi
*/
class Collection
: protected internal::Sch_object<internal::Collection_detail>
{
public:
Collection(const Schema &sch, const string &name)
try
: Sch_object(sch, name)
{}
CATCH_AND_WRAP
using Sch_object::getName;
using Sch_object::getSession;
using Sch_object::getSchema;
bool existsInDatabase() const
{
try {
Schema::StringList list(m_schema, Schema::COLLECTION, m_name);
return list.begin() != list.end();
}
CATCH_AND_WRAP
}
/**
Get the number of documents in the collection.
*/
uint64_t count();
/*
CRUD operations on a collection
-------------------------------
*/
/**
Return an operation which fetches all documents from the collection.
Call `execute()` on the returned operation object to execute it and get
a `DocResult` object that gives access to the documents. Specify additional
query parameters, such as ordering of the documents, using chained methods
of `CollectionFind` class before making the final call to `execute()`.
@note Any errors related to the operation are reported when the operation
is executed, not when it is created.
@see `CollectionFind`
*/
CollectionFind find()
{
try {
return CollectionFind(*this);
}
CATCH_AND_WRAP;
}
/**
Return an operation which finds documents that satisfy given criteria.
The criteria are specified as a Boolean expression string.
Call `execute()` on the returned operation object to execute it and get
a `DocResult` object that gives access to the documents. Specify additional
query parameters, such as ordering of the documents, using chained methods
of `CollectionFind` class before making the final call to `execute()`.
@note Any errors related to the operation are reported when the operation
is executed, not when it is created.
@see `CollectionFind`
*/
CollectionFind find(const string &cond)
{
try {
return CollectionFind(*this, cond);
}
CATCH_AND_WRAP;
}
/**
Return an operation which adds documents to the collection.
Specify documents to be added in the same way as when calling
`CollectionAdd::add()` method. Make additional calls to `add()` method on
the returned operation object to add more documents. Call `execute()`
to execute the operation and add all specified documents to the collection.
@note Any errors related to the operation are reported when the operation
is executed, not when it is created.
@see `CollectionAdd`
*/
template <typename... Types>
CollectionAdd add(Types... args)
{
try {
CollectionAdd add(*this);
return add.add(args...);
}
CATCH_AND_WRAP;
}
/**
Return an operation which removes documents satisfying given criteria.
The criteria are specified as a Boolean expression string.
Call `execute()` on the returned operation object to execute it and remove
the matching documents. Use chained methods of `CollectionRemove` class
before the final call to `execute()` to further limit the set of documents
that are removed.
@note To remove all documents in the collection, pass "true" as selection
criteria.
@note Any errors related to the operation are reported when the operation
is executed, not when it is created.
@see `CollectionRemove`
*/
CollectionRemove remove(const string &cond)
{
try {
return CollectionRemove(*this, cond);
}
CATCH_AND_WRAP;
}
/**
Return an operation which modifies documents that satisfy given criteria.
The criteria are specified as a Boolean expression string.
Specify modifications to be applied to each document using chained methods
of `CollectionModify` class on the returned operation object. Call
`execute()` to execute the operation and modify matching documents
as specified.
@note To modify all documents in the collection, pass "true" as selection
criteria.
@note Any errors related to the operation are reported when the operation
is executed, not when it is created.
@see `CollectionModify`
*/
CollectionModify modify(const string &expr)
{
try {
return CollectionModify(*this, expr);
}
CATCH_AND_WRAP;
}
/**
Return the document with the given id.
Returns null document if a document with the given id does not exist in
the collection.
*/
DbDoc getOne(const string &id)
{
return find("_id = :id").bind("id", id).execute().fetchOne();
}
/**
Remove the document with the given id.
Does nothing if a document with the given id does not exist in
the collection.
*/
Result removeOne(const string &id)
{
return remove("_id = :id").bind("id", id).execute();
}
/**
Replace the document with the given id by a new one.
Specify the new document as either a `DbDoc` object, a JSON string or
an `expr(docexpr)` argument, where `docexpr` is like a JSON string but
field values are given by expressions to be evaluated on the server.
If a document with the given id does not exist in the collection, nothing
is done and the returned `Result` object indicates that no documents were
modified.
@note If expressions are used, they can not use named parameters because
it is not possible to bind values prior to execution of `replaceOne()`
operation.
*/
Result replaceOne(const string &id, Value &&document)
{
try {
return
Collection_detail::add_or_replace_one(id, std::move(document), true);
}
CATCH_AND_WRAP
}
/**
Add a new document or replace an existing document with the given id.
Specify the new document as either a `DbDoc` object, a JSON string or
an `expr(docexpr)` argument, where `docexpr` is like a JSON string but
field values are given by expressions to be evaluated on the server.
If a document with the given id does not exist, the new document is added
to the collection.
@note If expressions are used, they can not use named parameters because
it is not possible to bind values prior to execution of `addOrReplaceOne()`
operation.
*/
Result addOrReplaceOne(const string &id, Value &&document)
{
try {
return
Collection_detail::add_or_replace_one(id, std::move(document), false);
}
CATCH_AND_WRAP
}
/**
Create index on the collection.
This function creates a named index in the collection using a JSON index
specification.
@param name name for an index to be created
@param idx_spec index specification as a JSON string
@see @ref indexing for information on how to define document
collection indexes.
*/
void createIndex(const string &name, const string &idx_spec)
{
try {
Collection_detail::index_create(name, idx_spec);
}
CATCH_AND_WRAP
}
/**
Drop index on the collection.
@param name name for an index to be dropped
@ingroup devapi_ddl
*/
void dropIndex(const string &name)
{
try {
Collection_detail::index_drop(name);
}
CATCH_AND_WRAP
}
friend CollectionFind;
friend CollectionAdd;
friend CollectionRemove;
friend CollectionModify;
///@cond IGNORE
friend internal::Crud_factory;
///@endcond
};
//------------------------------------------------------------------------------
template<>
inline
void CollectionValidation::do_set<CollectionValidation::SCHEMA>(DbDoc schema)
{
if(m_data.used.test(CollectionValidation::SCHEMA))
throw_error("Validation schema already set.");
m_data.used.set(CollectionValidation::SCHEMA);
m_data.validation_schema = schema;
}
template<>
inline
void CollectionValidation::do_set<CollectionValidation::SCHEMA>(const char* schema)
{
do_set<CollectionValidation::SCHEMA>(DbDoc(schema));
}
template<>
inline
void CollectionValidation::do_set<CollectionValidation::SCHEMA>(std::string schema)
{
do_set<CollectionValidation::SCHEMA>(DbDoc(schema));
}
template<>
inline
void CollectionValidation::do_set<CollectionValidation::LEVEL>(Level level)
{
if(m_data.used.test(CollectionValidation::LEVEL))
throw_error("Validation level already set.");
m_data.used.set(CollectionValidation::LEVEL);
#define SCHEMA_VALIDATION_CASE(x,y) case Level::x: m_data.validation_level = #x; break;
switch (level) {
COLLECTION_VALIDATION_LEVEL(SCHEMA_VALIDATION_CASE)
}
}
template<>
inline
void CollectionValidation::do_set<CollectionValidation::LEVEL>(std::string level)
{
if(m_data.used.test(CollectionValidation::LEVEL))
throw_error("Validation level already set.");
m_data.used.set(CollectionValidation::LEVEL);
m_data.validation_level = level;
}
template<>
inline
void CollectionOptions::do_set<CollectionOptions::REUSE>(bool reuse)
{
if(m_data.used[CollectionOptions::REUSE])
throw_error("Option reuse already set.");
m_data.used.set(CollectionOptions::REUSE);
m_data.reuse = reuse;
}
template<>
inline
void CollectionOptions::do_set<CollectionOptions::VALIDATION>(CollectionValidation validation)
{
if(m_data.used.test(CollectionOptions::VALIDATION) ||
m_data.validation.m_data.used.test(CollectionValidation::LEVEL) ||
m_data.validation.m_data.used.test(CollectionValidation::SCHEMA))
throw_error("Validation already set.");
m_data.used.set(CollectionOptions::VALIDATION);
m_data.validation.m_data.used.set(CollectionValidation::LEVEL);
m_data.validation.m_data.used.set(CollectionValidation::SCHEMA);
m_data.validation = validation;
}
inline
Collection Schema::createCollection(const string &name)
{
try {
Schema_detail::create_collection(
name,
CollectionOptions() );
return Collection(*this, name);
}
CATCH_AND_WRAP
}
inline
Collection Schema::createCollection(const string &name, bool reuse)
{
try {
Schema_detail::create_collection(
name,
CollectionOptions(
CollectionOptions::REUSE, reuse)
);
return Collection(*this, name);
}
CATCH_AND_WRAP
}
template<typename... Rest>
inline
Collection Schema::createCollection(const string &name, Rest&&... rest)
{
try {
Schema_detail::create_collection(name, CollectionOptions(std::forward<Rest>(rest)...));
return Collection(*this, name);
}
CATCH_AND_WRAP
}
template <typename... Opt>
inline
void Schema::modifyCollection(const string &name, Opt&&... options)
{
try {
Schema_detail::modify_collection(name, CollectionOptions(std::forward<Opt>(options)...));
}
CATCH_AND_WRAP
}
inline
Collection Schema::getCollection(const string &name, bool check_exists)
{
Collection coll(*this, name);
if (check_exists && !coll.existsInDatabase())
throw_error("Collection does not exist");
return coll;
}
/*
Table object
============
*/
/**
Represents a table in a schema.
A `Table` object can be obtained from `Schema::getTable()`
method:
~~~~~~
Schema db;
Table myTable;
myTable= db.getTable("My Table");
~~~~~~
or directly constructed as follows:
~~~~~~
Schema db;
Table myTable(db, "My Table");
~~~~~~
A `Table` object can refer to a plain table or to a view. In the latter case
method `isView()` called on the object returns true.
When creating a `Table` object, by default no checks are made that
it actually exists in the database. An operation that is executed
on the server and involves such a non-existent table throws
an error. Call `existsInDatabase()` to check the existence of the table.
@note A `Table` object should be used by at most one thread at a time. It is
not safe to call its methods by several threads simultaneously. It is
responsibility of the user to ensure this using a synchronization mechanism
such as mutexes.
@ingroup devapi
*/
class Table
: protected internal::Sch_object<>
{
public:
Table(const Schema &sch, const string &name)
: Sch_object(sch, name)
{}
Table(const Schema &sch, const string &name, bool is_view)
: Sch_object(sch, name)
{
m_type = is_view ? VIEW : TABLE;
}
using Sch_object::getName;
using Sch_object::getSession;
using Sch_object::getSchema;
bool existsInDatabase() const;
/**
Check if this table object corresponds to a view.
@note This check might involve communication with the server.
*/
bool isView();
/**
Get the number of rows in the table.
*/
uint64_t count()
{
try {
RowResult cnt = select("count(*)").execute();
return cnt.fetchOne()[0].get<uint64_t>();
}
CATCH_AND_WRAP
}
/*
CRUD operations
---------------
*/
/**
Return an operation which inserts rows into the full table without
restricting the columns.
Specify rows to be inserted using methods of `TableInsert` class chained
on the returned operation object. Call `execute()` to execute the operation
and insert the specified rows.
Each specified row must have the same number of values as the number
of columns of the table. If the value count and the table column count do
not match, server reports error when operation is executed.
@note Any errors related to the operation are reported when the operation
is executed, not when it is created.
@see `TableInsert`
*/
TableInsert insert()
{
try {
return TableInsert(*this);
}
CATCH_AND_WRAP;
}
/**
Return an operation which inserts rows into the table restricting
the columns.
Specify column names as method arguments. Values are inserted only into
the specified columns, other columns are set to null or to column's default
value (depending on the definition of the table). Specify rows to
be inserted using methods of `TableInsert` class chained on the returned
operation object. Call `execute()` to execute the operation and insert
the specified values.
Each specified row must have the same number of values as the number
of columns listed here. If the value count and the column count do
not match, server reports error when operation is executed.
@note Any errors related to the operation are reported when the operation
is executed, not when it is created.
@see `TableInsert`
*/
template <class... T>
TableInsert insert(const T&... t)
{
try {
return TableInsert(*this, t...);
}
CATCH_AND_WRAP;
}
/**
Return an operation which selects rows from the table.
Call `execute()` on the returned operation object to execute it and get
a `RowResult` object that gives access to the rows. Specify query
parameters, such as selection criteria and ordering of the rows, using
chained methods of `TableSelect` class before making the final call to
`execute()`. To project selected rows before returning them in the result,
specify a list of expressions as arguments of this method. These expressions
are evaluated to form a row in the result. An expression can be optionally
followed by "AS <name>" suffix to give a name to the corresponding column
in the result. If no expressions are given, rows are returned as is, without
any projection.
@note Any errors related to the operation are reported when the operation
is executed, not when it is created.
@see `TableSelect`
*/
template<typename ...PROJ>
TableSelect select(const PROJ&...proj)
{
try {
return TableSelect(*this, proj...);
}
CATCH_AND_WRAP;
}
/**
Return an operation which removes rows from the table.
Use chained methods of `TableRemove` class on the returned operation object
to specify the rows to be removed. Call `execute()` to execute the operation
and remove the rows.
@note Any errors related to the operation are reported when the operation
is executed, not when it is created.
@see `TableRemove`
*/
TableRemove remove()
{
try {
return TableRemove(*this);
}
CATCH_AND_WRAP;
}
/**
Return an operation which updates rows in the table.
Use chained methods of `TableUpdate` class on the returned operation object
to specify which rows should be updated and what modifications to apply
to each of them. Call `execute()` to execute the operation and remove
the rows.
@note Any errors related to the operation are reported when the operation
is executed, not when it is created.
@see `TableUpdate`
*/
TableUpdate update()
{
try {
return TableUpdate(*this);
}
CATCH_AND_WRAP;
}
private:
enum { TABLE, VIEW, UNKNOWN } m_type = UNKNOWN;
friend TableSelect;
friend TableInsert;
friend TableRemove;
friend TableUpdate;
///@cond IGNORE
friend internal::Crud_factory;
///@endcond
};
inline
bool Table::existsInDatabase() const
{
try {
/*
Note: When checking existence, we also determine if this is a view or
a plain table because this information is fetched from the server when
querying for a list of tables.
*/
Schema::TableList list(m_schema, m_name);
auto it = list.begin();
if (!(it != list.end()))
return false;
const_cast<Table*>(this)->m_type = (*it).isView() ? VIEW : TABLE;
return true;
}
CATCH_AND_WRAP
}
inline
bool Table::isView()
{
try {
/*
If view status was not determined yet, do existence check which
determines it as a side effect.
*/
if (UNKNOWN == m_type)
if (!existsInDatabase())
throw_error("Table does not exist");
return VIEW == m_type;
}
CATCH_AND_WRAP
}
inline
Table Schema::getTable(const string &name, bool check_exists)
{
Table tbl(*this, name);
if (check_exists && !tbl.existsInDatabase())
throw_error("Table does not exist");
return tbl;
}
inline
Table Schema::getCollectionAsTable(const string &name, bool check_exists)
{
if (check_exists && !getCollection(name).existsInDatabase())
throw_error("Collection does not exist");
return { *this, name };
}
inline
uint64_t Collection::count()
{
return m_schema.getCollectionAsTable(m_name).count();
}
using SqlStatement = internal::SQL_statement;
/**
Represents a session which gives access to data stored in a data store.
A `Session` object can be created from a connection string, from
`SessionSettings` or by directly specifying a host name, TCP/IP port and user
credentials. Once created, a session is ready to be used. Session destructor
closes the session and cleans up after it.
If it is not possible to create a valid session for some reason, errors
are thrown from session constructor.
Several hosts can be specified by session creation parameters. In that case
a failed connection to one of the hosts triggers a fail-over attempt
to connect to a different host in the list. Only if none of the hosts could
be contacted, session creation fails.
The fail-over logic tries hosts in the order in which they are specified in
session settings unless explicit priorities are assigned to the hosts. In that
case hosts are tried in the decreasing priority order and for hosts with
the same priority the order in which they are tired is random.
Once a valid session is created using one of the hosts, the session is bound
to that host and never re-connected again. If the connection gets broken,
the session fails without making any other fail-over attempts. The fail-over
logic is executed only when establishing a new session.
@note A `Session` object should be used by at most one thread at a time. It is
not safe to call its methods by several threads simultaneously. It is
responsibility of the user to ensure this using a synchronization mechanism
such as mutexes.
@ingroup devapi
*/
class Session
: private internal::Session_detail
{
public:
/**
Create a session specified by a `SessionSettings` object.
*/
Session(SessionSettings settings)
try
: Session_detail(settings)
{}
CATCH_AND_WRAP
/**
Create a session using given session settings.
This constructor forwards arguments to a `SessionSettings` constructor.
Thus all forms of specifying session options are also directly available
in `Session` constructor.
Examples:
~~~~~~
Session from_uri("mysqlx://user:pwd@host:port/db?ssl-mode=disabled");
Session from_options("host", port, "user", "pwd", "db");
Session from_option_list(
SessionOption::USER, "user",
SessionOption::PWD, "pwd",
SessionOption::HOST, "host",
SessionOption::PORT, port,
SessionOption::DB, "db",
SessionOption::SSL_MODE, SSLMode::DISABLED
);
~~~~~~
@see `SessionSettings`
*/
template<typename...T>
Session(T...options)
try
: Session(SessionSettings(options...))
{}CATCH_AND_WRAP
Session(Session &&other)
try
: internal::Session_detail(std::move(other))
{}CATCH_AND_WRAP
Session(Client &client);
/**
Create a new schema.
Returns the created schema. Set `reuse` flag to true to return an already
existing schema with the same name. Otherwise, an attempt to create
a schema which already exists throws an error.
*/
Schema createSchema(const string &name, bool reuse = false)
{
try {
Session_detail::create_schema(name, reuse);
return Schema(*this, name);
}
CATCH_AND_WRAP
}
/**
Return an object representing a schema with the given name.
To check if the schema actually exists in the database set `check_existence`
flag to true. Otherwise the returned object can refer to a non-existing
schema. An attempt to use such a non-existing schema in a database operation
throws an error.
@note Checking the existence of a schema involves communication with
the server. If `check_exists` is false, on the other hand, no I/O is
involved when creating a `Schema` object.
*/
Schema getSchema(const string &name, bool check_exists = false)
{
Schema sch(*this, name);
if (check_exists && !sch.existsInDatabase())
throw_error("Schema does not exist");
return sch;
}
/**
Get the default schema specified when the session was created.
*/
Schema getDefaultSchema()
{
return Schema(*this, getDefaultSchemaName());
}
/**
Get the name of the default schema specified when the session was created.
*/
string getDefaultSchemaName()
{
try {
return Session_detail::get_default_schema_name();
}
CATCH_AND_WRAP
}
/**
Get a list of all database schemas.
The returned value can be stored in a container that holds `Schema` objects,
such as `std::vector<Schema>`.
*/
SchemaList getSchemas()
{
try {
return Schema_src(*this, "%");
}
CATCH_AND_WRAP
}
// TODO: Should we have getSchemaNames() too?
/**
Drop the named schema.
Error is thrown if the schema doesn't exist,
*/
void dropSchema(const string &name)
{
try {
Session_detail::drop_schema(name);
}
CATCH_AND_WRAP
}
/**
Return an operation which executes an arbitrary SQL statement.
Call `execute()` on the returned operation object to execute the statement
and get an `SqlResult` object representing its results. If the SQL statement
contains `?` placeholders, call `bind()` to define their values
prior to the execution.
@note Errors related to SQL execution are reported when the statement
is executed, not when it is created.
*/
SqlStatement sql(const string &query)
{
try {
return SqlStatement(this, query);
}
CATCH_AND_WRAP
}
/**
Start a new transaction.
Throws error if previously opened transaction is not closed.
*/
void startTransaction()
{
try {
Session_detail::start_transaction();
}
CATCH_AND_WRAP
}
/**
Commit opened transaction, if any.
Does nothing if no transaction was opened. After committing the
transaction is closed.
*/
void commit()
{
try {
Session_detail::commit();
}
CATCH_AND_WRAP
}
/**
Roll back opened transaction, if any.
Does nothing if no transaction was opened. Transaction which was
rolled back is closed. To start a new transaction a call to
`startTransaction()` is needed.
*/
void rollback()
{
try {
Session_detail::rollback();
}
CATCH_AND_WRAP
}
/**
Roll back opened transaction to specified savepoint.
It rolls back to savepoint, but transaction remains active.
Does nothing if no transaction was opened.
@throws Error If savepoint doesn't exist or is empty.
*/
void rollbackTo(const string &savepoint)
{
try {
if (savepoint.empty())
throw_error("Invalid empty save point name");
Session_detail::rollback(savepoint);
}
CATCH_AND_WRAP
}
/**
Sets a named transaction savepoint with a name as
identifier.
To use savepoints a transaction has to be started using startTransaction().
@returns string with savepoint name.
@note If the current transaction has a savepoint with the same name,
the old savepoint is deleted and a new one is set.
*/
string setSavepoint(const string &savepoint)
{
try {
if (savepoint.empty())
throw_error("Invalid empty save point name");
return Session_detail::savepoint_set(savepoint);
}
CATCH_AND_WRAP
}
/**
Creats a transaction savepoint with a generated name as
identifier.
To use savepoints a transaction has to be started using startTransaction().
@returns string with generated savepoint name.
@note If the current transaction has a savepoint with the same name,
the old savepoint is deleted and a new one is set.
*/
string setSavepoint()
{
try {
return Session_detail::savepoint_set();
}
CATCH_AND_WRAP
}
/**
Releases savepoint previously added by setSavepoint().
@note Releasing savepoint doesn't affect data.
@throws Error If savepoint doesn't exist.
*/
void releaseSavepoint(const string &savepoint)
{
try {
if (savepoint.empty())
throw_error("Invalid empty save point name");
Session_detail::savepoint_remove(savepoint);
}
CATCH_AND_WRAP
}
/**
Close this session.
After the session is closed, any call to other session's methods
throws an error.
*/
void close()
{
try {
Session_detail::close();
}
CATCH_AND_WRAP
}
protected:
using internal::Session_detail::m_impl;
public:
friend Schema;
friend Collection;
friend Table;
friend Result;
friend RowResult;
///@cond IGNORE
friend internal::Session_detail;
friend internal::Crud_factory;
friend internal::Result_detail;
template <class Base> friend class internal::Sch_object;
///@endcond
};
/**
Create a client using given client settings.
Client allows the creation of sessions from a session pool.
This constructor forwards arguments to a ClientSettings constructor.
Thus all forms of specifying client options are also directly available
in Client constructor. ClientOptions and SessionOptions can be mixed
when construction Client objects
Examples:
~~~~~~
Client from_uri("mysqlx://user:pwd\@host:port/db?ssl-mode=disabled");
Client from_options("host", port, "user", "pwd", "db");
Client from_option_list(
SessionOption::USER, "user",
SessionOption::PWD, "pwd",
SessionOption::HOST, "host",
SessionOption::PORT, port,
SessionOption::DB, "db",
SessionOption::SSL_MODE, SSLMode::DISABLED
ClientOption::POOLING, true,
ClientOption::POOL_MAX_SIZE, 10,
ClientOption::POOL_QUEUE_TIMEOUT, 1000,
ClientOption::POOL_MAX_IDLE_TIME, 500,
);
~~~~~~
@see ClientSettings
*/
class Client : public internal::Client_detail
{
public:
Client(ClientSettings settings)
try
: Client_detail(settings)
{}
CATCH_AND_WRAP
Client(SessionSettings &settings)
try
: Client_detail(settings)
{}
CATCH_AND_WRAP
template<typename...T>
Client(T...options)
: Client(ClientSettings(options...))
{}
Session getSession()
{
return *this;
}
};
/*
Session
*/
inline
Session::Session(Client &client)
try
: internal::Session_detail(client.get_session_pool())
{}CATCH_AND_WRAP
/**
@ingroup devapi
Function to get Session object.
@param p same as needed by Session constructor.
*/
template<class ...P>
Session getSession(P...p)
{
return Session(p...);
}
/**
@ingroup devapi
Function to get Client object.
@param p same as needed by Client constructor.
*/
template<class ...P>
Client getClient(P...p)
{
return Client(p...);
}
/*
Schema class implementation
*/
inline
Schema::Schema(Session &sess, const string &name)
: Schema_detail(sess.m_impl, name)
, m_sess(&sess)
{}
template <class Base>
inline
internal::Sch_object<Base>::Sch_object(const Schema &sch, const string &name)
: Base(sch.getSession().m_impl, name)
, m_schema(sch)
{}
template <class Base>
inline
std::shared_ptr<common::Session_impl>
internal::Sch_object<Base>::get_session()
{
assert(m_schema.m_sess);
return m_schema.m_sess->m_impl;
}
MYSQLX_ABI_END(2,0)
} // mysqlx
#endif