// // storagenodedbaccess.cpp // // Created by Frank Vernon on 3/20/14. // Copyright (c) 2014 IOnU. All rights reserved. // Copyright (c) 2016, Sequence Logic, Inc. All rights reserved. // #include "storagenodedbaccess.h" #include "storagenodedbaccess_java.h" #include "../cppcoreobjects/cloudguardurn.h" #include "../cppcoreobjects/permissions.h" #include "eyetime.h" #include "eyelog.h" #include "eyeutils.h" #include #include #include using namespace sequencelogic; /** SQLite3 routine to extract a value from JSON. This is presently limted to elements at the root level of the JSON object. Non-scalar values are returned as BLOBs in JSON format. You could presumably call this again with that result until you get to the scalar value you want. The routine expects two arguments: key, and json. Both as text. */ #ifndef WIN32 static void value_from_json(sqlite3_context * context, int argc, sqlite3_value ** argv) __attribute__ ((unused)); #endif static void value_from_json(sqlite3_context * context, int argc, sqlite3_value ** argv) { //check we have what we need if (argc != 2 || sqlite3_value_type(argv[0]) == SQLITE_NULL || sqlite3_value_type(argv[1]) == SQLITE_NULL) { sqlite3_result_null(context); } sequencelogic::JSONObject parser(reinterpret_cast(sqlite3_value_text(argv[1]))); sequencelogic::JSONObject value = parser.getJSONObject(reinterpret_cast(sqlite3_value_text(argv[0]))); switch (value.gettype()) { case T_JSON::J_NUMBER: sqlite3_result_double(context, value.getdouble()); break; case T_JSON::J_STRING: { const std::string result = value.getstring(); char * returnValue = (char *)sqlite3_malloc((int)result.length()); memcpy(returnValue, result.c_str(), result.length()); sqlite3_result_text(context, result.c_str(), (int)result.length(), sqlite3_free); } break; case T_JSON::J_BOOLEAN: sqlite3_result_int(context, value.getboolean() ? 1 : 0); break; case T_JSON::J_OBJECT: case T_JSON::J_ARRAY: { const std::string result = value.toString(); char * returnValue = (char *)sqlite3_malloc((int)result.length()); memcpy(returnValue, result.c_str(), result.length()); sqlite3_result_blob(context, result.c_str(), (int)result.length(), sqlite3_free); } break; case T_JSON::J_NULL: default: sqlite3_result_null(context); break; } } namespace sequencelogic { //#pragma mark - StorageNodeDBAccess StorageNodeDBAccess::StorageNodeDBAccess() :EyeDB(), _tableInitialized(false) { } //NOTE: This method will also be responsible for migrating database changes as necessary. bool StorageNodeDBAccess::initDB(const std::string & dbFilePath) { std::lock_guard lock(_protectInit); _tableInitialized = false; if (!EyeDB::open(dbFilePath.c_str())) { return false; } static std::string const tableQuery = "create table if not exists 'storageguard' ( \ 'urn' TEXT, \ 'mount' TEXT, \ 'signature' TEXT, \ 'path' TEXT, \ 'parent' TEXT, \ 'mime_type' TEXT, \ 'nodeJSON' BLOB, \ 'metadata_utime' INTEGER, \ 'metadata_status' INTEGER, \ 'permission_status' INTEGER, \ UNIQUE (urn) on conflict replace)"; //determine existing table matches table hash std::string digest(DigestMessage("sha1", (const unsigned char *)tableQuery.c_str(), tableQuery.length())); if (getTableHash() != digest) { IONUWARN("StorageNodeDBAccess::initDB Drop \'storageguard\' table based on digest change.\n"); executeQuery("drop table if exists 'storageguard'"); if (executeQuery(tableQuery)) { setTableHash(digest); } } _tableInitialized = getTableHash() == digest; return _tableInitialized; } StorageNodeVector StorageNodeDBAccess::getNodes(const StorageNode & node) { StorageNodeVector result; // try a partial match static std::string const query = "select * from storageguard where \ mount like ? \ and signature like ? \ and path like ? \ "; //get query parameter or convert to wildcard if nil std::string mount = makeSQLWildcardIfNecessary(node.getMount()); std::string signature = makeSQLWildcardIfNecessary(node.getSignature()); std::string path = makeSQLWildcardIfNecessary(node.getPath()); if (path != "%") { path = replaceFilesystemWildcard(path); } uint16_t bindPos = 0; statement statement(*this, query); if (statement.isPrepared() && statement.bind(++bindPos, mount) && statement.bind(++bindPos, signature) && statement.bind(++bindPos, path)) { MAP_VECTOR nodes = fetchResults(statement); for (STRING_STRING_MAP & node : nodes) { result.push_back(StorageNodePtr(new StorageNode(node["nodeJSON"]))); } } return result; } StorageNodeVector StorageNodeDBAccess::getNodesInDirectory(const StorageNode & node) { //Get nodes for parent static std::string const query = "select * from storageguard where \ mount is ? \ and signature is ? \ and parent is ? \ "; StorageNodeVector result; std::string mount = makeSQLWildcardIfNecessary(node.getMount()); std::string signature = makeSQLWildcardIfNecessary(node.getSignature()); std::string parent = node.getURN(); uint16_t bindPos = 0; statement statement(*this, query); if (statement.isPrepared() && statement.bind(++bindPos, mount) && statement.bind(++bindPos, signature) && statement.bind(++bindPos, parent)) { MAP_VECTOR nodes = fetchResults(statement); for (STRING_STRING_MAP & node : nodes) { result.push_back(StorageNodePtr(new StorageNode(node["nodeJSON"]))); } } return result; } StorageNodeVector StorageNodeDBAccess::getNodesByPath(const StorageNode & node) { StorageNodeVector result; std::string path = node.getPath(); if (!path.empty()) { static std::string const query = "select * from storageguard where path like ?"; path = makeSQLWildcardIfNecessary(node.getPath()); if (path != "%") { path = replaceFilesystemWildcard(path); } statement statement(*this, query); if (statement.isPrepared() && statement.bind(1, path)) { MAP_VECTOR nodes = fetchResults(statement); //this should only be a single result but we'll do this just in case // the upstream consumer might want to interrogate the result to figure out // what is going on. for (STRING_STRING_MAP & node : nodes) { result.push_back(StorageNodePtr(new StorageNode(node["nodeJSON"]))); } } } return result; } StorageNodeVector StorageNodeDBAccess::getNodesByPathForMountAndSignature(const StorageNode & node) { StorageNodeVector result; static std::string const query = "select * from storageguard where mount like ? and signature like ? and path like ?"; std::string mount = makeSQLWildcardIfNecessary(node.getMount()); std::string signature = makeSQLWildcardIfNecessary(node.getSignature()); std::string path = makeSQLWildcardIfNecessary(node.getPath()); if (path != "%") { path = replaceFilesystemWildcard(path); } uint16_t bindPos = 0; statement statement(*this, query); if (statement.isPrepared() && statement.bind(++bindPos, mount) && statement.bind(++bindPos, signature) && statement.bind(++bindPos, path)) { MAP_VECTOR nodes = fetchResults(statement); //this should only be a single result but we'll do this just in case // the upstream consumer might want to interrogate the result to figure out // what is going on. for (STRING_STRING_MAP & node : nodes) { result.push_back(StorageNodePtr(new StorageNode(node["nodeJSON"]))); } } return result; } StorageNodeVector StorageNodeDBAccess::getNodesByStatus(const StorageNode & node) { StorageNodeVector result; // Finding deleted nodes is a common use case; deleted files can not be discovered // using the local file system static std::string const query = "select * from storageguard where \ mount like ? \ and metadata_status like ? \ and signature like ? \ and path like ? \ "; //get query parameter or convert to wildcard if nil std::string mount = makeSQLWildcardIfNecessary(node.getMount()); int metadata_status = (int) node.getStatus(); std::string signature = makeSQLWildcardIfNecessary(node.getSignature()); std::string path = makeSQLWildcardIfNecessary(node.getPath()); if (path != "%") { path = replaceFilesystemWildcard(path); } uint16_t bindPos = 0; statement statement(*this, query); if (statement.isPrepared() && statement.bind(++bindPos, mount) && statement.bind(++bindPos, metadata_status) && statement.bind(++bindPos, signature) && statement.bind(++bindPos, path)) { MAP_VECTOR nodes = fetchResults(statement); for (STRING_STRING_MAP & node : nodes) { result.push_back(StorageNodePtr(new StorageNode(node["nodeJSON"]))); } } return result; } StorageNodeVector StorageNodeDBAccess::getNodesByUniformResourceName(const StorageNode & node) { StorageNodeVector result; std::string urn = node.getURN(); if (!urn.empty()) { static std::string const query = "select * from storageguard where urn is ?"; statement statement(*this, query); if (statement.isPrepared() && statement.bind(1, urn)) { MAP_VECTOR nodes = fetchResults(statement); //this should only be a single result but we'll do this just in case // the upstream consumer might want to interrogate the result to figure out // what is going on. for (STRING_STRING_MAP & node : nodes) { result.push_back(StorageNodePtr(new StorageNode(node["nodeJSON"]))); } } } return result; } bool StorageNodeDBAccess::replaceNode(const StorageNode & node) { //prepare statement static std::string const query = "replace into storageguard (urn, mount, signature, path, parent, mime_type, nodeJSON, metadata_utime, metadata_status, permission_status) values (?,?,?,?,?,?,?,?,?,?)"; statement statement(*this, query); std::string urn = node.getURN(); std::string mount = node.getMount(); std::string signature = node.getSignature(); std::string path = node.getPath(); std::string parent = node.getParent(); std::string mimeType = node.getMimeType(); int status = node.getStatus(); int permStatus = node.getInt(SGDB_PERM_STATUS); sqlite3_int64 utime = node.getutime().GetTimeMs(); std::string nodeJSON = node.toString(); if (urn.length() == 0) { std::cout << "Bad document URN in database insert." << std::endl; } uint16_t bindPos = 0; if (statement.isPrepared() && statement.bind(++bindPos, urn) && statement.bind(++bindPos, mount) && statement.bind(++bindPos, signature) && statement.bind(++bindPos, path) && statement.bind(++bindPos, parent) && statement.bind(++bindPos, mimeType) && statement.bind(++bindPos, (const void*)nodeJSON.data(), (int)nodeJSON.length()) && statement.bind(++bindPos, utime) && statement.bind(++bindPos, status) && statement.bind(++bindPos, permStatus)) { int result = statement.step(); if (result != SQLITE_DONE) { IONUDEBUG("StorageNodeDBAccess::putNode() - sqlite3_step failed %d", result); return false; } } else { return false; } return true; } bool StorageNodeDBAccess::putNode(const StorageNode & node) { transaction transaction(*this); //add children as necessary StorageNodeVector children = node.getChildren(); if (children.size() > 0) { for (StorageNodePtr & child : children) { if (!putNode(*child)) { return false; } } } //insert node else if (!replaceNode(node)) { return false; } transaction.commit(); return true; } bool StorageNodeDBAccess::deleteNodes(const StorageNode & node) { transaction transaction(*this); //do children first StorageNodeVector children = node.getChildren(); for (StorageNodePtr & child : children) { if (!deleteNodes(*child)) { return false; } } //delete the node, prefer urn if specified std::string urn = node.getURN(); if (!urn.empty() && CloudGuardURN(urn).getType() == CloudGuardURN::PMO_DEVICE_DOCUMENT) { static std::string const query = "delete from storageguard where urn is ?"; statement statement(*this, query); if (statement.isPrepared() && statement.bind(1, urn)) { int result = statement.step(); if (result != SQLITE_DONE) { IONUDEBUG("StorageNodeDBAccess::deleteNodes() - sqlite3_step failed %d", result); return false; } } else { return false; } } else { static std::string const query = "delete from storageguard where \ mount like ? \ and signature like ? \ and path like ? \ "; //get query parameter or convert to wildcard if nil std::string mount = makeSQLWildcardIfNecessary(node.getMount()); std::string signature = makeSQLWildcardIfNecessary(node.getSignature()); std::string path = makeSQLWildcardIfNecessary(node.getPath()); if (path != "%") { path = replaceFilesystemWildcard(path); } uint16_t bindPos = 0; statement statement(*this, query); if (statement.isPrepared() && statement.bind(++bindPos, mount) && statement.bind(++bindPos, signature) && statement.bind(++bindPos, path)) { int result = statement.step(); if (result != SQLITE_DONE) { IONUDEBUG("StorageNodeDBAccess::deleteNodes() - sqlite3_step failed %d", result); return false; } } else { return false; } } transaction.commit(); return true; } StorageNodePtr StorageNodeDBAccess::listChildren(const StorageNode & parent, const StorageNode & startingNode, int32_t limit, const std::string & sort, bool sortAscending) { std::string parentURN = parent.getURN(); std::string startingNodeURN = startingNode.getURN(); if (parentURN.empty() || startingNodeURN.empty()) { return nullptr; } std::string query = "select * from storageguard where parent is ?"; sequencelogic::StorageNodeStatus status = parent.getStatus(); bool haveStatus = false; if (status != sequencelogic::StorageNodeStatus::unknown) { query += " and metadata_status is ?"; haveStatus = true; } int permStatus = parent.getInt(SGDB_PERM_STATUS); bool havePermStatus = false; if (permStatus != 0) { query += " and permission_status is ?"; havePermStatus = true; } //special case utime sort //this may need to be extended to other columns bool haveOffset = false; bool haveLimit = false; if (sort == "metadata_utime") { query += " and metadata_utime"; query += sortAscending ? " >" : " <"; query += " (select metadata_utime from storageguard where urn is ?) order by metadata_utime"; query += sortAscending ? " ASC" : " DESC"; haveOffset = true; //Do limit in the query in the simple case if (limit > 0) { haveLimit = true; query += " LIMIT "; std::stringstream limitStr; limitStr << limit; query += limitStr.str(); } } //prepare and execute statement int bindPos = 0; statement statement(*this, query); if (statement.bind(++bindPos, parentURN)) { //bind status if necessary if (haveStatus) { statement.bind(++bindPos, status); } if (havePermStatus) { statement.bind(++bindPos, havePermStatus); } //bind offset if necessary if (haveOffset) { statement.bind(++bindPos, startingNodeURN); } MAP_VECTOR nodes = fetchResults(statement); //sort if (sort.length() > 0 && sort != "metadata_utime") { MapVectorSortFunctor sorter(sort, sortAscending); std::sort(nodes.begin(), nodes.end(), sorter); } //offset if (!haveOffset) { //ugly... have to walk array to find our starting point MapVectorFindFunctor finder("urn", startingNodeURN); MAP_VECTOR::iterator node_offset = std::find_if(nodes.begin(), nodes.end(), finder); if (node_offset != nodes.end()) { nodes.erase(nodes.begin(), node_offset); } } //limit if (!haveLimit && nodes.size() > (MAP_VECTOR::size_type)abs(limit)) { if (limit > 0) { nodes.erase(nodes.begin()+abs(limit), nodes.end()); } else if (limit < 0) { nodes.erase(nodes.begin(), nodes.end()-abs(limit)); } } //create result... StorageNodePtr result = StorageNodePtr(new StorageNode(parent)); StorageNodeVector children; for (STRING_STRING_MAP & node : nodes) { children.push_back(StorageNodePtr(new StorageNode(node["nodeJSON"]))); } result->setChildren(children); return result; } return nullptr; } StorageNodePtr StorageNodeDBAccess::listChildren(const StorageNode & parent, int32_t offset, int32_t limit, const std::string & sort, bool sortAscending) { std::string parentURN = parent.getURN(); if (parentURN.empty()) { return nullptr; } std::string query = "select * from storageguard where parent is ?"; sequencelogic::StorageNodeStatus status = parent.getStatus(); bool haveStatus = false; if (status != sequencelogic::StorageNodeStatus::unknown) { query += " and metadata_status is ?"; haveStatus = true; } int permStatus = parent.getInt(SGDB_PERM_STATUS); bool havePermStatus = false; if (permStatus != 0) { query += " and permission_status is ?"; havePermStatus = true; } //special case utime sort //this may need to be extended to other columns bool haveOffset = false; bool haveLimit = false; if (sort == "metadata_utime") { query += " order by metadata_utime"; query += sortAscending ? " ASC" : " DESC"; //Do the offset and limit in the query in the simple case // ...the other cases make my head hurt if (limit > 0) { haveLimit = true; query += " LIMIT "; std::stringstream limitStr; limitStr << limit; query += limitStr.str(); } if (offset > 0) { haveOffset = true; query += " OFFSET "; std::stringstream offsetStr; offsetStr << offset; query += offsetStr.str(); } } //prepare and execute statement int bindPos = 0; statement statement(*this, query); if (statement.bind(++bindPos, parentURN)) { //bind status if necessary if (haveStatus) { statement.bind(++bindPos, status); } if (havePermStatus) { statement.bind(++bindPos, permStatus); } MAP_VECTOR nodes = fetchResults(statement); //sort if (sort.length() > 0 && sort != "metadata_utime") { MapVectorSortFunctor sorter(sort, sortAscending); std::sort(nodes.begin(), nodes.end(), sorter); } //offset if (!haveOffset) { if (nodes.size() >= static_cast(abs(offset))) { if (offset > 0) { nodes.erase(nodes.begin(), nodes.begin()+abs(offset)); } else if (offset < 0) { nodes.erase(nodes.begin(), nodes.end()-abs(offset)); } } else if (abs(offset) > 0) { nodes.erase(nodes.begin(), nodes.end()); } } //limit if (!haveLimit && nodes.size() > (MAP_VECTOR::size_type)abs(limit)) { if (limit > 0) { nodes.erase(nodes.begin()+abs(limit), nodes.end()); } else if (limit < 0) { nodes.erase(nodes.begin(), nodes.end()-abs(limit)); } } //create result... StorageNodePtr result = StorageNodePtr(new StorageNode(parent)); StorageNodeVector children; for (STRING_STRING_MAP & node : nodes) { children.push_back(StorageNodePtr(new StorageNode(node["nodeJSON"]))); } result->setChildren(children); return result; } return nullptr; } int32_t StorageNodeDBAccess::countChildren(const StorageNode & parent) { std::string parentURN = parent.getURN(); if (parentURN.empty()) { return 0; } std::string query = "select count(*) as rows from storageguard where parent is ? "; sequencelogic::StorageNodeStatus status = parent.getStatus(); bool haveStatus = false; if (status != sequencelogic::StorageNodeStatus::unknown) { query += " and metadata_status is ?"; haveStatus = true; } int permStatus = parent.getInt(SGDB_PERM_STATUS); bool havePermStatus = false; if (permStatus != 0) { query += " and permission_status is ?"; havePermStatus = true; } statement statement(*this, query); int bindPos = 0; if (statement.bind(++bindPos, parentURN)) { //bind status if necessary if (haveStatus) { statement.bind(++bindPos, status); } if (havePermStatus) { statement.bind(++bindPos, permStatus); } MAP_VECTOR nodes = fetchResults(statement); std::string resultString = nodes[0]["rows"]; return (int32_t)std::atol(resultString.c_str()); } return 0; } int32_t StorageNodeDBAccess::rowCount() { int32_t result = -1; static std::string const query = "select count(*) as rows from storageguard"; statement queryStatement(*this, query); if (queryStatement.isPrepared()) { MAP_VECTOR nodes = fetchResults(queryStatement); std::string resultString = nodes[0]["rows"]; result = (int32_t)std::atol(resultString.c_str()); } return result; } //#pragma mark - Utilities std::string StorageNodeDBAccess::getTableHash() { std::string tableHash; static std::string const query = "select hash from version"; statement queryStatement(*this, query); if (queryStatement.isPrepared()) { MAP_VECTOR result = fetchResults(queryStatement); if (result.size() > 0) { tableHash = result[0]["hash"]; } } return tableHash; } bool StorageNodeDBAccess::setTableHash(const std::string & tableHash) { static std::string const versionTable = "create table if not exists 'version' (hash)"; if (executeQuery(versionTable)) { static std::string const clearTableQuery = "delete from version"; executeQuery(clearTableQuery); static std::string const updateTableQuery = "insert into version values (?)"; statement updateStatement(*this, updateTableQuery); updateStatement.bind(1, tableHash); int result = updateStatement.step(); return result == SQLITE_DONE; } return false; } //might promote this to the EyeDB class, could be generally useful std::string StorageNodeDBAccess::makeSQLWildcardIfNecessary(const std::string & string) { if (string.length() == 0) { return "%"; } return string; } std::string StorageNodeDBAccess::replaceFilesystemWildcard(const std::string & string) { std::string result(string); // escape existing wildcard characters presuming they are // expected parts of the filesystem query ReplaceStringInSitu(result, "%", "\\%"); ReplaceStringInSitu(result, "?", "\\?"); #ifdef WIN32 // replace windows directory wildcard // do this before * substitution below to avoid conflicts ReplaceStringInSitu(result, "**", "%"); #endif // replace all multi char matches std::replace(result.begin(), result.end(), '*', '%'); // replace all single char matches std::replace(result.begin(), result.end(), '?', '_'); // character set notation should be the same return result; } //#pragma mark - StorageNodeDBAccessJava StorageNodeDBAccessJava::StorageNodeDBAccessJava() { } StorageNodeDBAccessJava::~StorageNodeDBAccessJava() { } bool StorageNodeDBAccessJava::initDB(const std::string & dbFilePath) { return dbAccess.initDB(dbFilePath); } StorageNodeDBAccessJava::STRING_VECTOR_PTR StorageNodeDBAccessJava::getNodes(const std::string & nodeJSON) { StorageNode node(nodeJSON); StorageNodeVector nodes = dbAccess.getNodes(node); STRING_VECTOR_PTR result(new STRING_VECTOR); for (const StorageNode resultNode : nodes) { result->push_back(resultNode.toString()); } return result; } std::string StorageNodeDBAccessJava::listChildren(const std::string & parentJSON, const std::string & startingNodeJSON, int32_t limit, const std::string & sort, bool sortAscending) { StorageNode node(parentJSON); StorageNode startingNode(startingNodeJSON); StorageNodePtr result = dbAccess.listChildren(node, startingNode, limit, sort, sortAscending); if (result != nullptr) { return result->toString(); } else { return ""; } } std::string StorageNodeDBAccessJava::listChildren(const std::string & parentJSON, int32_t offset, int32_t limit, const std::string & sort, bool sortAscending) { StorageNode node(parentJSON); StorageNodePtr result = dbAccess.listChildren(node, offset, limit, sort, sortAscending); if (result != nullptr) { return result->toString(); } else { return ""; } } bool StorageNodeDBAccessJava::putNode(const std::string & nodeJSON) { StorageNode node(nodeJSON); return dbAccess.putNode(node); } int StorageNodeDBAccessJava::rowCount() { return dbAccess.rowCount(); } int StorageNodeDBAccessJava::countChildren(const std::string & parent) { StorageNode node(parent); return dbAccess.countChildren(node); } StorageNodeDBAccessJava::STRING_VECTOR_PTR StorageNodeDBAccessJava::getNodesInDirectory(const std::string & nodeJSON) { StorageNode node(nodeJSON); StorageNodeVector nodeVec = dbAccess.getNodesInDirectory(node); STRING_VECTOR_PTR result(new STRING_VECTOR); for (const StorageNode resultNode : nodeVec) result->push_back(resultNode.toString()); return result; } StorageNodeDBAccessJava::STRING_VECTOR_PTR StorageNodeDBAccessJava::getNodesByPath(const std::string & nodeJSON) { StorageNode node(nodeJSON); StorageNodeVector nodeVec = dbAccess.getNodesByPath(node); STRING_VECTOR_PTR result(new STRING_VECTOR); for (const StorageNode resultNode : nodeVec) result->push_back(resultNode.toString()); return result; } StorageNodeDBAccessJava::STRING_VECTOR_PTR StorageNodeDBAccessJava::getNodesByPathForMountAndSignature(const std::string & nodeJSON) { StorageNode node(nodeJSON); StorageNodeVector nodeVec = dbAccess.getNodesByPathForMountAndSignature(node); STRING_VECTOR_PTR result(new STRING_VECTOR); for (const StorageNode resultNode : nodeVec) result->push_back(resultNode.toString()); return result; } StorageNodeDBAccessJava::STRING_VECTOR_PTR StorageNodeDBAccessJava::getNodesByStatus(const std::string & nodeJSON) { StorageNode node(nodeJSON); StorageNodeVector nodeVec = dbAccess.getNodesByStatus(node); STRING_VECTOR_PTR result(new STRING_VECTOR); for (const StorageNode resultNode : nodeVec) result->push_back(resultNode.toString()); return result; } StorageNodeDBAccessJava::STRING_VECTOR_PTR StorageNodeDBAccessJava::getNodesByUniformResourceName(const std::string & nodeJSON) { StorageNode node(nodeJSON); StorageNodeVector nodeVec = dbAccess.getNodesByUniformResourceName(node); STRING_VECTOR_PTR result(new STRING_VECTOR); for (const StorageNode resultNode : nodeVec) result->push_back(resultNode.toString()); return result; } bool StorageNodeDBAccessJava::deleteNodes(const std::string & nodeJSON) { StorageNode node(nodeJSON); return dbAccess.deleteNodes(node); } }