Skip to content
Snippets Groups Projects
Commit 89f7f8e4 authored by Daniel Hornung's avatar Daniel Hornung
Browse files

WIP: Insert, delete, update

parent 0b774f3f
No related branches found
No related tags found
1 merge request!3Full functionality of libcaosdb
Pipeline #11990 failed
......@@ -105,8 +105,7 @@ string(REGEX REPLACE ";" ":" _MKOCTFILE_RPATH "${CONAN_LIB_DIRS}")
set(_MKOCTFILE_OPTIONS "-Wl,-rpath,${_MKOCTFILE_RPATH}" "--mex" "-std=gnu++17"
"-L/usr/local/lib" ${_MKOCTFILE_INCLUDES} ${_MKOCTFILE_LIB_DIRS} ${_MKOCTFILE_LIBS})
add_custom_target(mex ALL
DEPENDS maoxdb)
add_custom_target(mex ALL)
file(GLOB_RECURSE _CPP_SOURCES RELATIVE "${PROJECT_SOURCE_DIR}/src" src/private/*.cpp)
foreach(sourcefile ${_CPP_SOURCES})
string(REGEX REPLACE ".cpp$" ".mex" _mex_ext_file ${sourcefile})
......@@ -119,7 +118,7 @@ foreach(sourcefile ${_CPP_SOURCES})
ARGS ${_MKOCTFILE_OPTIONS} ${_mkoct_output} ${_abs_source}
MAIN_DEPENDENCY ${_abs_source}
)
add_custom_target(${_target_name} DEPENDS ${_mex_ext_file})
add_custom_target(${_target_name} DEPENDS ${_mex_ext_file} maoxdb)
add_dependencies(mex ${_target_name})
endforeach(sourcefile)
......
......@@ -145,7 +145,43 @@ classdef Caosdb < handle
try
[collection, count_results] = maox_query(obj.connection, query_str);
entities = maox_convert_collection(collection);
if (count_results >= 0)
if count_results >= 0
entities = count_results;
end
catch
% disp("error handling in Caosdb.m");
% disp(lasterror());
rethrow(lasterror());
end
end
%%
% Insert an entity
%
% entities = Caosdb.query("FIND Record Foo WITH bar=baz")
% entities = Caosdb.query("COUNT Record Foo WITH bar=baz")
%
% Parameters
% ----------
%
% query_str: string
% The query to be executed.
%
% Returns
% -------
% entities : cell array
% The retrieved entities. If the query was a COUNT query, the result is an int64 instead.
function entities = tr(obj, query_str)
% Ensure that QUERY is a string.
assert(ischar(query_str), "maox:InvalidArgument", "QUERY must be a string, was:\n%s", ...
disp(query_str));
assert(nargin == 2, "maox:InvalidArgument", "This method accepts exactly 1 argument.");
% disp("query:");
% disp(query_str);
try
[collection, count_results] = maox_query(obj.connection, query_str);
entities = maox_convert_collection(collection);
if count_results >= 0
entities = count_results;
end
catch
......
......@@ -27,10 +27,12 @@
#include "maoxdb.hpp"
#include "mex.h"
#include "caosdb/connection.h"
#include "caosdb/status_code.h"
#include "caosdb/transaction.h"
#include "caosdb/transaction_status.h"
#include <boost/lexical_cast.hpp>
#include <map>
namespace maoxdb {
using std::string;
......@@ -230,20 +232,97 @@ auto mxFromCaosDBMessages(const caosdb::entity::Messages &messages) -> mxArray *
/*
* Set an Entity object's data (parents, properties, name etc.) from the data in ARRAY.
*/
void assignEntityDataFromMx(caosdb::entity::Entity &entity, const mxArray *array) {
entity.SetRole(mxGetStdString(mxGetField(array, 0, "role")));
entity.SetName(mxGetStdString(mxGetField(array, 0, "name")));
entity.SetDescription(mxGetStdString(mxGetField(array, 0, "description")));
entity.SetDatatype(mxGetStdString(mxGetField(array, 0, "datatype")));
entity.SetUnit(mxGetStdString(mxGetField(array, 0, "unit")));
// entity.SetValue();
void assignEntityDataFromMx(caosdb::entity::Entity &entity, const mxArray *array, mwSize index) {
entity.SetRole(mxGetStdString(mxGetField(array, index, "role")));
entity.SetName(mxGetStdString(mxGetField(array, index, "name")));
entity.SetDescription(mxGetStdString(mxGetField(array, index, "description")));
entity.SetDatatype(mxGetStdString(mxGetField(array, index, "datatype")));
entity.SetUnit(mxGetStdString(mxGetField(array, index, "unit")));
entity.SetValue(mxScalarToString(mxGetField(array, index, "value")));
// TODO Remove existing parents and properties.
for (auto parent : parentsFromMx(mxGetField(array, index, "parents"))) {
entity.AppendParent(parent);
}
for (auto property : propertiesFromMx(mxGetField(array, index, "properties"))) {
entity.AppendProperty(property);
}
}
/*
* Create a vector of Entity objects from the data in ARRAY.
*/
// auto entitiesFromMx(const mxArray *array, bool to_update = false)
// -> std::vector<caosdb::entity::Entity> {}
auto entitiesFromMx(const mxArray *array, bool for_update, string conn_name)
-> std::vector<caosdb::entity::Entity> {
auto numel = mxGetNumberOfElements(array);
std::vector<caosdb::entity::Entity> entities(numel);
// for_update: Retrieve entities and assign everything but ID ///////////////
if (for_update) {
std::shared_ptr<caosdb::connection::Connection> connection = nullptr;
if (conn_name.empty()) {
connection = caosdb::connection::ConnectionManager::GetDefaultConnection();
} else {
connection = caosdb::connection::ConnectionManager::GetConnection(conn_name);
}
std::map<string, mwSize> idMap; // To get the Entity data for each ID
auto transaction = connection->CreateTransaction();
for (mwSize i = 0; i < numel; ++i) {
auto id = mxGetStdString(mxGetField(array, i, "id"));
idMap[id] = i;
transaction->RetrieveById(id);
}
transaction->ExecuteAsynchronously();
auto t_stat = transaction->WaitForIt();
maoxdb::throwOctExceptionIfError(transaction.get());
const auto &results = transaction->GetResultSet();
// Update the Entity contents
for (auto entity : results) {
auto index = idMap[entity.GetId()];
auto remote_version = entity.GetVersionId();
auto local_version = mxGetStdString(mxGetField(array, index, "versionId"));
if (remote_version != local_version) {
throw std::invalid_argument(
"Version is not at HEAD of remote, but this is required for an update action.");
}
assignEntityDataFromMx(entity, array, index);
}
}
// insert only: No ID, only assign other values ///////////////////////////
else {
for (mwSize i = 0; i < numel; ++i) {
assignEntityDataFromMx(entities[i], array, i);
}
}
return entities;
}
/*
* Extract the parents from the data in ARRAY.
*/
auto parentsFromMx(const mxArray *parentsArray) -> std::vector<caosdb::entity::Parent> {
std::vector<caosdb::entity::Parent> parents(mxGetNumberOfElements(parentsArray));
for (mwSize i = 0; i < mxGetNumberOfElements(parentsArray); ++i) {
parents[i].SetId(mxGetStdString(mxGetField(parentsArray, i, "id")));
parents[i].SetName(mxGetStdString(mxGetField(parentsArray, i, "name")));
}
return parents;
}
/*
* Extract the properties from the data in ARRAY.
*/
auto propertiesFromMx(const mxArray *propertiesArray) -> std::vector<caosdb::entity::Property> {
std::vector<caosdb::entity::Property> properties(mxGetNumberOfElements(propertiesArray));
for (mwSize i = 0; i < mxGetNumberOfElements(propertiesArray); ++i) {
properties[i].SetId(mxGetStdString(mxGetField(propertiesArray, i, "id")));
properties[i].SetName(mxGetStdString(mxGetField(propertiesArray, i, "name")));
properties[i].SetDescription(mxGetStdString(mxGetField(propertiesArray, i, "description")));
properties[i].SetImportance(mxGetStdString(mxGetField(propertiesArray, i, "importance")));
properties[i].SetDatatype(mxGetStdString(mxGetField(propertiesArray, i, "datatype")));
properties[i].SetUnit(mxGetStdString(mxGetField(propertiesArray, i, "unit")));
properties[i].SetValue(mxScalarToString(mxGetField(propertiesArray, i, "value")));
}
return properties;
}
///////////////////////////////////////////////////////////////////////////////
// Utility functions //////////////////////////////////////////////////////////
......@@ -254,7 +333,7 @@ void assignEntityDataFromMx(caosdb::entity::Entity &entity, const mxArray *array
auto mxMergeScalarStructs(const std::vector<const mxArray *> &structs) -> mxArray * {
// We need the field names first to create a new struct
auto nFields = (size_t)mxGetNumberOfFields(structs[0]);
auto nFields = (mwSize)mxGetNumberOfFields(structs[0]);
auto fields = std::vector<const char *>(nFields);
for (mwIndex i = 0; i < nFields; ++i) {
fields[i] = mxGetFieldNameByNumber(structs[0], i);
......@@ -307,8 +386,23 @@ auto mxScalarToString(const mxArray *array) -> string {
string result = *((bool *)mxGetData(array)) ? "TRUE" : "FALSE";
return result;
}
std::cout << "Unknown type!" << std::endl;
std::cerr << "Unknown type!" << std::endl; // libcaosdb will provide better features soon, so no
// need to put more work into this for the moment.
return "Unknown type!";
}
/*
* @brief Convert the mx cell string array to a string vector.
*/
auto mxCellToStrings(const mxArray *array) -> std::vector<std::string> {
if (!mxIsChar(array)) {
throw std::invalid_argument("Must be a cell array of strings.");
}
std::vector<string> result(mxGetNumberOfElements(array));
for (mwIndex i = 0; i < mxGetNumberOfElements(array); ++i) {
result.push_back(mxGetStdString(mxGetCell(array, i)));
}
return result;
}
} // namespace maoxdb
......@@ -33,7 +33,7 @@
* - parents: Struct array with the following fields:
* - id
* - name
* - description
* - description <
* - properties: Struct array with the following fields:
* - id
* - name
......@@ -76,32 +76,60 @@ auto mxFromCaosDBEntity(const caosdb::entity::Entity &entity) -> mxArray *;
/**
* @brief Set an Entity object's data (parents, properties, name etc.) from the data in ARRAY.
*
* @details The id and versionId are not modified. Either they exist already or they stay unset.
* @details The id and versionId are not modified. Either they exist already or they remain
* unset.
*
* @param entity The entity to be updated.
*
* @param array The 1x1 struct array with the data.
* @param array The struct array with the data.
*
* @param index The index of the array from which to take the data. Default is 0.
*/
auto assignEntityDataFromMx(caosdb::entity::Entity &entity, const mxArray* array) -> void;
auto assignEntityDataFromMx(caosdb::entity::Entity &entity, const mxArray *array, mwSize index = 0)
-> void;
/**
* @brief Create a vector of Entity objects from the data in ARRAY.
*
* @details By default, the id and versionId are not set, this is the required state for
* inserting the entity into a CaosDB instance. If `to_update` is `true`, the id and
* inserting the entity into a CaosDB instance. If `for_update` is `true`, the id and
* versionId are matched against the remote HEAD version and set if they are the same.
* If they are not the same, an exception is thrown.
*
* This function is typically used for insert and update actions.
*
* @param array The struct array with the data (may have more than one element).
*
* @param to_update If true, assert that id and versionId match the remote HEAD and the
* @param for_update If true, assert that id and versionId match the remote HEAD and the
* returned Entity.
*
* @return An vector with Entity objects which have their values set according to ARRAY.
* @param conn_name The connection name. Same conventions hold as for other functions.
*
* @return A vector with Entity objects which have their values set according to ARRAY.
*/
auto entitiesFromMx(const mxArray* array, bool to_update=false)
auto entitiesFromMx(const mxArray *array, bool for_update = false, string conn_name = "")
-> std::vector<caosdb::entity::Entity>;
/**
* @brief Extract the parents from the data in ARRAY.
*
* @param array The 1x1 struct array with the data, for example the "parents" field of an
* entity struct.
*
* @return A vector with Parent objects, created from the values in ARRAY.
*/
auto parentsFromMx(const mxArray *parentsArray) -> std::vector<caosdb::entity::Parent>;
/**
* @brief Extract the properties from the data in ARRAY.
*
* @param array The 1x1 struct array with the data, for example the "properties" field of an
* entity struct.
*
* @return A vector with Property objects, created from the values in ARRAY.
*/
auto propertiesFromMx(const mxArray *propertiesArray) -> std::vector<caosdb::entity::Property>;
// Exception and error handling ///////////////////////////////////////////////
/**
......@@ -274,6 +302,11 @@ auto mxGetStdString(const mxArray *array) -> std::string;
*/
auto mxScalarToString(const mxArray *array) -> std::string;
/**
* @brief Convert the mx cell string array to a string vector.
*/
auto mxCellToStrings(const mxArray *array) -> std::vector<std::string>;
} // namespace maoxdb
#endif /* MAOXDB_H */
% This file is a part of the CaosDB Project.
%
% Copyright (C) 2021 IndiScale GmbH <info@indiscale.com>
% Copyright (C) 2021 Daniel Hornung <d.hornung@indiscale.com>
%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU Affero General Public License as
% published by the Free Software Foundation, either version 3 of the
% License, or (at your option) any later version.
%
% 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 Affero General Public License for more details.
%
% You should have received a copy of the GNU Affero General Public License
% along with this program. If not, see <https://www.gnu.org/licenses/>.
%%
% Create and execute a transaction with all possible parameters (which may be empty).
%
% Parameters
% ----------
%
% connection_name : string
% The connection name
%
% retrieve_ids : string cell array
% The IDs to retrieve by ID.
%
% queries : string cell array
% The queries to execute.
%
% inserts : struct array
% The entities to insert, as a struct array (not Entity objects).
%
% updates : struct array
% The entities to update, as a struct array (not Entity objects).
%
% deletes : string cell array
% The IDs to delete.
%
% Returns
% -------
% single_result : cell array
% The retrieved entities. If no entities are returned and the query was a COUNT query, the result
% is an int64 instead with the COUNT result. If there are returned entities AND there is a count
% result, a warning is printed and the result is a cell array with two elements: the entities and
% the count result. NOTE: The arguments should be so that this does not happen, and the behaviour
% may change in the future.
%
% entities, count_result : cell array, int64
% With two output arguments, the first is the cell array with the entities, and the second is the
% COUNT result. The count result is -1 if there was not exactly one COUNT query.
function [entities, count_result] = maox_run_transaction(connection_name, ...
retrieve_ids, queries, inserts, ...
updates, deletes)
% Boilerplate for default values
if nargin == 0
connection_name = "";
end
if nargin < 2
retrieve_ids = {};
end
if nargin < 3
queries = {};
end
if nargin < 4
inserts = struct();
end
if nargin < 5
updates = struct();
end
if nargin < 6
deletes = {};
end
try
% Retrieve and convert
[collection, count_results] = maox_transaction(connection_name, retrieve_ids, queries, ...
inserts, updates, deletes);
entities = maox_convert_collection(collection);
% And some output argument handling
if nargout == 1
if count_results >= 0
entities = count_results;
if not(isempty(entities))
warning("caosdb:LostValues", ...
["Only the count result was returned although there were entities. "
"Consider using two output arguments."]);
end
end
end
catch
% disp("error handling in Caosdb.m");
% disp(lasterror());
rethrow(lasterror());
end
end
......@@ -24,6 +24,7 @@
#include "caosdb/info.h" // for VersionInfo
#include "maoxdb.hpp" // caosDB utils for mex files
#include "mex.h" // for mxArray, mexFunction
#include <algorithm> // for strcmp
#include <cstring> // for strcmp
#include <memory> // for unique_ptr, __shared_ptr_access, shar...
#include <string> // for allocator, char_traits, operator+
......@@ -49,7 +50,9 @@ using std::string;
*
* @param inserts A struct array of Entities to insert.
*
* @param updates A struct array of Entities to update.
* @param updates A struct array of Entities to update. The behaviour is undefined if there are
* several Entities with the same ID. The versionId of all Entities must be the
* same as the remote HEAD.
*
* @param deletes A cell array of IDs to delete.
*
......@@ -58,7 +61,6 @@ using std::string;
void mexFunction(int /*nlhs*/, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {
string conn_name;
string query;
std::vector<string> ids;
std::vector<string> queries;
std::vector<Entity> inserts;
......@@ -74,7 +76,7 @@ void mexFunction(int /*nlhs*/, mxArray *plhs[], int nrhs, const mxArray *prhs[])
}
if (nrhs > 6) {
mexErrMsgIdAndTxt("maox:TooManyArguments",
(string("Can handle at most 6 arguments: ") + helpText).c_str());
(string("Can handle at most 7 arguments: ") + helpText).c_str());
}
if (!mxIsChar(prhs[1])) {
mexErrMsgIdAndTxt("maox:InvalidArgument", "The second argument must be a string.");
......@@ -82,7 +84,22 @@ void mexFunction(int /*nlhs*/, mxArray *plhs[], int nrhs, const mxArray *prhs[])
if (mxGetNumberOfElements(prhs[0]) > 0) {
conn_name = mxArrayToString(prhs[0]);
}
query = mxArrayToString(prhs[1]);
if (nrhs > 1) {
ids = maoxdb::mxCellToStrings(prhs[1]);
}
if (nrhs > 2) {
queries = maoxdb::mxCellToStrings(prhs[2]);
}
if (nrhs > 3) {
inserts = maoxdb::entitiesFromMx(prhs[3], false, conn_name);
}
if (nrhs > 4) {
updates = maoxdb::entitiesFromMx(prhs[4], true, conn_name);
}
if (nrhs > 5) {
ids = maoxdb::mxCellToStrings(prhs[5]);
}
} catch (...) {
mexErrMsgIdAndTxt("maox:ArgumentHandling", "Error while handling the arguments.");
}
......@@ -95,7 +112,17 @@ void mexFunction(int /*nlhs*/, mxArray *plhs[], int nrhs, const mxArray *prhs[])
connection = ConnectionManager::GetConnection(conn_name);
}
auto transaction = connection->CreateTransaction();
transaction->Query(query);
// Fill transaction with content
std::for_each(ids.begin(), ids.end(), [&](const string &id) { transaction->RetrieveById(id); });
std::for_each(queries.begin(), queries.end(),
[&](const string &query) { transaction->Query(query); });
std::for_each(inserts.begin(), inserts.end(),
[&](Entity &ent) { transaction->InsertEntity(&ent); });
std::for_each(updates.begin(), updates.end(),
[&](Entity &ent) { transaction->UpdateEntity(&ent); });
std::for_each(deletes.begin(), deletes.end(),
[&](const string &id) { transaction->DeleteById(id); });
// Execute transaction
transaction->ExecuteAsynchronously();
auto t_stat = transaction->WaitForIt();
maoxdb::throwOctExceptionIfError(transaction.get());
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment