diff --git a/Documentation/InstallationManual/Installing.md b/Documentation/InstallationManual/Installing.md index 2ecae8cbf3..60636514ad 100644 --- a/Documentation/InstallationManual/Installing.md +++ b/Documentation/InstallationManual/Installing.md @@ -36,7 +36,17 @@ Please use the @EXTREF_S{https://github.com/mgiken/portage-overlay/tree/master/dev-db/ArangoDB,portage} provided by @@mgiken. -### Linux-Mint {#InstallingDebian} +### Debian sid {#InstallingDebian} + +To use ArangoDB on Debian sid (the development version of Debian), a different version +of ICU is required. User basir provided the following instructions for getting ArangoDB 2.0.7 +to work on an x86_64: + +@EXTREF_S{https://github.com/triAGENS/ArangoDB/issues/865,link to Github issue} + +Other versions of ArangoDB or other architectures should work similarly. + +### Linux-Mint {#InstallingLinuxMint} Download and import GPG-PublicKey: diff --git a/Documentation/InstallationManual/InstallingTOC.md b/Documentation/InstallationManual/InstallingTOC.md index 568fd16578..ba974f042e 100644 --- a/Documentation/InstallationManual/InstallingTOC.md +++ b/Documentation/InstallationManual/InstallingTOC.md @@ -5,6 +5,7 @@ TOC {#InstallingTOC} - @ref InstallingLinux - @ref InstallingLinuxPackageManager - @ref InstallingDebian + - @ref InstallingLinuxMint - @ref InstallingMacOSX - @ref InstallingMacOSXHomebrew - @ref InstallingMacOSXAppStore diff --git a/arangod/Ahuacatl/ahuacatl-functions.c b/arangod/Ahuacatl/ahuacatl-functions.c index c2e18c5d4b..fd7cd12b86 100644 --- a/arangod/Ahuacatl/ahuacatl-functions.c +++ b/arangod/Ahuacatl/ahuacatl-functions.c @@ -705,11 +705,15 @@ TRI_associative_pointer_t* TRI_CreateFunctionsAql (void) { REGISTER_FUNCTION("PATHS", "GRAPH_PATHS", false, false, "c,h|s,b", &OptimisePaths); REGISTER_FUNCTION("GRAPH_PATHS", "GENERAL_GRAPH_PATHS", false, false, "s|s,b,n,n", &OptimisePaths); REGISTER_FUNCTION("SHORTEST_PATH", "GRAPH_SHORTEST_PATH", false, false, "h,h,s,s,s|a", NULL); + REGISTER_FUNCTION("GRAPH_SHORTEST_PATH", "GENERAL_GRAPH_SHORTEST_PATH", false, false, "s,s,s,s|a", NULL); REGISTER_FUNCTION("TRAVERSAL", "GRAPH_TRAVERSAL", false, false, "h,h,s,s|a", NULL); + REGISTER_FUNCTION("GRAPH_TRAVERSAL", "GENERAL_GRAPH_TRAVERSAL", false, false, "s,s,s|a", NULL); REGISTER_FUNCTION("TRAVERSAL_TREE", "GRAPH_TRAVERSAL_TREE", false, false, "h,h,s,s,s|a", NULL); + REGISTER_FUNCTION("GRAPH_TRAVERSAL_TREE", "GENERAL_GRAPH_TRAVERSAL_TREE", false, false, "s,s,s,s|a", NULL); REGISTER_FUNCTION("EDGES", "GRAPH_EDGES", false, false, "h,s,s|l", NULL); REGISTER_FUNCTION("GRAPH_EDGES", "GENERAL_GRAPH_EDGES", false, false, "s,s,s|lza,ls", NULL); REGISTER_FUNCTION("NEIGHBORS", "GRAPH_NEIGHBORS", false, false, "h,h,s,s|l", NULL); + REGISTER_FUNCTION("GRAPH_NEIGHBORS", "GENERAL_GRAPH_NEIGHBORS", false, false, "s,s,s|l", NULL); // date functions REGISTER_FUNCTION("DATE_NOW", "DATE_NOW", false, false, "", NULL); // NOW is non-deterministic diff --git a/arangod/Utils/Transaction.h b/arangod/Utils/Transaction.h index 5d1c78578a..fee09f2ff8 100644 --- a/arangod/Utils/Transaction.h +++ b/arangod/Utils/Transaction.h @@ -35,6 +35,7 @@ #include "VocBase/barrier.h" #include "VocBase/collection.h" #include "VocBase/document-collection.h" +#include "VocBase/edge-collection.h" #include "VocBase/transaction.h" #include "VocBase/update-policy.h" #include "VocBase/vocbase.h" @@ -882,31 +883,16 @@ namespace triagens { TRI_primary_collection_t* primary = primaryCollection(trxCollection); bool lock = ! isLocked(trxCollection, TRI_TRANSACTION_WRITE); - int res; - - if (markerType == TRI_DOC_MARKER_KEY_DOCUMENT) { - // document - res = primary->insertDocument(trxCollection, - key, - rid, - mptr, - shaped, - lock, - forceSync, - false); - } - else { - // edge - res = primary->insertEdge(trxCollection, - key, - rid, - mptr, - shaped, - data, - lock, - forceSync, - false); - } + int res = primary->insertDocument(trxCollection, + markerType, + key, + rid, + mptr, + static_cast(data), + shaped, + lock, + forceSync, + false); return res; } diff --git a/arangod/VocBase/document-collection.cpp b/arangod/VocBase/document-collection.cpp index 52fd09b0b3..ae2c868f11 100644 --- a/arangod/VocBase/document-collection.cpp +++ b/arangod/VocBase/document-collection.cpp @@ -1579,14 +1579,17 @@ static int InsertIndexes (TRI_transaction_collection_t* trxCollection, //////////////////////////////////////////////////////////////////////////////// static int InsertDocumentShapedJson (TRI_transaction_collection_t* trxCollection, + TRI_df_marker_type_e markerType, TRI_voc_key_t key, TRI_voc_rid_t rid, TRI_doc_mptr_t* mptr, + TRI_document_edge_t const* edge, TRI_shaped_json_t const* shaped, bool lock, bool forceSync, bool isRestore) { + // TODO: isRestore is not used yet! TRI_voc_tick_t tick; if (rid == 0) { @@ -1622,28 +1625,59 @@ static int InsertDocumentShapedJson (TRI_transaction_collection_t* trxCollection keyString = key; } - // TODO: isRestore is not used yet! + // construct a legend for the shaped json + triagens::basics::JsonLegend legend(primary->_shaper); + int res = legend.addShape(shaped->_sid, &shaped->_data); - triagens::wal::DocumentMarker marker(primary->base._vocbase->_id, - primary->base._info._cid, - rid, - TRI_GetMarkerIdTransaction(trxCollection->_transaction), - keyString, - shaped); + if (res != TRI_ERROR_NO_ERROR) { + return res; + } + + + triagens::wal::SlotInfo slotInfo; + + if (markerType == TRI_DOC_MARKER_KEY_DOCUMENT) { + // document + assert(edge == nullptr); + + triagens::wal::DocumentMarker marker(primary->base._vocbase->_id, + primary->base._info._cid, + rid, + TRI_GetMarkerIdTransaction(trxCollection->_transaction), + keyString, + legend, + shaped); + + slotInfo = triagens::wal::LogfileManager::instance()->writeMarker(marker, forceSync); + } + else { + // edge + assert(edge != nullptr); + + triagens::wal::EdgeMarker marker(primary->base._vocbase->_id, + primary->base._info._cid, + rid, + TRI_GetMarkerIdTransaction(trxCollection->_transaction), + keyString, + edge, + legend, + shaped); + + slotInfo = triagens::wal::LogfileManager::instance()->writeMarker(marker, forceSync); + } - // insert into WAL first - triagens::wal::SlotInfo slotInfo = triagens::wal::LogfileManager::instance()->writeMarker(marker, forceSync); if (slotInfo.errorCode != TRI_ERROR_NO_ERROR) { // some error occurred return slotInfo.errorCode; } + // now insert into indexes triagens::arango::CollectionWriteLocker collectionLocker(primary, lock); TRI_document_collection_t* document = (TRI_document_collection_t*) primary; - TRI_doc_mptr_t* header = document->_headers->request(document->_headers, marker.size); + TRI_doc_mptr_t* header = document->_headers->request(document->_headers, slotInfo.size); if (header == NULL) { return TRI_ERROR_OUT_OF_MEMORY; @@ -1657,7 +1691,7 @@ static int InsertDocumentShapedJson (TRI_transaction_collection_t* trxCollection header->_data = (void*) m; header->_key = (char*) m + m->_offsetKey; - int res = InsertIndexes(trxCollection, header, forceSync); + res = InsertIndexes(trxCollection, header, forceSync); // eager unlock collectionLocker.unlock(); @@ -3098,7 +3132,6 @@ static bool InitDocumentCollection (TRI_document_collection_t* document, // crud methods document->base.insert = InsertShapedJson; document->base.insertDocument = InsertDocumentShapedJson; -// document->base.insertEdge = InsertEdgeShapedJson; // TODO document->base.read = ReadShapedJson; document->base.update = UpdateShapedJson; document->base.remove = RemoveShapedJson; diff --git a/arangod/VocBase/edge-collection.h b/arangod/VocBase/edge-collection.h index 60a42f070d..bc0bb2c244 100644 --- a/arangod/VocBase/edge-collection.h +++ b/arangod/VocBase/edge-collection.h @@ -38,56 +38,10 @@ extern "C" { // --SECTION-- EDGE COLLECTION // ----------------------------------------------------------------------------- -// ----------------------------------------------------------------------------- -// --SECTION-- private defines -// ----------------------------------------------------------------------------- - -//////////////////////////////////////////////////////////////////////////////// -/// @addtogroup VocBase -/// @{ -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -/// @brief special bit that can be set within edge flags -/// this bit will be set if the edge is an in-marker -//////////////////////////////////////////////////////////////////////////////// - -#define TRI_EDGE_BIT_DIRECTION_IN ((TRI_edge_flags_t) (1 << 1)) - -//////////////////////////////////////////////////////////////////////////////// -/// @brief special bit that can be set within edge flags -/// this bit will be set if the edge is an out-marker -//////////////////////////////////////////////////////////////////////////////// - -#define TRI_EDGE_BIT_DIRECTION_OUT ((TRI_edge_flags_t) (1 << 2)) - -//////////////////////////////////////////////////////////////////////////////// -/// @brief special bit that can be set within edge flags -/// this bit will be set if the edge is self-reflexive (i.e. _from and _to are -/// the same) -//////////////////////////////////////////////////////////////////////////////// - -#define TRI_EDGE_BIT_REFLEXIVE ((TRI_edge_flags_t) (1 << 3)) - -//////////////////////////////////////////////////////////////////////////////// -/// @brief combination of the two directional bits -//////////////////////////////////////////////////////////////////////////////// - -#define TRI_EDGE_BITS_DIRECTION (TRI_EDGE_BIT_DIRECTION_IN | TRI_EDGE_BIT_DIRECTION_OUT) - -//////////////////////////////////////////////////////////////////////////////// -/// @} -//////////////////////////////////////////////////////////////////////////////// - // ----------------------------------------------------------------------------- // --SECTION-- public types // ----------------------------------------------------------------------------- -//////////////////////////////////////////////////////////////////////////////// -/// @addtogroup VocBase -/// @{ -//////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// /// @brief typedef for edge flags /// the type is an integer which indicates the edge direction (IN/OUT) @@ -131,10 +85,6 @@ typedef struct TRI_edge_header_s { } TRI_edge_header_t; -//////////////////////////////////////////////////////////////////////////////// -/// @} -//////////////////////////////////////////////////////////////////////////////// - // ----------------------------------------------------------------------------- // --SECTION-- EDGES INDEX // ----------------------------------------------------------------------------- diff --git a/arangod/VocBase/primary-collection.h b/arangod/VocBase/primary-collection.h index d6971f1d71..126689ebde 100644 --- a/arangod/VocBase/primary-collection.h +++ b/arangod/VocBase/primary-collection.h @@ -50,6 +50,7 @@ struct TRI_cap_constraint_s; struct TRI_doc_deletion_key_marker_s; struct TRI_doc_document_key_marker_s; struct TRI_doc_update_policy_s; +struct TRI_document_edge_s; struct TRI_key_generator_s; struct TRI_primary_collection_s; @@ -330,8 +331,7 @@ typedef struct TRI_primary_collection_s { int (*notifyTransaction) (struct TRI_primary_collection_s*, TRI_transaction_status_e); int (*insert) (struct TRI_transaction_collection_s*, const TRI_voc_key_t, TRI_voc_rid_t, TRI_doc_mptr_t*, TRI_df_marker_type_e, TRI_shaped_json_t const*, void const*, const bool, const bool, const bool); - int (*insertDocument) (struct TRI_transaction_collection_s*, TRI_voc_key_t, TRI_voc_rid_t, TRI_doc_mptr_t*, TRI_shaped_json_t const*, bool, bool, bool); - int (*insertEdge) (struct TRI_transaction_collection_s*, TRI_voc_key_t, TRI_voc_rid_t, TRI_doc_mptr_t*, TRI_shaped_json_t const*, void const*, bool, bool, bool); + int (*insertDocument) (struct TRI_transaction_collection_s*, TRI_df_marker_type_e, TRI_voc_key_t, TRI_voc_rid_t, TRI_doc_mptr_t*, struct TRI_document_edge_s const*, TRI_shaped_json_t const*, bool, bool, bool); int (*read) (struct TRI_transaction_collection_s*, const TRI_voc_key_t, TRI_doc_mptr_t*, const bool); diff --git a/arangod/Wal/LogfileManager.cpp b/arangod/Wal/LogfileManager.cpp index 3a5f5dad2c..1ac4c8fa8c 100644 --- a/arangod/Wal/LogfileManager.cpp +++ b/arangod/Wal/LogfileManager.cpp @@ -29,6 +29,7 @@ #include "BasicsC/hashes.h" #include "BasicsC/json.h" #include "BasicsC/logging.h" +#include "BasicsC/tri-strings.h" #include "Basics/Exceptions.h" #include "Basics/FileUtils.h" #include "Basics/JsonHelper.h" @@ -447,6 +448,7 @@ SlotInfo LogfileManager::allocateAndWrite (void* src, slotInfo.slot->fill(src, size); +std::cout << TRI_PrintableString((char const*) src, size) << "\n"; finalise(slotInfo, waitForSync); return slotInfo; } diff --git a/arangod/Wal/Marker.h b/arangod/Wal/Marker.h index c9b4f00cdb..db69d034d3 100644 --- a/arangod/Wal/Marker.h +++ b/arangod/Wal/Marker.h @@ -29,8 +29,10 @@ #define TRIAGENS_WAL_MARKER_H 1 #include "Basics/Common.h" +#include "ShapedJson/Legends.h" #include "ShapedJson/shaped-json.h" #include "VocBase/datafile.h" +#include "VocBase/edge-collection.h" namespace triagens { namespace wal { @@ -164,6 +166,19 @@ namespace triagens { return base() + sizeof(TRI_df_marker_t); } + void storeSizedString (size_t offset, + char const* value, + size_t length) { + // init key buffer + char* p = static_cast(base()) + offset; + memset(p, '\0', (1 + ((length + 1) / 8)) * 8); + + // store length of key + *p = (uint8_t) length; + // store actual key + memcpy(p + 1, value, length); + } + char* buffer; uint32_t const size; }; @@ -225,9 +240,10 @@ namespace triagens { TRI_voc_rid_t revisionId, TRI_voc_tid_t transactionId, std::string const& key, + triagens::basics::JsonLegend& legend, TRI_shaped_json_t const* shapedJson) : Marker(TRI_WAL_MARKER_DOCUMENT, - sizeof(document_marker_t) + alignedSize(key.size() + 2) + shapedJson->_data.length) { + sizeof(document_marker_t) + alignedSize(key.size() + 2) + legend.getSize() + shapedJson->_data.length) { document_marker_t* m = reinterpret_cast(base()); m->_databaseId = databaseId; @@ -237,24 +253,16 @@ namespace triagens { m->_shape = shapedJson->_sid; m->_offsetKey = sizeof(document_marker_t); // start position of key m->_offsetLegend = m->_offsetKey + alignedSize(key.size() + 2); - m->_offsetJson = m->_offsetLegend; // TODO: account for legendSize // + alignedSize(legendSize) + m->_offsetJson = m->_offsetLegend + alignedSize(legend.getSize()); + storeSizedString(m->_offsetKey, key.c_str(), key.size()); + + // store legend { - // store key - size_t const n = key.size(); - char* p = static_cast(base()) + m->_offsetKey; - - // init key buffer - memset(p, '\0', (1 + ((n + 1) / 8)) * 8); - - // store length of key - *p = (uint8_t) n; - // store actual key - memcpy(p + 1, key.c_str(), n); + char* p = static_cast(base()) + m->_offsetLegend; + legend.dump(p); } - // store legend // TODO - // store shapedJson { char* p = static_cast(base()) + m->_offsetJson; @@ -266,6 +274,57 @@ namespace triagens { } }; + + struct EdgeMarker : public Marker { + EdgeMarker (TRI_voc_tick_t databaseId, + TRI_voc_cid_t collectionId, + TRI_voc_rid_t revisionId, + TRI_voc_tid_t transactionId, + std::string const& key, + TRI_document_edge_t const* edge, + triagens::basics::JsonLegend& legend, + TRI_shaped_json_t const* shapedJson) + : Marker(TRI_WAL_MARKER_EDGE, + sizeof(edge_marker_t) + alignedSize(key.size() + 2) + alignedSize(strlen(edge->_fromKey) + 2) + alignedSize(strlen(edge->_toKey) + 2) + legend.getSize() + shapedJson->_data.length) { + + document_marker_t* m = reinterpret_cast(base()); + edge_marker_t* e = reinterpret_cast(base()); + + m->_databaseId = databaseId; + m->_collectionId = collectionId; + m->_rid = revisionId; + m->_tid = transactionId; + m->_shape = shapedJson->_sid; + m->_offsetKey = sizeof(edge_marker_t); // start position of key + e->_toCid = edge->_toCid; + e->_fromCid = edge->_fromCid; + e->_offsetToKey = m->_offsetKey + alignedSize(key.size() + 2); + e->_offsetFromKey = e->_offsetToKey + alignedSize(strlen(edge->_toKey) + 2); + m->_offsetLegend = e->_offsetFromKey + alignedSize(strlen(edge->_fromKey) + 2); + m->_offsetJson = m->_offsetLegend + alignedSize(legend.getSize()); + + // store keys + storeSizedString(m->_offsetKey, key.c_str(), key.size()); + storeSizedString(e->_offsetFromKey, edge->_fromKey, strlen(edge->_fromKey)); + storeSizedString(e->_offsetToKey, edge->_toKey, strlen(edge->_toKey)); + + // store legend + { + char* p = static_cast(base()) + m->_offsetLegend; + legend.dump(p); + } + + // store shapedJson + { + char* p = static_cast(base()) + m->_offsetJson; + memcpy(p, shapedJson->_data.data, static_cast(shapedJson->_data.length)); + } + } + + ~EdgeMarker () { + } + + }; /* struct RemoveMarker : public Marker { diff --git a/arangod/Wal/Slots.h b/arangod/Wal/Slots.h index c9204d26d0..bce3ee6ad0 100644 --- a/arangod/Wal/Slots.h +++ b/arangod/Wal/Slots.h @@ -48,12 +48,14 @@ namespace triagens { explicit SlotInfo (int errorCode) : slot(nullptr), mem(nullptr), + size(0), errorCode(errorCode) { } explicit SlotInfo (Slot* slot) : slot(slot), mem(slot->mem()), + size(slot->size()), errorCode(TRI_ERROR_NO_ERROR) { } @@ -63,6 +65,7 @@ namespace triagens { Slot* slot; void const* mem; + uint32_t size; int errorCode; }; diff --git a/js/apps/system/aardvark/frontend/js/modules/org/arangodb/graph/traversal.js b/js/apps/system/aardvark/frontend/js/modules/org/arangodb/graph/traversal.js index bb11770548..11f48b7cb3 100644 --- a/js/apps/system/aardvark/frontend/js/modules/org/arangodb/graph/traversal.js +++ b/js/apps/system/aardvark/frontend/js/modules/org/arangodb/graph/traversal.js @@ -30,6 +30,7 @@ module.define("org/arangodb/graph/traversal", function(exports, module) { //////////////////////////////////////////////////////////////////////////////// var graph = require("org/arangodb/graph-blueprint"); +var generalGraph = require("org/arangodb/general-graph"); var arangodb = require("org/arangodb"); var BinaryHeap = require("org/arangodb/heap").BinaryHeap; var ArangoError = arangodb.ArangoError; @@ -159,6 +160,72 @@ function collectionDatasourceFactory (edgeCollection) { }; } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief general graph datasource +/// +/// This is a factory function that creates a datasource that operates on the +/// specified general graph. The vertices and edges are delivered by the +/// the general-graph module. +//////////////////////////////////////////////////////////////////////////////// + +function generalGraphDatasourceFactory (graph) { + var g = graph; + if (typeof g === 'string') { + g = generalGraph._graph(g); + } + + return { + graph: g, + + getVertexId: function (vertex) { + return vertex._id; + }, + + getPeerVertex: function (edge, vertex) { + if (edge._from === vertex._id) { + return db._document(edge._to); + } + + if (edge._to === vertex._id) { + return db._document(edge._from); + } + + return null; + }, + + getInVertex: function (edge) { + return db._document(edge._to); + }, + + getOutVertex: function (edge) { + return db._document(edge._from); + }, + + getEdgeId: function (edge) { + return edge._id; + }, + + getLabel: function (edge) { + return edge.$label; + }, + + getAllEdges: function (vertex) { + return this.graph._EDGES(vertex._id); + }, + + getInEdges: function (vertex) { + return this.graph._INEDGES(vertex._id); + }, + + getOutEdges: function (vertex) { + return this.graph._OUTEDGES(vertex._id); + } + }; +} + + + //////////////////////////////////////////////////////////////////////////////// /// @brief default Graph datasource /// @@ -1487,6 +1554,7 @@ ArangoTraverser.EXCLUDE = 'exclude'; //////////////////////////////////////////////////////////////////////////////// exports.collectionDatasourceFactory = collectionDatasourceFactory; +exports.generalGraphDatasourceFactory = generalGraphDatasourceFactory; exports.graphDatasourceFactory = graphDatasourceFactory; exports.outboundExpander = outboundExpander; diff --git a/js/apps/system/aardvark/frontend/js/templates/queryView.ejs b/js/apps/system/aardvark/frontend/js/templates/queryView.ejs index aa347531ba..a900c115ff 100644 --- a/js/apps/system/aardvark/frontend/js/templates/queryView.ejs +++ b/js/apps/system/aardvark/frontend/js/templates/queryView.ejs @@ -70,54 +70,4 @@ - - - - - diff --git a/js/apps/system/aardvark/frontend/js/views/documentsView.js b/js/apps/system/aardvark/frontend/js/views/documentsView.js index 7a32964bfc..7d08f970ae 100644 --- a/js/apps/system/aardvark/frontend/js/views/documentsView.js +++ b/js/apps/system/aardvark/frontend/js/views/documentsView.js @@ -168,7 +168,7 @@ this.buildCollectionLink( this.collectionContext.prev ), - { + { trigger: true } ); @@ -281,7 +281,7 @@ var num = ++this.filterId; $('#filterHeader').append('
'+ - ''+ ''+ - ''+ ' ' + '
'); @@ -432,6 +432,7 @@ this.collection.getDocuments(this.collection.collectionID, page); $('#docDeleteModal').modal('hide'); this.drawTable(); + this.renderPaginationElements(); } }, @@ -554,7 +555,7 @@ $('.modalImportTooltips').tooltip({ placement: "left" }); - + arangoHelper.fixTooltips(".icon_arangodb, .arangoicon", "top"); this.drawTable(); this.renderPaginationElements(); diff --git a/js/apps/system/aardvark/frontend/js/views/modalView.js b/js/apps/system/aardvark/frontend/js/views/modalView.js index 4b5ebfd573..b93759e8ae 100644 --- a/js/apps/system/aardvark/frontend/js/views/modalView.js +++ b/js/apps/system/aardvark/frontend/js/views/modalView.js @@ -13,7 +13,7 @@ }; }; - var createTextStub = function(type, label, value, info, placeholder, mandatory) { + var createTextStub = function(type, label, value, info, placeholder, mandatory, regexp) { var obj = { type: type, label: label @@ -30,6 +30,12 @@ if (mandatory) { obj.mandatory = mandatory; } + if (regexp){ + // returns true if the string contains the match + obj.validateInput = function(el){ + return regexp.test(el.val()); + }; + } return obj; }; @@ -161,8 +167,9 @@ return obj; }, - createTextEntry: function(id, label, value, info, placeholder, mandatory) { - var obj = createTextStub(this.tables.TEXT, label, value, info, placeholder, mandatory); + createTextEntry: function(id, label, value, info, placeholder, mandatory, regexp) { + var obj = createTextStub(this.tables.TEXT, label, value, info, placeholder, mandatory, + regexp); obj.id = id; return obj; }, @@ -270,6 +277,20 @@ }); } });//handle select2 + + self.testInput = (function(){ + _.each(tableContent,function(r){ + if(r.validateInput){ + $('#' + r.id).on('keyup', function(){ + if(r.validateInput($('#' + r.id))){ + $('#' + r.id).addClass('invalid-input'); + } else { + $('#' + r.id).removeClass('invalid-input'); + } + }); + } + }); + }()); if (events) { this.events = events; this.delegateEvents(); diff --git a/js/apps/system/aardvark/frontend/js/views/queryView.js b/js/apps/system/aardvark/frontend/js/views/queryView.js index f1ccaa63a0..e7aefea820 100644 --- a/js/apps/system/aardvark/frontend/js/views/queryView.js +++ b/js/apps/system/aardvark/frontend/js/views/queryView.js @@ -17,26 +17,6 @@ this.tableDescription.rows = this.customQueries; }, - updateTable: function () { - this.tableDescription.rows = this.customQueries; - - _.each(this.tableDescription.rows, function(k,v) { - k.thirdRow = ''; - }); - - this.$(this.id).html(this.table.render({content: this.tableDescription})); - }, - - editCustomQuery: function(e) { - var queryName = $(e.target).parent().children().first().text(); - var inputEditor = ace.edit("aqlEditor"); - inputEditor.setValue(this.getCustomQueryValueByName(queryName)); - this.deselect(inputEditor); - $('#querySelect').val(queryName); - this.switchTab("query-switch"); - }, - events: { "click #result-switch": "switchTab", "click #query-switch": "switchTab", @@ -57,18 +37,56 @@ 'click #clearQueryButton': 'clearInput', 'click #addAQL': 'addAQL', 'click #editAQL': 'editAQL', - 'click #save-query': 'saveAQL', - 'click #delete-edit-query': 'showDeleteField', + 'click #delete-edit-query': 'showDeleteFie/ld', 'click #abortDeleteQuery': 'hideDeleteField', - 'keyup #new-query-name': 'listenKey', 'change #queryModalSelect': 'updateEditSelect', 'change #querySelect': 'importSelected', 'change #querySize': 'changeSize', 'keypress #aqlEditor': 'aqlShortcuts', 'click #arangoQueryTable .table-cell0': 'editCustomQuery', 'click #arangoQueryTable .table-cell1': 'editCustomQuery', - 'click #arangoQueryTable .table-cell2 a': 'deleteAQL', - 'click #queryDiv .showHotkeyHelp': 'shortcutModal' + 'click #arangoQueryTable .table-cell2 a': 'deleteAQL' + + }, + + createCustomQueryModal: function(){ + var buttons = [], tableContent = []; + tableContent.push( + window.modalView.createTextEntry( + 'new-query-name', + 'Name', + '', + undefined, + undefined, + false, + /[<>&'"]/ + ) + ); + buttons.push( + window.modalView.createSuccessButton('Save', this.saveAQL.bind(this)) + ); + window.modalView.show('modalTable.ejs', 'Save Query', buttons, tableContent, undefined, + {'keyup #new-query-name' : this.listenKey.bind(this)}); + }, + + updateTable: function () { + this.tableDescription.rows = this.customQueries; + + _.each(this.tableDescription.rows, function(k,v) { + k.thirdRow = ''; + }); + + this.$(this.id).html(this.table.render({content: this.tableDescription})); + }, + + editCustomQuery: function(e) { + var queryName = $(e.target).parent().children().first().text(); + var inputEditor = ace.edit("aqlEditor"); + inputEditor.setValue(this.getCustomQueryValueByName(queryName)); + this.deselect(inputEditor); + $('#querySelect').val(queryName); + this.switchTab("query-switch"); }, initTabArray: function() { @@ -92,42 +110,19 @@ return; } - //check for invalid query names, if present change the box-shadoq to red + //check for invalid query names, if present change the box-shadow to red // and disable the save functionality - - var dangerCss = { - "webkit-box-shadow" : "inset 0 1px 1px rgba( 0,0,0, 0.075), 0 0 8px rgba(234, 23, 23, 0.6)", - "moz-box-shadow" : "inset 0 1px 1px rgba( 0,0,0, 0.075), 0 0 8px rgba(234, 23, 23, 0.6)", - "box-shadow" : "inset 0 1px 1px rgba( 0,0,0, 0.075), 0 0 8px rgba(234, 23, 23, 0.6)", - "border-color" : "rgba(234, 23, 23, 0.8)" - }; - - var normalCss = { - "webkit-box-shadow" : "inset 0 1px 1px rgba( 0,0,0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6)", - "moz-box-shadow" : "inset 0 1px 1px rgba( 0,0,0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6)", - "box-shadow" : "inset 0 1px 1px rgba( 0,0,0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6)", - "border-color" : "rgba(82, 168, 236, 0.8)" - }; - - if ( saveName.match(/[<>&'"]/g)){ - $('#new-query-name').css(dangerCss); - $('#new-query-name').addClass('invalid'); - } else { - $('#new-query-name').css(normalCss); - $('#new-query-name').removeClass('invalid'); - } - var boolTemp = false; this.customQueries.some(function(query){ if( query.name === saveName ){ - $('#save-query').removeClass('button-success'); - $('#save-query').addClass('button-warning'); - $('#save-query').text('Update'); + $('#modalButton1').removeClass('button-success'); + $('#modalButton1').addClass('button-warning'); + $('#modalButton1').text('Update'); boolTemp = true; } else { - $('#save-query').removeClass('button-warning'); - $('#save-query').addClass('button-success'); - $('#save-query').text('Save'); + $('#modalButton1').removeClass('button-warning'); + $('#modalButton1').addClass('button-success'); + $('#modalButton1').text('Save'); } if (boolTemp) { @@ -309,8 +304,8 @@ addAQL: function () { //render options + this.createCustomQueryModal(); $('#new-query-name').val($('#querySelect').val()); - $('#new-aql-query').modal('show'); setTimeout(function () { $('#new-query-name').focus(); }, 500); @@ -363,10 +358,11 @@ }, saveAQL: function (e) { + e.stopPropagation(); var inputEditor = ace.edit("aqlEditor"); var saveName = $('#new-query-name').val(); - if ($('#new-query-name').hasClass('invalid')) { + if ($('#new-query-name').hasClass('invalid-input')) { return; } @@ -389,8 +385,7 @@ if (quit === true) { //Heiko: Form-Validator - name already taken - $('#new-aql-query').modal('hide'); - $('#edit-aql-query').modal('hide'); + window.modalView.hide(); return; } @@ -399,8 +394,7 @@ value: content }); - $('#new-aql-query').modal('hide'); - $('#edit-aql-query').modal('hide'); + window.modalView.hide(); localStorage.setItem("customQueries", JSON.stringify(this.customQueries)); this.renderSelectboxes(); @@ -554,10 +548,6 @@ }, - shortcutModal: function() { - window.arangoHelper.hotkeysFunctions.showHotkeysModal(); - }, - // This function changes the focus onto the tab that has been clicked // it can be given an event-object or the id of the tab to switch to // e.g. switchTab("result-switch"); diff --git a/js/apps/system/aardvark/frontend/scss/_colors.scss b/js/apps/system/aardvark/frontend/scss/_colors.scss index 9a8068f4a3..aa4068eef3 100644 --- a/js/apps/system/aardvark/frontend/scss/_colors.scss +++ b/js/apps/system/aardvark/frontend/scss/_colors.scss @@ -72,6 +72,7 @@ $c-shell-input: #dd0; $c-shell-old-input: #bb0; $c-notification-red: #c00; +$c-invalid-red: rgba(234, 23, 23, .6); $c-cluster-button-green: #617e2b; $c-cluster-button-green-hover: #8ba142; diff --git a/js/apps/system/aardvark/frontend/scss/_mixins.scss b/js/apps/system/aardvark/frontend/scss/_mixins.scss index 943916cc38..33ec89639c 100644 --- a/js/apps/system/aardvark/frontend/scss/_mixins.scss +++ b/js/apps/system/aardvark/frontend/scss/_mixins.scss @@ -1,5 +1,5 @@ // Horizontal offset, vertical offset, blur, spread, color -@mixin box-shadow($params) { +@mixin box-shadow($params...) { -webkit-box-shadow: $params; -moz-box-shadow: $params; box-shadow: $params; diff --git a/js/apps/system/aardvark/frontend/scss/_modals.scss b/js/apps/system/aardvark/frontend/scss/_modals.scss index 43b234bffb..032455929c 100644 --- a/js/apps/system/aardvark/frontend/scss/_modals.scss +++ b/js/apps/system/aardvark/frontend/scss/_modals.scss @@ -10,6 +10,18 @@ margin-top: 10px; } + input[type='checkbox'] { + margin-bottom: 10px; + } + + input[type='text'].invalid-input { + border-color: $c-invalid-red; + + &:focus { + @include box-shadow(inset 0 1px 1px $c-scenario-bg-transp, 0 0 8px $c-invalid-red); + } + } + th { %cell-centered { text-align: center; @@ -56,6 +68,18 @@ opacity: 1; } + .icon_arangodb_info { + color: $c-neutral-hover; + font-size: 18px; + margin-top: -10px; + position: absolute; + right: 12px; + } + + .icon_arangodb_info:hover { + color: $c-black; + } + .collapse { margin-right: -14px; position: relative; @@ -116,10 +140,16 @@ opacity: .4; } - - .modal { border-radius: 0 !important; + + .fade.in { + top: 12.1% !important; + } + + table tr:last-child { + border-bottom: 0 !important; + } } .waitModal { @@ -169,55 +199,37 @@ pre.gv-object-view { padding-left: 5px; padding-right: 10px; padding-top: 4px; + + .arangoHeader { + position: relative; + top: 3px; + } + + a { + top: 2px !important; + } + + .close { + color: $c-modal-header; + font-weight: 300; + opacity: .5; + } + + .close:hover { + opacity: 1; + } } -.modal-header .arangoHeader { - position: relative; - top: 3px; -} -.modal-header a { - top: 2px !important; -} -.modal-header .close { - color: $c-modal-header; - font-weight: 300; - opacity: .5; -} -.modal-header .close:hover { - opacity: 1; -} - -.modal-body .icon_arangodb_info { - color: $c-neutral-hover; - font-size: 18px; - margin-top: -10px; - position: absolute; - right: 12px; -} - -.modal-body .icon_arangodb_info:hover { - color: $c-black; -} - -.modal.fade.in { - top: 12.1% !important; -} .modal table tr, .thBorderBottom { border-bottom: 1px solid $c-modal-table-border-bottom !important; } -.modal table tr:last-child { - border-bottom: 0 !important; -} -.modal-body input[type='checkbox'] { - margin-bottom: 10px; -} .modal-delete-confirmation { display: none; @@ -263,5 +275,4 @@ pre.gv-object-view { width: 100%; } - } diff --git a/js/apps/system/aardvark/frontend/scss/generated.css b/js/apps/system/aardvark/frontend/scss/generated.css index d6181d3b59..598ac9d54e 100644 --- a/js/apps/system/aardvark/frontend/scss/generated.css +++ b/js/apps/system/aardvark/frontend/scss/generated.css @@ -3864,6 +3864,14 @@ div.breadcrumb a.disabledBread { .modal-body select, .modal-body textarea { margin-top: 10px; } + .modal-body input[type='checkbox'] { + margin-bottom: 10px; } + .modal-body input[type='text'].invalid-input { + border-color: rgba(234, 23, 23, 0.6); } + .modal-body input[type='text'].invalid-input:focus { + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(234, 23, 23, 0.6); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(234, 23, 23, 0.6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(234, 23, 23, 0.6); } .modal-body th th.actionCell, .modal-body th th.keyCell, .modal-body th .valueCell { text-align: center; } .modal-body th.actionCell { @@ -3886,6 +3894,14 @@ div.breadcrumb a.disabledBread { padding-bottom: 5px; } .modal-body .icon-info-sign:hover { opacity: 1; } + .modal-body .icon_arangodb_info { + color: #736b68; + font-size: 18px; + margin-top: -10px; + position: absolute; + right: 12px; } + .modal-body .icon_arangodb_info:hover { + color: black; } .modal-body .collapse { margin-right: -14px; position: relative; } @@ -3920,6 +3936,10 @@ div.breadcrumb a.disabledBread { .modal { border-radius: 0 !important; } + .modal .fade.in { + top: 12.1% !important; } + .modal table tr:last-child { + border-bottom: 0 !important; } .waitModal { -webkit-box-shadow: none; @@ -3958,45 +3978,22 @@ pre.gv-object-view { padding-left: 5px; padding-right: 10px; padding-top: 4px; } - -.modal-header .arangoHeader { - position: relative; - top: 3px; } - -.modal-header a { - top: 2px !important; } - -.modal-header .close { - color: white; - font-weight: 300; - opacity: .5; } - -.modal-header .close:hover { - opacity: 1; } - -.modal-body .icon_arangodb_info { - color: #736b68; - font-size: 18px; - margin-top: -10px; - position: absolute; - right: 12px; } - -.modal-body .icon_arangodb_info:hover { - color: black; } - -.modal.fade.in { - top: 12.1% !important; } + .modal-header .arangoHeader { + position: relative; + top: 3px; } + .modal-header a { + top: 2px !important; } + .modal-header .close { + color: white; + font-weight: 300; + opacity: .5; } + .modal-header .close:hover { + opacity: 1; } .modal table tr, .thBorderBottom { border-bottom: 1px solid #f7f3f2 !important; } -.modal table tr:last-child { - border-bottom: 0 !important; } - -.modal-body input[type='checkbox'] { - margin-bottom: 10px; } - .modal-delete-confirmation { display: none; } .modal-delete-confirmation button { @@ -5270,8 +5267,8 @@ input.gv-radio-button { .arango-collection-ul a { font-size: 13px; } } .hotkeysList .hotkeysLabel { - color: #000; clear: both; + color: #000; font-size: 16px; font-weight: 400; } .hotkeysList .hotkeysContent { @@ -5292,8 +5289,8 @@ input.gv-radio-button { width: 19px; } .hotkeysContentLabel { - width: 30%; - float: left; } + float: left; + width: 30%; } .arango-table { border-top: 1px solid #000; diff --git a/js/apps/system/aardvark/test/specs/views/documentsViewSpec.js b/js/apps/system/aardvark/test/specs/views/documentsViewSpec.js index fa724253da..bba796eb90 100644 --- a/js/apps/system/aardvark/test/specs/views/documentsViewSpec.js +++ b/js/apps/system/aardvark/test/specs/views/documentsViewSpec.js @@ -1290,9 +1290,11 @@ spyOn(view, "drawTable"); view.collection = new window.arangoDocuments(); view.target = "#confirmDeleteBtn"; + spyOn(view, "renderPaginationElements"); view.reallyDelete(); + expect(view.renderPaginationElements).toHaveBeenCalled(); expect(window.$).toHaveBeenCalledWith("#confirmDeleteBtn"); expect(window.$).toHaveBeenCalledWith("#documentsTableID"); expect(window.$).toHaveBeenCalledWith("#docDeleteModal"); @@ -1375,8 +1377,11 @@ view.collection = new window.arangoDocuments(); view.target = "#confirmDeleteBtn"; spyOn(view, "drawTable"); + spyOn(view, "renderPaginationElements"); + view.reallyDelete(); + expect(view.renderPaginationElements).toHaveBeenCalled(); expect(window.$).toHaveBeenCalledWith("#confirmDeleteBtn"); diff --git a/js/common/modules/org/arangodb/general-graph.js b/js/common/modules/org/arangodb/general-graph.js index d186326e19..b5daa0eefc 100644 --- a/js/common/modules/org/arangodb/general-graph.js +++ b/js/common/modules/org/arangodb/general-graph.js @@ -31,6 +31,7 @@ var arangodb = require("org/arangodb"), ArangoCollection = arangodb.ArangoCollection, + ArangoError = arangodb.ArangoError, db = arangodb.db, errors = arangodb.errors, _ = require("underscore"); @@ -53,7 +54,7 @@ var stringToArray = function (x) { if (typeof x === "string") { return [x]; } - return x; + return _.clone(x); }; //////////////////////////////////////////////////////////////////////////////// @@ -118,6 +119,19 @@ var findOrCreateCollectionsByEdgeDefinitions = function (edgeDefinitions, noCrea ]; }; +//////////////////////////////////////////////////////////////////////////////// +/// @brief internal function to get graphs collection +//////////////////////////////////////////////////////////////////////////////// + +var _getGraphCollection = function() { + var gCol = db._graphs; + if (gCol === null || gCol === undefined) { + throw "_graphs collection does not exist."; + } + return gCol; +}; + + // ----------------------------------------------------------------------------- // --SECTION-- module "org/arangodb/general-graph" @@ -127,10 +141,6 @@ var findOrCreateCollectionsByEdgeDefinitions = function (edgeDefinitions, noCrea // --SECTION-- Fluent AQL Interface // ----------------------------------------------------------------------------- -// ----------------------------------------------------------------------------- -// --SECTION-- Fluent AQL Interface -// ----------------------------------------------------------------------------- - var AQLStatement = function(query, isEdgeQuery) { this.query = query; this.edgeQuery = isEdgeQuery || false; @@ -148,11 +158,12 @@ AQLStatement.prototype.isEdgeQuery = function() { // --SECTION-- AQL Generator // ----------------------------------------------------------------------------- -var AQLGenerator = function(graphName) { +var AQLGenerator = function(graph) { this.stack = []; this.bindVars = { - "graphName": graphName + "graphName": graph.__name }; + this.graph = graph; this.lastEdgeVar = ""; }; @@ -177,6 +188,20 @@ AQLGenerator.prototype.getLastEdgeVar = function() { }; AQLGenerator.prototype.restrict = function(restrictions) { + var rest = stringToArray(restrictions); + var unknown = []; + var g = this.graph; + _.each(rest, function(r) { + if (!g.__edgeCollections[r]) { + unknown.push(r); + } + }); + if (unknown.length > 0) { + var err = new ArangoError(); + err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code; + err.errorMessage = "edge collections: " + unknown.join(" and ") + " are not known to the graph"; + throw err; + } var lastQuery = this.stack.pop(); if (!lastQuery.isEdgeQuery()) { this.stack.push(lastQuery); @@ -184,7 +209,7 @@ AQLGenerator.prototype.restrict = function(restrictions) { } lastQuery.query = lastQuery.query.replace(")", ",{},@restrictions_" + this.stack.length + ")"); lastQuery.edgeQuery = false; - this.bindVars["restrictions_" + this.stack.length] = stringToArray(restrictions); + this.bindVars["restrictions_" + this.stack.length] = rest; this.stack.push(lastQuery); return this; }; @@ -279,7 +304,7 @@ var _directedRelationDefinition = function ( relationName, fromVertexCollections, toVertexCollections) { if (arguments.length < 3) { - throw "method _undirectedRelationDefinition expects 3 arguments"; + throw "method _directedRelationDefinition expects 3 arguments"; } if (typeof relationName !== "string" || relationName === "") { @@ -324,14 +349,11 @@ var edgeDefinitions = function () { var _create = function (graphName, edgeDefinitions) { - var gdb = db._graphs, + var gdb = _getGraphCollection(), g, graphAlreadyExists = true, collections; - if (gdb === null || gdb === undefined) { - throw "_graphs collection does not exist."; - } if (!graphName) { throw "a graph name is required to create a graph."; } @@ -377,6 +399,24 @@ var Graph = function(graphName, edgeDefinitions, vertexCollections, edgeCollecti _.each(vertexCollections, function(obj, key) { self[key] = obj; + var old_remove = obj.remove.bind(obj); + obj.remove = function(vertexId, options) { + var myEdges = self._EDGES(vertexId); + myEdges.forEach( + function(edgeObj) { + var edgeId = edgeObj._id; + var edgeCollection = edgeId.split("/")[0]; + if (db[edgeCollection] && db[edgeCollection].exists(edgeId)) { + db[edgeCollection].remove(edgeId); + } + } + ); + + if (options === null || options === undefined) { + return old_remove(vertexId); + } + return old_remove(vertexId, options); + }; }); _.each(edgeCollections, function(obj, key) { @@ -408,13 +448,9 @@ var Graph = function(graphName, edgeDefinitions, vertexCollections, edgeCollecti var _graph = function(graphName) { - var gdb = db._graphs, + var gdb = _getGraphCollection(), g, collections; - if (gdb === null || gdb === undefined) { - throw "_graphs collection does not exist."; - } - try { g = gdb.document(graphName); } @@ -430,6 +466,54 @@ var _graph = function(graphName) { return new Graph(graphName, g.edgeDefinitions, collections[0], collections[1]); }; +//////////////////////////////////////////////////////////////////////////////// +/// @brief check if a graph exists. +//////////////////////////////////////////////////////////////////////////////// + +var _exists = function(graphId) { + var gCol = _getGraphCollection(); + return gCol.exists(graphId); +}; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief drop a graph. +//////////////////////////////////////////////////////////////////////////////// + +var _drop = function(graphId, dropCollections) { + + var gdb = _getGraphCollection(); + + if (!gdb.exists(graphId)) { + throw "Graph " + graphId + " does not exist."; + } + + if (dropCollections !== false) { + var graph = gdb.document(graphId); + var edgeDefinitions = graph.edgeDefinitions; + edgeDefinitions.forEach( + function(edgeDefinition) { + var from = edgeDefinition.from; + var to = edgeDefinition.to; + var edge = edgeDefinition.collection; + db._drop(edge); + from.forEach( + function(col) { + db._drop(col); + } + ); + to.forEach( + function(col) { + db._drop(col); + } + ); + } + ); + } + + gdb.remove(graphId); + return true; +}; + //////////////////////////////////////////////////////////////////////////////// /// @brief return all edge collections of the graph. //////////////////////////////////////////////////////////////////////////////// @@ -506,7 +590,7 @@ Graph.prototype._OUTEDGES = function(vertexId) { //////////////////////////////////////////////////////////////////////////////// Graph.prototype._edges = function(vertexId) { - var AQLStmt = new AQLGenerator(this.__name); + var AQLStmt = new AQLGenerator(this); return AQLStmt.edges(vertexId, "any"); }; @@ -515,7 +599,7 @@ Graph.prototype._edges = function(vertexId) { //////////////////////////////////////////////////////////////////////////////// Graph.prototype._inEdges = function(vertexId) { - var AQLStmt = new AQLGenerator(this.__name); + var AQLStmt = new AQLGenerator(this); return AQLStmt.edges(vertexId, "inbound"); }; @@ -524,7 +608,7 @@ Graph.prototype._inEdges = function(vertexId) { //////////////////////////////////////////////////////////////////////////////// Graph.prototype._outEdges = function(vertexId) { - var AQLStmt = new AQLGenerator(this.__name); + var AQLStmt = new AQLGenerator(this); return AQLStmt.edges(vertexId, "outbound"); }; //////////////////////////////////////////////////////////////////////////////// @@ -587,6 +671,8 @@ exports._directedRelationDefinition = _directedRelationDefinition; exports._graph = _graph; exports.edgeDefinitions = edgeDefinitions; exports._create = _create; +exports._drop = _drop; +exports._exists = _exists; // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE diff --git a/js/common/tests/shell-general-graph.js b/js/common/tests/shell-general-graph.js index c1641da63d..3fb5e6ce46 100644 --- a/js/common/tests/shell-general-graph.js +++ b/js/common/tests/shell-general-graph.js @@ -1,5 +1,5 @@ /*jslint indent: 2, nomen: true, maxlen: 80, sloppy: true */ -/*global require, assertEqual, assertTrue, assertFalse */ +/*global require, assertEqual, assertTrue, assertFalse, fail */ //////////////////////////////////////////////////////////////////////////////// /// @brief test the general-graph class @@ -32,6 +32,7 @@ var jsunity = require("jsunity"); var arangodb = require("org/arangodb"); var db = arangodb.db; var graph = require("org/arangodb/general-graph"); +var ERRORS = arangodb.errors; var _ = require("underscore"); @@ -165,7 +166,7 @@ function GeneralGraphCreationSuite() { exception = err; } - assertEqual(exception, "method _undirectedRelationDefinition expects 3 arguments"); + assertEqual(exception, "method _directedRelationDefinition expects 3 arguments"); }, @@ -414,20 +415,18 @@ function GeneralGraphCreationSuite() { function GeneralGraphAQLQueriesSuite() { + // Definition of names + var graphName = "UnitTestsGraph"; + var included = "UnitTestIncluded"; + var excluded = "UnitTestExcluded"; + var v1 = "UnitTestV1"; + var v2 = "UnitTestV2"; + var v3 = "UnitTestV3"; + var dropInclExcl = function() { - var col = db._collection("_graphs"); - try { - col.remove("graph"); - } catch (e) { - return; + if (graph._exists(graphName)) { + graph._drop(graphName); } - var colList = ["v1", "v2", "v3", "included", "excluded"]; - _.each(colList, function(c) { - var colToClear = db._collection(c); - if (col) { - colToClear.truncate(); - } - }); }; var e1, e2, e3; @@ -435,35 +434,35 @@ function GeneralGraphAQLQueriesSuite() { var createInclExcl = function() { dropInclExcl(); var inc = graph._directedRelationDefinition( - "included", ["v1"], ["v1", "v2"] + included, [v1], [v1, v2] ); var exc = graph._directedRelationDefinition( - "excluded", ["v1"], ["v3"] + excluded, [v1], [v3] ); - var g = graph._create("graph", [inc, exc]); - g.v1.save({_key: "1"}); - g.v1.save({_key: "2"}); - g.v2.save({_key: "1"}); - g.v3.save({_key: "1"}); - e1 = g.included.save( - "v1/1", - "v2/1", + var g = graph._create(graphName, [inc, exc]); + g[v1].save({_key: "1"}); + g[v1].save({_key: "2"}); + g[v2].save({_key: "1"}); + g[v3].save({_key: "1"}); + e1 = g[included].save( + v1 + "/1", + v2 + "/1", { _key: "e1", val: true } )._id; - e2 = g.included.save( - "v1/2", - "v1/1", + e2 = g[included].save( + v1 + "/2", + v1 + "/1", { _key: "e2", val: false } )._id; - e3 = g.excluded.save( - "v1/1", - "v3/1", + e3 = g[excluded].save( + v1 + "/1", + v3 + "/1", { _key: "e3", val: false @@ -504,12 +503,12 @@ function GeneralGraphAQLQueriesSuite() { //////////////////////////////////////////////////////////////////////////////// test_edges: function() { - var query = g._edges("v1/1"); + var query = g._edges(v1 + "/1"); assertEqual(query.printQuery(), 'FOR edges_0 IN GRAPH_EDGES(' + '@graphName,@startVertex_0,"any")'); var bindVars = query.bindVars; - assertEqual(bindVars.graphName, "graph"); - assertEqual(bindVars.startVertex_0, "v1/1"); + assertEqual(bindVars.graphName, graphName); + assertEqual(bindVars.startVertex_0, v1 + "/1"); var result = query.toArray(); assertEqual(result.length, 3); assertTrue(findIdInResult(result, e1), "Did not include e1"); @@ -522,12 +521,12 @@ function GeneralGraphAQLQueriesSuite() { //////////////////////////////////////////////////////////////////////////////// test_outEdges: function() { - var query = g._outEdges("v1/1"); + var query = g._outEdges(v1 + "/1"); assertEqual(query.printQuery(), "FOR edges_0 IN GRAPH_EDGES(" + '@graphName,@startVertex_0,"outbound")'); var bindVars = query.bindVars; - assertEqual(bindVars.graphName, "graph"); - assertEqual(bindVars.startVertex_0, "v1/1"); + assertEqual(bindVars.graphName, graphName); + assertEqual(bindVars.startVertex_0, v1 + "/1"); var result = query.toArray(); assertEqual(result.length, 2); assertTrue(findIdInResult(result, e1), "Did not include e1"); @@ -540,12 +539,12 @@ function GeneralGraphAQLQueriesSuite() { //////////////////////////////////////////////////////////////////////////////// test_inEdges: function() { - var query = g._inEdges("v1/1"); + var query = g._inEdges(v1 + "/1"); assertEqual(query.printQuery(), "FOR edges_0 IN GRAPH_EDGES(" + '@graphName,@startVertex_0,"inbound")'); var bindVars = query.bindVars; - assertEqual(bindVars.graphName, "graph"); - assertEqual(bindVars.startVertex_0, "v1/1"); + assertEqual(bindVars.graphName, graphName); + assertEqual(bindVars.startVertex_0, v1 + "/1"); var result = query.toArray(); assertEqual(result.length, 1); assertTrue(findIdInResult(result, e2), "Did not include e2"); @@ -554,13 +553,13 @@ function GeneralGraphAQLQueriesSuite() { }, test_restrictOnEdges: function() { - var query = g._edges("v1/1").restrict("included"); + var query = g._edges(v1 + "/1").restrict(included); assertEqual(query.printQuery(), "FOR edges_0 IN GRAPH_EDGES(" + '@graphName,@startVertex_0,"any",{},@restrictions_0)'); var bindVars = query.bindVars; - assertEqual(bindVars.graphName, "graph"); - assertEqual(bindVars.startVertex_0, "v1/1"); - assertEqual(bindVars.restrictions_0, ["included"]); + assertEqual(bindVars.graphName, graphName); + assertEqual(bindVars.startVertex_0, v1 + "/1"); + assertEqual(bindVars.restrictions_0, [included]); var result = query.toArray(); assertEqual(result.length, 2); @@ -574,13 +573,13 @@ function GeneralGraphAQLQueriesSuite() { //////////////////////////////////////////////////////////////////////////////// test_restrictOnInEdges: function() { - var query = g._inEdges("v1/1").restrict("included"); + var query = g._inEdges(v1 + "/1").restrict(included); assertEqual(query.printQuery(), "FOR edges_0 IN GRAPH_EDGES(" + '@graphName,@startVertex_0,"inbound",{},@restrictions_0)'); var bindVars = query.bindVars; - assertEqual(bindVars.graphName, "graph"); - assertEqual(bindVars.startVertex_0, "v1/1"); - assertEqual(bindVars.restrictions_0, ["included"]); + assertEqual(bindVars.graphName, graphName); + assertEqual(bindVars.startVertex_0, v1 + "/1"); + assertEqual(bindVars.restrictions_0, [included]); var result = query.toArray(); assertEqual(result.length, 1); assertTrue(findIdInResult(result, e2), "Did not include e2"); @@ -593,13 +592,13 @@ function GeneralGraphAQLQueriesSuite() { //////////////////////////////////////////////////////////////////////////////// test_restrictOnOutEdges: function() { - var query = g._outEdges("v1/1").restrict("included"); + var query = g._outEdges(v1 + "/1").restrict(included); assertEqual(query.printQuery(), "FOR edges_0 IN GRAPH_EDGES(" + '@graphName,@startVertex_0,"outbound",{},@restrictions_0)'); var bindVars = query.bindVars; - assertEqual(bindVars.graphName, "graph"); - assertEqual(bindVars.startVertex_0, "v1/1"); - assertEqual(bindVars.restrictions_0, ["included"]); + assertEqual(bindVars.graphName, graphName); + assertEqual(bindVars.startVertex_0, v1 + "/1"); + assertEqual(bindVars.restrictions_0, [included]); var result = query.toArray(); assertEqual(result.length, 1); assertTrue(findIdInResult(result, e1), "Did not include e1"); @@ -607,19 +606,47 @@ function GeneralGraphAQLQueriesSuite() { assertFalse(findIdInResult(result, e3), "e3 is not excluded"); }, +//////////////////////////////////////////////////////////////////////////////// +/// @brief test: restrict error handling +//////////////////////////////////////////////////////////////////////////////// + + test_restrictErrorHandlingSingle: function() { + try { + g._outEdges(v1 + "/1").restrict([included, "unknown"]); + fail(); + } catch (err) { + assertEqual(err.errorNum, ERRORS.ERROR_BAD_PARAMETER.code); + assertEqual(err.errorMessage, "edge collections: unknown are not known to the graph"); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test: restrict error handling on multiple failures +//////////////////////////////////////////////////////////////////////////////// + + test_restrictErrorHandlingMultiple: function() { + try { + g._outEdges(v1 + "/1").restrict(["failed", included, "unknown", "foxxle"]); + fail(); + } catch (err) { + assertEqual(err.errorNum, ERRORS.ERROR_BAD_PARAMETER.code); + assertEqual(err.errorMessage, "edge collections: failed and unknown and foxxle are not known to the graph"); + } + }, + //////////////////////////////////////////////////////////////////////////////// /// @brief test: filter construct on Edges //////////////////////////////////////////////////////////////////////////////// test_filterOnEdges: function() { - var query = g._edges("v1/1").filter({val: true}); + var query = g._edges(v1 + "/1").filter({val: true}); // var query = g._edges("v1/1").filter("e.val = true"); assertEqual(query.printQuery(), "FOR edges_0 IN GRAPH_EDGES(" + '@graphName,@startVertex_0,"any") ' + 'FILTER MATCHES(edges_0,[{"val":true}])'); var bindVars = query.bindVars; - assertEqual(bindVars.graphName, "graph"); - assertEqual(bindVars.startVertex_0, "v1/1"); + assertEqual(bindVars.graphName, graphName); + assertEqual(bindVars.startVertex_0, v1 + "/1"); var result = query.toArray(); assertEqual(result.length, 1); assertTrue(findIdInResult(result, e1), "Did not include e1"); @@ -632,13 +659,13 @@ function GeneralGraphAQLQueriesSuite() { //////////////////////////////////////////////////////////////////////////////// test_filterOnInEdges: function() { - var query = g._inEdges("v1/1").filter({val: true}); + var query = g._inEdges(v1 + "/1").filter({val: true}); assertEqual(query.printQuery(), "FOR edges_0 IN GRAPH_EDGES(" + '@graphName,@startVertex_0,"inbound") ' + 'FILTER MATCHES(edges_0,[{"val":true}])'); var bindVars = query.bindVars; - assertEqual(bindVars.graphName, "graph"); - assertEqual(bindVars.startVertex_0, "v1/1"); + assertEqual(bindVars.graphName, graphName); + assertEqual(bindVars.startVertex_0, v1 + "/1"); var result = query.toArray(); assertEqual(result.length, 0); assertFalse(findIdInResult(result, e1), "e1 is not excluded"); @@ -651,14 +678,14 @@ function GeneralGraphAQLQueriesSuite() { //////////////////////////////////////////////////////////////////////////////// test_filterOnOutEdges: function() { - var query = g._outEdges("v1/1").filter({val: true}); + var query = g._outEdges(v1 + "/1").filter({val: true}); // var query = g._outEdges("v1/1").filter("e.val = true"); assertEqual(query.printQuery(), "FOR edges_0 IN GRAPH_EDGES(" + '@graphName,@startVertex_0,"outbound") ' + 'FILTER MATCHES(edges_0,[{"val":true}])'); var bindVars = query.bindVars; - assertEqual(bindVars.graphName, "graph"); - assertEqual(bindVars.startVertex_0, "v1/1"); + assertEqual(bindVars.graphName, graphName); + assertEqual(bindVars.startVertex_0, v1 + "/1"); var result = query.toArray(); assertEqual(result.length, 1); assertTrue(findIdInResult(result, e1), "Did not include e1"); @@ -703,15 +730,15 @@ function EdgesAndVerticesSuite() { fillCollections = function() { var ids = {}; - var vertex = g.vertexCollection1.save({first_name: "Tam"}); + var vertex = g.unitTestVertexCollection1.save({first_name: "Tam"}); ids["vId11"] = vertex._id; - vertex = g.vertexCollection1.save({first_name: "Tem"}); + vertex = g.unitTestVertexCollection1.save({first_name: "Tem"}); ids["vId12"] = vertex._id; - vertex = g.vertexCollection1.save({first_name: "Tim"}); + vertex = g.unitTestVertexCollection1.save({first_name: "Tim"}); ids["vId13"] = vertex._id; - vertex = g.vertexCollection1.save({first_name: "Tom"}); + vertex = g.unitTestVertexCollection1.save({first_name: "Tom"}); ids["vId14"] = vertex._id; - vertex = g.vertexCollection1.save({first_name: "Tum"}); + vertex = g.unitTestVertexCollection1.save({first_name: "Tum"}); ids["vId15"] = vertex._id; vertex = g.unitTestVertexCollection3.save({first_name: "Tam"}); ids["vId31"] = vertex._id; @@ -757,11 +784,11 @@ function EdgesAndVerticesSuite() { setUp : function() { try { - arangodb.db._collection("_graphs").remove("_graphs/blubGraph") + arangodb.db._collection("_graphs").remove("_graphs/unitTestGraph") } catch (err) { } g = graph._create( - "blubGraph", + "unitTestGraph", graph.edgeDefinitions( graph._undirectedRelationDefinition("unitTestEdgeCollection1", "unitTestVertexCollection1"), graph._directedRelationDefinition("unitTestEdgeCollection2", @@ -830,6 +857,20 @@ function EdgesAndVerticesSuite() { assertTrue(vertex); }, + test_vC_removeWithEdge : function () { + var vertex1 = g.unitTestVertexCollection1.save({first_name: "Tim"}); + var vertexId1 = vertex1._id; + var vertex2 = g.unitTestVertexCollection1.save({first_name: "Tom"}); + var vertexId2 = vertex2._id; + var edge = g.unitTestEdgeCollection1.save(vertexId1, vertexId2, {}); + var edgeId = edge._id; + var result = g.unitTestVertexCollection1.remove(vertexId1); + assertTrue(result); + assertFalse(db.unitTestEdgeCollection1.exists(edgeId)); + result = g.unitTestVertexCollection1.remove(vertexId2); + assertTrue(result); + }, + test_eC_save_undirected : function() { var vertex1 = g.unitTestVertexCollection1.save({first_name: "Tom"}); var vertexId1 = vertex1._id; @@ -941,8 +982,6 @@ function EdgesAndVerticesSuite() { assertEqual(result._id, ids.vId35); } - - }; } diff --git a/js/server/modules/org/arangodb/ahuacatl.js b/js/server/modules/org/arangodb/ahuacatl.js index 67d0b7bf09..b977499f72 100644 --- a/js/server/modules/org/arangodb/ahuacatl.js +++ b/js/server/modules/org/arangodb/ahuacatl.js @@ -3971,6 +3971,34 @@ function DATE_MILLISECOND (value) { // --SECTION-- graph functions // ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @brief find all paths through a graph, INTERNAL part called recursively +//////////////////////////////////////////////////////////////////////////////// + +function GET_SUB_EDGES (edgeCollections, direction, vertexId) { + + if (!Array.isArray(edgeCollections)) { + edgeCollections = [edgeCollections]; + } + + var result = []; + edgeCollections.forEach(function (edgeCollection) { + if (direction === 1) { + result = result.concat(edgeCollection.outEdges(vertexId)); + } + else if (direction === 2) { + result = result.concat(edgeCollection.inEdges(vertexId)); + } + else if (direction === 3) { + result = result.concat(edgeCollection.edges(vertexId)); + } + }); + return result; + +} + + //////////////////////////////////////////////////////////////////////////////// /// @brief find all paths through a graph, INTERNAL part called recursively //////////////////////////////////////////////////////////////////////////////// @@ -3993,18 +4021,9 @@ function GRAPH_SUBNODES (searchAttributes, vertexId, visited, edges, vertices, l return result; } - - var subEdges; - - if (searchAttributes.direction === 1) { - subEdges = searchAttributes.edgeCollection.outEdges(vertexId); - } - else if (searchAttributes.direction === 2) { - subEdges = searchAttributes.edgeCollection.inEdges(vertexId); - } - else if (searchAttributes.direction === 3) { - subEdges = searchAttributes.edgeCollection.edges(vertexId); - } + var subEdges = GET_SUB_EDGES( + searchAttributes.edgeCollection, searchAttributes.direction, vertexId + ); var i, j, k; for (i = 0; i < subEdges.length; ++i) { @@ -4125,73 +4144,92 @@ function GRAPH_PATHS (vertices, edgeCollection, direction, followCycles, minLeng function GENERAL_GRAPH_PATHS (graphname, direction, followCycles, minLength, maxLength) { "use strict"; - /*var searchDirection; + var searchDirection; direction = direction || "outbound"; followCycles = followCycles || false; minLength = minLength || 0; maxLength = maxLength !== undefined ? maxLength : 10; - var graph = DOCUMENT_HANDLE("_graph" + graphname); + // check graph exists and load edgeDefintions + var graph = DOCUMENT_HANDLE("_graphs/" + graphname); if (!graph) { - THROW(INTERNAL.errors.ERROR_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "GRAPH_PATHS"); + THROW(INTERNAL.errors.ERROR_GRAPH_INVALID_GRAPH, "GRAPH_EDGES"); } - var vertexCollections = []; + var startCollections = [], edgeCollections = []; - // validate arguments - if (direction === "outbound") { - searchDirection = 1; - graph.__edgeDefinitions.forEach(function (def) { - vertexCollections.concat(def.from); - }); - } - else if (direction === "inbound") { - graph.__edgeDefinitions.forEach(function (def) { - vertexCollections.concat(def.to); - }); - searchDirection = 2; - } - else if (direction === "any") { - graph.__edgeDefinitions.forEach(function (def) { - vertexCollections.concat(def.to); - vertexCollections.concat(def.from); - }); - searchDirection = 3; - } - else { - THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "GRAPH_PATHS"); - } + // validate direction and create edgeCollection array. + graph.edgeDefinitions.forEach(function (def) { + if (direction === "outbound") { + searchDirection = 1; + def.from.forEach(function (s) { + if (startCollections.indexOf(s) === -1) { + startCollections.push(s); + } + }); + } + else if (direction === "inbound") { + searchDirection = 2; + def.to.forEach(function (s) { + if (startCollections.indexOf(s) === -1) { + startCollections.push(s); + } + }); + } + else if (direction === "any") { + def.from.forEach(function (s) { + searchDirection = 3; + if (startCollections.indexOf(s) === -1) { + startCollections.push(s); + } + }); + def.to.forEach(function (s) { + if (startCollections.indexOf(s) === -1) { + startCollections.push(s); + } + }); + } + else { + THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "GRAPH_PATHS"); + } + if (edgeCollections.indexOf(def.collection) === -1) { + edgeCollections.push(COLLECTION(def.collection)); + } + + }); if (minLength < 0 || maxLength < 0 || minLength > maxLength) { THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "GRAPH_PATHS"); } - - - var searchAttributes = { - //edgeCollection : COLLECTION(edgeCollection), - minLength : minLength, - maxLength : maxLength, - direction : searchDirection, - followCycles : followCycles - }; - var result = [ ]; - var n = vertices.length, i, j; - for (i = 0; i < n; ++i) { - var vertex = vertices[i]; - var visited = { }; + startCollections.forEach(function (startCollection) { - visited[vertex._id] = true; - //GRAPH_SUBNODES (searchAttributes, vertexId, visited, edges, vertices, level) { - var connected = GRAPH_SUBNODES(searchAttributes, vertex._id, visited, [ ], [ vertex ], 0); - for (j = 0; j < connected.length; ++j) { - result.push(connected[j]); + var searchAttributes = { + edgeCollection : edgeCollections, + minLength : minLength, + maxLength : maxLength, + direction : searchDirection, + followCycles : followCycles + }; + + var vertices = GET_DOCUMENTS(startCollection); + var n = vertices.length, i, j; + for (i = 0; i < n; ++i) { + var vertex = vertices[i]; + var visited = { }; + + visited[vertex._id] = true; + var connected = GRAPH_SUBNODES(searchAttributes, vertex._id, visited, [ ], [ vertex ], 0); + for (j = 0; j < connected.length; ++j) { + result.push(connected[j]); + } } - } - return result;*/ + }); + + return result; } @@ -4291,39 +4329,36 @@ function TRAVERSAL_CHECK_EXAMPLES_TYPEWEIGHTS (examples, func) { }); } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief tranform key to id +//////////////////////////////////////////////////////////////////////////////// + +function TO_ID (vertex, collection) { + "use strict"; + + if (vertex === 'object' && vertex.hasOwnProperty('_id')) { + return vertex._id; + } + + if (vertex.indexOf('/') === -1 && collection) { + return collection + '/' + vertex; + } + return vertex; +} + + //////////////////////////////////////////////////////////////////////////////// /// @brief traverse a graph //////////////////////////////////////////////////////////////////////////////// function TRAVERSAL_FUNC (func, - vertexCollection, - edgeCollection, + datasource, startVertex, endVertex, direction, params) { "use strict"; - - if (startVertex === 'object' && startVertex.hasOwnProperty('_id')) { - startVertex = startVertex._id; - } - - if (startVertex.indexOf('/') === -1) { - startVertex = vertexCollection + '/' + startVertex; - } - - if (endVertex !== undefined) { - if (endVertex === 'object' && endVertex.hasOwnProperty('_id')) { - endVertex = endVertex._id; - } - - if (endVertex.indexOf('/') === -1) { - endVertex = vertexCollection + '/' + endVertex; - } - } - - vertexCollection = COLLECTION(vertexCollection); - edgeCollection = COLLECTION(edgeCollection); if (params === undefined) { params = { }; @@ -4350,7 +4385,7 @@ function TRAVERSAL_FUNC (func, var config = { distance: params.distance, connect: params.connect, - datasource: TRAVERSAL.collectionDatasourceFactory(edgeCollection), + datasource: datasource, trackPaths: params.paths || false, visitor: params.visitor, maxDepth: params.maxDepth, @@ -4453,15 +4488,53 @@ function GRAPH_SHORTEST_PATH (vertexCollection, params.distance = undefined; } - return TRAVERSAL_FUNC("SHORTEST_PATH", - vertexCollection, - edgeCollection, - startVertex, - endVertex, + return TRAVERSAL_FUNC("SHORTEST_PATH", + TRAVERSAL.collectionDatasourceFactory(COLLECTION(edgeCollection)), + TO_ID(startVertex, vertexCollection), + TO_ID(endVertex, vertexCollection), direction, params); } - + +//////////////////////////////////////////////////////////////////////////////// +/// @brief shortest path algorithm +//////////////////////////////////////////////////////////////////////////////// + +function GENERAL_GRAPH_SHORTEST_PATH (graphName, + startVertex, + endVertex, + direction, + params) { + "use strict"; + + if (params === undefined) { + params = { }; + } + + params.strategy = "dijkstra"; + params.itemorder = "forward"; + params.visitor = TRAVERSAL_VISITOR; + + if (typeof params.distance === "string") { + var name = params.distance.toUpperCase(); + + params.distance = function (config, vertex1, vertex2, edge) { + return FCALL_USER(name, [ config, vertex1, vertex2, edge ]); + }; + } + else { + params.distance = undefined; + } + + return TRAVERSAL_FUNC("SHORTEST_PATH", + TRAVERSAL.generalGraphDatasourceFactory(graphName), + TO_ID(startVertex), + TO_ID(endVertex), + direction, + params); +} + + //////////////////////////////////////////////////////////////////////////////// /// @brief traverse a graph //////////////////////////////////////////////////////////////////////////////// @@ -4479,15 +4552,40 @@ function GRAPH_TRAVERSAL (vertexCollection, params.visitor = TRAVERSAL_VISITOR; - return TRAVERSAL_FUNC("TRAVERSAL", - vertexCollection, - edgeCollection, - startVertex, - undefined, + return TRAVERSAL_FUNC("TRAVERSAL", + TRAVERSAL.collectionDatasourceFactory(COLLECTION(edgeCollection)), + TO_ID(startVertex, vertexCollection), + undefined, direction, params); } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief traverse a graph +//////////////////////////////////////////////////////////////////////////////// + +function GENERAL_GRAPH_TRAVERSAL (graphName, + startVertex, + direction, + params) { + "use strict"; + + if (params === undefined) { + params = { }; + } + + params.visitor = TRAVERSAL_VISITOR; + + return TRAVERSAL_FUNC("TRAVERSAL", + TRAVERSAL.generalGraphDatasourceFactory(graphName), + TO_ID(startVertex), + undefined, + direction, + params); +} + + //////////////////////////////////////////////////////////////////////////////// /// @brief traverse a graph and create a hierarchical result /// this function uses the same setup as the TRAVERSE() function but will use @@ -4513,10 +4611,9 @@ function GRAPH_TRAVERSAL_TREE (vertexCollection, params.visitor = TRAVERSAL_TREE_VISITOR; params.connect = connectName; - var result = TRAVERSAL_FUNC("TRAVERSAL_TREE", - vertexCollection, - edgeCollection, - startVertex, + var result = TRAVERSAL_FUNC("TRAVERSAL_TREE", + TRAVERSAL.collectionDatasourceFactory(COLLECTION(edgeCollection)), + TO_ID(startVertex, vertexCollection), undefined, direction, params); @@ -4527,6 +4624,44 @@ function GRAPH_TRAVERSAL_TREE (vertexCollection, return [ result[0][params.connect] ]; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief traverse a graph and create a hierarchical result +/// this function uses the same setup as the TRAVERSE() function but will use +/// a different visitor to create the result +//////////////////////////////////////////////////////////////////////////////// + +function GENERAL_GRAPH_TRAVERSAL_TREE (graphName, + startVertex, + direction, + connectName, + params) { + "use strict"; + + if (connectName === "") { + THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "TRAVERSAL_TREE"); + } + + if (params === undefined) { + params = { }; + } + + params.visitor = TRAVERSAL_TREE_VISITOR; + params.connect = connectName; + + var result = TRAVERSAL_FUNC("TRAVERSAL_TREE", + TRAVERSAL.generalGraphDatasourceFactory(graphName), + TO_ID(startVertex), + undefined, + direction, + params); + + if (result.length === 0) { + return [ ]; + } + return [ result[0][params.connect] ]; +} + + //////////////////////////////////////////////////////////////////////////////// /// @brief return connected edges //////////////////////////////////////////////////////////////////////////////// @@ -4689,7 +4824,54 @@ function GRAPH_NEIGHBORS (vertexCollection, }); return result; -} +} + + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return connected neighbors +//////////////////////////////////////////////////////////////////////////////// + +function GENERAL_GRAPH_NEIGHBORS (graphName, + vertex, + direction, + examples) { + "use strict"; + + + var edges = GENERAL_GRAPH_EDGES(graphName, TO_ID(vertex), direction); + var result = [ ]; + + FILTER(edges, examples).forEach (function (e) { + var key; + + if (direction === "inbound") { + key = e._from; + } + else if (direction === "outbound") { + key = e._to; + } + else if (direction === "any") { + key = e._from; + if (key === vertex) { + key = e._to; + } + } + + if (key === vertex) { + // do not return the start vertex itself + return; + } + + try { + result.push({ edge: CLONE(e), vertex: DOCUMENT_HANDLE(key) }); + } + catch (err) { + } + }); + + return result; +} + // ----------------------------------------------------------------------------- // --SECTION-- MODULE EXPORTS @@ -4796,11 +4978,16 @@ exports.GEO_WITHIN = GEO_WITHIN; exports.FULLTEXT = FULLTEXT; exports.GRAPH_PATHS = GRAPH_PATHS; exports.GRAPH_SHORTEST_PATH = GRAPH_SHORTEST_PATH; +exports.GENERAL_GRAPH_SHORTEST_PATH = GENERAL_GRAPH_SHORTEST_PATH; exports.GRAPH_TRAVERSAL = GRAPH_TRAVERSAL; exports.GRAPH_TRAVERSAL_TREE = GRAPH_TRAVERSAL_TREE; +exports.GENERAL_GRAPH_TRAVERSAL = GENERAL_GRAPH_TRAVERSAL; +exports.GENERAL_GRAPH_TRAVERSAL_TREE = GENERAL_GRAPH_TRAVERSAL_TREE; exports.GRAPH_EDGES = GRAPH_EDGES; exports.GENERAL_GRAPH_EDGES = GENERAL_GRAPH_EDGES; +exports.GENERAL_GRAPH_PATHS = GENERAL_GRAPH_PATHS; exports.GRAPH_NEIGHBORS = GRAPH_NEIGHBORS; +exports.GENERAL_GRAPH_NEIGHBORS = GENERAL_GRAPH_NEIGHBORS; exports.NOT_NULL = NOT_NULL; exports.FIRST_LIST = FIRST_LIST; exports.FIRST_DOCUMENT = FIRST_DOCUMENT; diff --git a/js/server/tests/ahuacatl-general-graph.js b/js/server/tests/ahuacatl-general-graph.js index 3237a0eb68..f43f6c8873 100644 --- a/js/server/tests/ahuacatl-general-graph.js +++ b/js/server/tests/ahuacatl-general-graph.js @@ -113,16 +113,9 @@ function ahuacatlQueryGeneralEdgesTestSuite() { }, //////////////////////////////////////////////////////////////////////////////// -/// @brief checks EDGES() +/// @brief checks GRAPH_EDGES() and GRAPH_NEIGHBOURS() //////////////////////////////////////////////////////////////////////////////// - //graphname, - //startvertex, - //direction, - //edgeexamples, - //collectionRestrictions - - testEdgesAny: function () { var actual; @@ -135,6 +128,9 @@ function ahuacatlQueryGeneralEdgesTestSuite() { actual = getQueryResults("FOR e IN GRAPH_EDGES('bla3', 'UnitTestsAhuacatlVertex1/v1', 'any' , [{'what' : 'v2->v1'}]) SORT e.what RETURN e.what"); assertEqual(actual, [ "v2->v1" ]); + actual = getQueryResults("FOR e IN GRAPH_NEIGHBORS('bla3', 'UnitTestsAhuacatlVertex1/v1', 'any' , [{'what' : 'v2->v1'}]) SORT e.what RETURN e"); + assertEqual(actual[0].edge.what , "v2->v1"); + assertEqual(actual[0].vertex._key , "v2"); }, //////////////////////////////////////////////////////////////////////////////// @@ -152,6 +148,10 @@ function ahuacatlQueryGeneralEdgesTestSuite() { actual = getQueryResults("FOR e IN GRAPH_EDGES('bla3', 'UnitTestsAhuacatlVertex3/v5', 'inbound' , [{'what' : 'v2->v5'}]) SORT e.what RETURN e.what"); assertEqual(actual, [ "v2->v5" ]); + + actual = getQueryResults("FOR e IN GRAPH_NEIGHBORS('bla3', 'UnitTestsAhuacatlVertex3/v5', 'inbound' , [{'what' : 'v2->v5'}]) SORT e.what RETURN e"); + assertEqual(actual[0].edge.what , "v2->v5"); + assertEqual(actual[0].vertex._key , "v2"); }, @@ -170,6 +170,12 @@ function ahuacatlQueryGeneralEdgesTestSuite() { actual = getQueryResults("FOR e IN GRAPH_EDGES('bla3', 'UnitTestsAhuacatlVertex1/v1', 'outbound' , [{'what' : 'v2->v5'}]) SORT e.what RETURN e.what"); assertEqual(actual, []); + + actual = getQueryResults("FOR e IN GRAPH_NEIGHBORS('bla3', 'UnitTestsAhuacatlVertex1/v1', 'outbound') SORT e.what RETURN e"); + assertEqual(actual[0].edge.what , "v1->v2"); + assertEqual(actual[0].vertex._key , "v2"); + assertEqual(actual[1].edge.what , "v1->v5"); + assertEqual(actual[1].vertex._key , "v5"); }, //////////////////////////////////////////////////////////////////////////////// @@ -188,11 +194,351 @@ function ahuacatlQueryGeneralEdgesTestSuite() { } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test suite for GRAPH_PATHS() function +//////////////////////////////////////////////////////////////////////////////// + +function ahuacatlQueryGeneralPathsTestSuite() { + var vertex = null; + var edge = null; + + return { + +//////////////////////////////////////////////////////////////////////////////// +/// @brief set up +//////////////////////////////////////////////////////////////////////////////// + + setUp: function () { + db._drop("UnitTestsAhuacatlVertex1"); + db._drop("UnitTestsAhuacatlVertex2"); + db._drop("UnitTestsAhuacatlVertex3"); + db._drop("UnitTestsAhuacatlVertex4"); + db._drop("UnitTestsAhuacatlEdge1"); + db._drop("UnitTestsAhuacatlEdge2"); + + e1 = "UnitTestsAhuacatlEdge1"; + e2 = "UnitTestsAhuacatlEdge2"; + + vertex1 = db._create("UnitTestsAhuacatlVertex1"); + vertex2 = db._create("UnitTestsAhuacatlVertex2"); + vertex3 = db._create("UnitTestsAhuacatlVertex3"); + vertex4 = db._create("UnitTestsAhuacatlVertex4"); + edge1 = db._createEdgeCollection(e1); + edge2 = db._createEdgeCollection(e2); + + var v1 = vertex1.save({ _key: "v1" }); + var v2 = vertex1.save({ _key: "v2" }); + var v3 = vertex2.save({ _key: "v3" }); + var v4 = vertex2.save({ _key: "v4" }); + var v5 = vertex3.save({ _key: "v5" }); + var v6 = vertex3.save({ _key: "v6" }); + var v7 = vertex4.save({ _key: "v7" }); + + try { + db._collection("_graphs").remove("_graphs/bla3") + } catch (err) { + } + var g = graph._create( + "bla3", + graph.edgeDefinitions( + graph._undirectedRelationDefinition("UnitTestsAhuacatlEdge1", "UnitTestsAhuacatlVertex1"), + graph._directedRelationDefinition("UnitTestsAhuacatlEdge2", + ["UnitTestsAhuacatlVertex1", "UnitTestsAhuacatlVertex2"], + ["UnitTestsAhuacatlVertex3", "UnitTestsAhuacatlVertex4"] + ) + ) + ); + function makeEdge(from, to, collection) { + collection.save(from, to, { what: from.split("/")[1] + "->" + to.split("/")[1] }); + } + makeEdge(v1._id, v2._id, g[e1]); + makeEdge(v2._id, v1._id, g[e1]); + makeEdge(v1._id, v5._id, g[e2]); + makeEdge(v2._id, v5._id, g[e2]); + makeEdge(v4._id, v7._id, g[e2]); + makeEdge(v3._id, v5._id, g[e2]); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief tear down +//////////////////////////////////////////////////////////////////////////////// + + tearDown: function () { + db._drop("UnitTestsAhuacatlVertex1"); + db._drop("UnitTestsAhuacatlVertex2"); + db._drop("UnitTestsAhuacatlVertex3"); + db._drop("UnitTestsAhuacatlVertex4"); + db._drop("UnitTestsAhuacatlEdge1"); + db._drop("UnitTestsAhuacatlEdge2"); + db._collection("_graphs").remove("_graphs/bla3"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief checks GRAPH_PATHS() +//////////////////////////////////////////////////////////////////////////////// + + testPaths: function () { + var actual, result= {}, i = 0, ed; + + actual = getQueryResults("FOR e IN GRAPH_PATHS('bla3') SORT e.source._key,e.destination._key RETURN [e.source._key,e.destination._key,e.edges]"); + actual.forEach(function (p) { + i++; + ed = ""; + p[2].forEach(function (e) { + ed += "|" + e._from.split("/")[1] + "->" + e._to.split("/")[1]; + }); + result[i + ":" + p[0] + p[1]] = ed; + }); + + assertEqual(result["1:v1v1"] , ""); + assertEqual(result["2:v1v2"] , "|v1->v2"); + assertEqual(result["3:v1v5"] , "|v1->v2|v2->v5"); + assertEqual(result["4:v1v5"] , "|v1->v5"); + assertEqual(result["5:v2v1"] , "|v2->v1"); + assertEqual(result["6:v2v2"] , ""); + assertEqual(result["7:v2v5"] , "|v2->v5"); + assertEqual(result["8:v2v5"] , "|v2->v1|v1->v5"); + assertEqual(result["9:v3v3"] , ""); + assertEqual(result["10:v3v5"] , "|v3->v5"); + assertEqual(result["11:v4v4"] , ""); + assertEqual(result["12:v4v7"] , "|v4->v7"); + + + + }, + + testPathsWithDirectionAnyAndMaxLength1: function () { + var actual, result= {}, i = 0, ed; + + actual = getQueryResults("FOR e IN GRAPH_PATHS('bla3', 'any', false , 1 , 1) SORT e.source._key,e.destination._key RETURN [e.source._key,e.destination._key,e.edges]"); + actual.forEach(function (p) { + i++; + ed = ""; + p[2].forEach(function (e) { + ed += "|" + e._from.split("/")[1] + "->" + e._to.split("/")[1]; + }); + result[i + ":" + p[0] + p[1]] = ed; + }); + + assertEqual(result["1:v1v2"] , "|v2->v1"); + assertEqual(result["2:v1v2"] , "|v1->v2"); + assertEqual(result["3:v1v5"] , "|v1->v5"); + assertEqual(result["4:v2v1"] , "|v1->v2"); + assertEqual(result["5:v2v1"] , "|v2->v1"); + assertEqual(result["6:v2v5"] , "|v2->v5"); + assertEqual(result["7:v3v5"] , "|v3->v5"); + assertEqual(result["8:v4v7"] , "|v4->v7"); + assertEqual(result["9:v5v1"] , "|v1->v5"); + assertEqual(result["10:v5v2"] , "|v2->v5"); + assertEqual(result["11:v5v3"] , "|v3->v5"); + assertEqual(result["12:v7v4"] , "|v4->v7"); + + + }, + + testInBoundPaths: function () { + var actual, result= {}, i = 0, ed; + + actual = getQueryResults("FOR e IN GRAPH_PATHS('bla3', 'inbound', false, 1) SORT e.source._key,e.destination._key RETURN [e.source._key,e.destination._key,e.edges]"); + + actual.forEach(function (p) { + i++; + ed = ""; + p[2].forEach(function (e) { + ed += "|" + e._from.split("/")[1] + "->" + e._to.split("/")[1]; + }); + result[i + ":" + p[0] + p[1]] = ed; + }); + + assertEqual(result["1:v1v2"] , "|v2->v1"); + assertEqual(result["2:v2v1"] , "|v1->v2"); + assertEqual(result["3:v5v1"] , "|v1->v5"); + assertEqual(result["4:v5v1"] , "|v2->v5|v1->v2"); + assertEqual(result["5:v5v2"] , "|v1->v5|v2->v1"); + assertEqual(result["6:v5v2"] , "|v2->v5"); + assertEqual(result["7:v5v3"] , "|v3->v5"); + assertEqual(result["8:v7v4"] , "|v4->v7"); + } + + } +} + + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test suite for GRAPH_TRAVERSAL() and GRAPH_SHORTEST_PATH function +//////////////////////////////////////////////////////////////////////////////// + +function ahuacatlQueryGeneralTraversalTestSuite() { + var vertex = null; + var edge = null; + + return { + +//////////////////////////////////////////////////////////////////////////////// +/// @brief set up +//////////////////////////////////////////////////////////////////////////////// + + setUp: function () { + db._drop("UnitTests_Berliner"); + db._drop("UnitTests_Hamburger"); + db._drop("UnitTests_Frankfurter"); + db._drop("UnitTests_Leipziger"); + db._drop("UnitTests_KenntAnderenBerliner"); + db._drop("UnitTests_KenntAnderen"); + + KenntAnderenBerliner = "UnitTests_KenntAnderenBerliner"; + KenntAnderen = "UnitTests_KenntAnderen"; + + Berlin = db._create("UnitTests_Berliner"); + Hamburg = db._create("UnitTests_Hamburger"); + Frankfurt = db._create("UnitTests_Frankfurter"); + Leipzig = db._create("UnitTests_Leipziger"); + db._createEdgeCollection(KenntAnderenBerliner); + db._createEdgeCollection(KenntAnderen); + + var Anton = Berlin.save({ _key: "Anton" , gender : "male"}); + var Berta = Berlin.save({ _key: "Berta" , gender : "female"}); + var Caesar = Hamburg.save({ _key: "Caesar" , gender : "male"}); + var Dieter = Hamburg.save({ _key: "Dieter" , gender : "male"}); + var Emil = Frankfurt.save({ _key: "Emil" , gender : "male"}); + var Fritz = Frankfurt.save({ _key: "Fritz" , gender : "male"}); + var Gerda = Leipzig.save({ _key: "Gerda" , gender : "female"}); + + try { + db._collection("_graphs").remove("_graphs/werKenntWen") + } catch (err) { + } + var g = graph._create( + "werKenntWen", + graph.edgeDefinitions( + graph._undirectedRelationDefinition(KenntAnderenBerliner, "UnitTests_Berliner"), + graph._directedRelationDefinition(KenntAnderen, + ["UnitTests_Hamburger", "UnitTests_Frankfurter", "UnitTests_Berliner", "UnitTests_Leipziger"], + ["UnitTests_Hamburger", "UnitTests_Frankfurter", "UnitTests_Berliner", "UnitTests_Leipziger"] + ) + ) + ); + function makeEdge(from, to, collection) { + collection.save(from, to, { what: from.split("/")[1] + "->" + to.split("/")[1] }); + } + makeEdge(Berta._id, Anton._id, g[KenntAnderenBerliner]); + makeEdge(Caesar._id, Anton._id, g[KenntAnderen]); + makeEdge(Caesar._id, Berta._id, g[KenntAnderen]); + makeEdge(Berta._id, Gerda._id, g[KenntAnderen]); + makeEdge(Gerda._id, Dieter._id, g[KenntAnderen]); + makeEdge(Dieter._id, Emil._id, g[KenntAnderen]); + makeEdge(Emil._id, Fritz._id, g[KenntAnderen]); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief tear down +//////////////////////////////////////////////////////////////////////////////// + + tearDown: function () { + db._drop("UnitTests_Berliner"); + db._drop("UnitTests_Hamburger"); + db._drop("UnitTests_Frankfurter"); + db._drop("UnitTests_Leipziger"); + db._drop("UnitTests_KenntAnderenBerliner"); + db._drop("UnitTests_KenntAnderen"); + db._collection("_graphs").remove("_graphs/werKenntWen"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief checks GRAPH_TRAVERSAL() +//////////////////////////////////////////////////////////////////////////////// + + testGRAPH_TRAVERSALs: function () { + var actual, result= []; + + actual = getQueryResults("FOR e IN GRAPH_TRAVERSAL('werKenntWen', 'UnitTests_Hamburger/Caesar', 'outbound') RETURN e"); + //require("internal").print(actual); + actual.forEach(function (s) { + result.push(s.vertex._key); + }); + //require("internal").print(result) + assertEqual(result, [ + "Caesar", + "Anton", + "Berta", + "Anton", + "Gerda", + "Dieter", + "Emil", + "Fritz" + ]); + }, + + testGENERAL_GRAPH_TRAVERSAL_TREE: function () { + var actual, start, middle; + + actual = getQueryResults("FOR e IN GRAPH_TRAVERSAL_TREE('werKenntWen', 'UnitTests_Hamburger/Caesar', 'outbound', 'connected') RETURN e"); + start = actual[0][0]; + + assertEqual(start._key, "Caesar"); + assertTrue(start.hasOwnProperty("connected")); + assertTrue(start.connected.length === 2); + assertEqual(start.connected[0]._key, "Anton"); + assertEqual(start.connected[1]._key, "Berta"); + + assertTrue(!start.connected[0].hasOwnProperty("connected")); + assertTrue(start.connected[1].hasOwnProperty("connected")); + + middle = start.connected[1]; + + assertTrue(middle.connected.length === 2); + assertEqual(middle.connected[0]._key, "Anton"); + assertEqual(middle.connected[1]._key, "Gerda"); + + assertTrue(!middle.connected[0].hasOwnProperty("connected")); + assertTrue(middle.connected[1].hasOwnProperty("connected")); + + middle = middle.connected[1]; + assertTrue(middle.connected.length === 1); + assertEqual(middle.connected[0]._key, "Dieter"); + + middle = middle.connected[0]; + + assertTrue(middle.connected.length === 1); + assertEqual(middle.connected[0]._key, "Emil"); + + middle = middle.connected[0]; + assertTrue(middle.connected.length === 1); + assertEqual(middle.connected[0]._key, "Fritz"); + + }, + + testGRAPH_SHORTEST_PATH: function () { + var actual, result= []; + + actual = getQueryResults("FOR e IN GRAPH_SHORTEST_PATH('werKenntWen', 'UnitTests_Hamburger/Caesar', 'UnitTests_Frankfurter/Emil', 'outbound') RETURN e"); + actual.forEach(function (s) { + result.push(s.vertex._key); + }); + assertEqual(result, [ + "Caesar", + "Berta", + "Gerda", + "Dieter", + "Emil" + ]); + + } + } +} + + + + + //////////////////////////////////////////////////////////////////////////////// /// @brief executes the test suite //////////////////////////////////////////////////////////////////////////////// +jsunity.run(ahuacatlQueryGeneralTraversalTestSuite); jsunity.run(ahuacatlQueryGeneralEdgesTestSuite); +jsunity.run(ahuacatlQueryGeneralPathsTestSuite); return jsunity.done(); diff --git a/lib/ShapedJson/Legends.cpp b/lib/ShapedJson/Legends.cpp index c0e021ad8f..9e57941291 100644 --- a/lib/ShapedJson/Legends.cpp +++ b/lib/ShapedJson/Legends.cpp @@ -188,11 +188,11 @@ int JsonLegend::addShape (TRI_shape_sid_t sid, return res; } -static inline TRI_shape_size_t roundup8(TRI_shape_size_t x) { +static inline TRI_shape_size_t roundup8 (TRI_shape_size_t x) { return (x + 7) - ((x + 7) & 7); } -size_t JsonLegend::getSize () { +size_t JsonLegend::getSize () const { // Add string pool size and shape pool size and add space for header // and tables in bytes. return sizeof(TRI_shape_size_t) // number of aids diff --git a/lib/ShapedJson/Legends.h b/lib/ShapedJson/Legends.h index 5b2b68796f..0161a10c22 100644 --- a/lib/ShapedJson/Legends.h +++ b/lib/ShapedJson/Legends.h @@ -73,7 +73,7 @@ namespace triagens { int addShape (TRI_shape_sid_t sid, char const* data, uint32_t len); - size_t getSize (); + size_t getSize () const; void dump (void* buf); diff --git a/lib/Utilities/ShellImplFactory.cpp b/lib/Utilities/ShellImplFactory.cpp index cea37c77b1..ee9b764ef9 100644 --- a/lib/Utilities/ShellImplFactory.cpp +++ b/lib/Utilities/ShellImplFactory.cpp @@ -41,11 +41,10 @@ using namespace triagens; using namespace std; -ShellImplementation * ShellImplFactory::buildShell(string const & history, Completer * completer) { - +ShellImplementation * ShellImplFactory::buildShell (string const & history, Completer * completer) { #ifdef _WIN32 - //under windows the realine is not compilable + //under windows the readline is not compilable return new LinenoiseShell(history, completer); #elif defined TRI_HAVE_LINENOISE return new LinenoiseShell(history, completer);