/*jshint strict: false, unused: false, maxlen: 200 */ // ////////////////////////////////////////////////////////////////////////////// // / @brief ArangoCollection // / // / @file // / // / DISCLAIMER // / // / Copyright 2011-2013 triagens GmbH, Cologne, Germany // / // / Licensed under the Apache License, Version 2.0 (the "License") // / you may not use this file except in compliance with the License. // / You may obtain a copy of the License at // / // / http://www.apache.org/licenses/LICENSE-2.0 // / // / Unless required by applicable law or agreed to in writing, software // / distributed under the License is distributed on an "AS IS" BASIS, // / WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // / See the License for the specific language governing permissions and // / limitations under the License. // / // / Copyright holder is triAGENS GmbH, Cologne, Germany // / // / @author Dr. Frank Celler // / @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany // ////////////////////////////////////////////////////////////////////////////// var ArangoCollection = require('@arangodb/arango-collection').ArangoCollection; var arangodb = require('@arangodb'); var ArangoError = arangodb.ArangoError; var sprintf = arangodb.sprintf; var db = arangodb.db; var simple = require('@arangodb/simple-query'); var SimpleQueryAll = simple.SimpleQueryAll; var SimpleQueryByExample = simple.SimpleQueryByExample; var SimpleQueryByCondition = simple.SimpleQueryByCondition; var SimpleQueryRange = simple.SimpleQueryRange; var SimpleQueryGeo = simple.SimpleQueryGeo; var SimpleQueryNear = simple.SimpleQueryNear; var SimpleQueryWithin = simple.SimpleQueryWithin; var SimpleQueryWithinRectangle = simple.SimpleQueryWithinRectangle; var SimpleQueryFulltext = simple.SimpleQueryFulltext; // ////////////////////////////////////////////////////////////////////////////// // / @brief collection is corrupted // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.STATUS_CORRUPTED = 0; // ////////////////////////////////////////////////////////////////////////////// // / @brief collection is new born // / @deprecated // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.STATUS_NEW_BORN = 1; // ////////////////////////////////////////////////////////////////////////////// // / @brief collection is unloaded // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.STATUS_UNLOADED = 2; // ////////////////////////////////////////////////////////////////////////////// // / @brief collection is loaded // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.STATUS_LOADED = 3; // ////////////////////////////////////////////////////////////////////////////// // / @brief collection is unloading // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.STATUS_UNLOADING = 4; // ////////////////////////////////////////////////////////////////////////////// // / @brief collection is deleted // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.STATUS_DELETED = 5; // ////////////////////////////////////////////////////////////////////////////// // / @brief collection is currently loading // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.STATUS_LOADING = 6; // ////////////////////////////////////////////////////////////////////////////// // / @brief document collection // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.TYPE_DOCUMENT = 2; // ////////////////////////////////////////////////////////////////////////////// // / @brief edge collection // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.TYPE_EDGE = 3; ArangoCollection.prototype.isArangoCollection = true; // ////////////////////////////////////////////////////////////////////////////// // / @brief prints a collection // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype._PRINT = function (context) { var status = 'unknown'; var type = 'unknown'; var name = this.name(); switch (this.status()) { case ArangoCollection.STATUS_NEW_BORN: status = 'new born'; break; case ArangoCollection.STATUS_UNLOADED: status = 'unloaded'; break; case ArangoCollection.STATUS_UNLOADING: status = 'unloading'; break; case ArangoCollection.STATUS_LOADED: status = 'loaded'; break; case ArangoCollection.STATUS_CORRUPTED: status = 'corrupted'; break; case ArangoCollection.STATUS_DELETED: status = 'deleted'; break; } switch (this.type()) { case ArangoCollection.TYPE_DOCUMENT: type = 'document'; break; case ArangoCollection.TYPE_EDGE: type = 'edge'; break; } var colors = require('internal').COLORS; var useColor = context.useColor; context.output += '[ArangoCollection '; if (useColor) { context.output += colors.COLOR_NUMBER; } context.output += this._id; if (useColor) { context.output += colors.COLOR_RESET; } context.output += ', "'; if (useColor) { context.output += colors.COLOR_STRING; } context.output += name || 'unknown'; if (useColor) { context.output += colors.COLOR_RESET; } context.output += '" (type ' + type + ', status ' + status + ')]'; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief converts into a string // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.toString = function () { return '[ArangoCollection: ' + this._id + ']'; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief qualifies a given document key // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.documentId = function (documentKey) { if (documentKey && typeof documentKey !== "string") { documentKey = String(documentKey); } if (!arangodb.isValidDocumentKey(documentKey)) { throw new ArangoError({ errorNum: arangodb.errors.ERROR_ARANGO_DOCUMENT_KEY_BAD.code, errorMessage: arangodb.errors.ERROR_ARANGO_DOCUMENT_KEY_BAD.message }); } return `${this.name()}/${documentKey}`; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock collectionAll // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.all = function () { return new SimpleQueryAll(this); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock collectionByExample // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.byExample = function (example) { var e; var i; // example is given as only argument if (arguments.length === 1) { e = example; } // example is given as list else { e = {}; // create a REAL array, otherwise JSON.stringify will fail for (i = 0; i < arguments.length; i += 2) { e[arguments[i]] = arguments[i + 1]; } } return new SimpleQueryByExample(this, e); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock collectionRange // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.range = function (name, left, right) { return new SimpleQueryRange(this, name, left, right, 0); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock collectionClosedRange // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.closedRange = function (name, left, right) { return new SimpleQueryRange(this, name, left, right, 1); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock collectionGeo // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.geo = function (loc, order) { var idx; var locateGeoIndex1 = function (collection, loc, order) { var inds = collection.getIndexes(); var i; for (i = 0; i < inds.length; ++i) { var index = inds[i]; if (index.type === 'geo1' || (index.type === 'geo' && index.fields.length === 1)) { if (index.fields[0] === loc && index.geoJson === order) { return index; } } } return null; }; var locateGeoIndex2 = function (collection, lat, lon) { var inds = collection.getIndexes(); var i; for (i = 0; i < inds.length; ++i) { var index = inds[i]; if (index.type === 'geo2' || (index.type === 'geo' && index.fields.length === 2)) { if (index.fields[0] === lat && index.fields[1] === lon) { return index; } } } return null; }; if (order === undefined) { if (typeof loc === 'object') { idx = this.index(loc); }else { idx = locateGeoIndex1(this, loc, false); } } else if (typeof order === 'boolean') { idx = locateGeoIndex1(this, loc, order); }else { idx = locateGeoIndex2(this, loc, order); } if (idx === null) { var err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_QUERY_GEO_INDEX_MISSING.code; err.errorMessage = require('internal').sprintf(arangodb.errors.ERROR_QUERY_GEO_INDEX_MISSING.message, this.name()); throw err; } return new SimpleQueryGeo(this, idx.id); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock collectionNear // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.near = function (lat, lon) { return new SimpleQueryNear(this, lat, lon); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock collectionWithin // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.within = function (lat, lon, radius) { return new SimpleQueryWithin(this, lat, lon, radius); }; ArangoCollection.prototype.withinRectangle = function (lat1, lon1, lat2, lon2) { return new SimpleQueryWithinRectangle(this, lat1, lon1, lat2, lon2); }; ArangoCollection.prototype.fulltext = function (attribute, query, iid) { return new SimpleQueryFulltext(this, attribute, query, iid); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock collectionIterate // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.iterate = function (iterator, options) { var probability = 1.0; var limit = null; var stmt; var cursor; var pos; // TODO: this is not optimal for the client, there should be an HTTP call handling // everything on the server if (options !== undefined) { if (options.hasOwnProperty('probability')) { probability = options.probability; } if (options.hasOwnProperty('limit')) { limit = options.limit; } } if (limit === null) { if (probability >= 1.0) { cursor = this.all(); }else { stmt = sprintf('FOR d IN %s FILTER rand() >= @prob RETURN d', this.name()); stmt = db._createStatement({ query: stmt }); if (probability < 1.0) { stmt.bind('prob', probability); } cursor = stmt.execute(); } }else { if (typeof limit !== 'number') { var error = new ArangoError(); error.errorNum = arangodb.errors.ERROR_ILLEGAL_NUMBER.code; error.errorMessage = 'expecting a number, got ' + String(limit); throw error; } if (probability >= 1.0) { cursor = this.all().limit(limit); }else { stmt = sprintf('FOR d IN %s FILTER rand() >= @prob LIMIT %d RETURN d', this.name(), limit); stmt = db._createStatement({ query: stmt }); if (probability < 1.0) { stmt.bind('prob', probability); } cursor = stmt.execute(); } } pos = 0; while (cursor.hasNext()) { var document = cursor.next(); iterator(document, pos); pos++; } }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock documentsCollectionRemoveByExample // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.removeByExample = function (example, waitForSync, limit) { throw 'cannot call abstract removeByExample function'; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock documentsCollectionReplaceByExample // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.replaceByExample = function (example, newValue, waitForSync, limit) { throw 'cannot call abstract replaceByExample function'; }; // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock documentsCollectionUpdateByExample // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.updateByExample = function (example, newValue, keepNull, waitForSync, limit) { throw 'cannot call abstract updateExample function'; }; // ////////////////////////////////////////////////////////////////////////////// // / SECTION Indexes // ////////////////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////////////////// // / @brief add options from arguments to index specification // ////////////////////////////////////////////////////////////////////////////// function addIndexOptions (body, parameters) { body.fields = []; var setOption = function (k) { if (! body.hasOwnProperty(k)) { body[k] = parameters[i][k]; } }; var i; for (i = 0; i < parameters.length; ++i) { if (typeof parameters[i] === 'string') { // set fields body.fields.push(parameters[i]); } else if (typeof parameters[i] === 'object' && ! Array.isArray(parameters[i]) && parameters[i] !== null) { // set arbitrary options Object.keys(parameters[i]).forEach(setOption); break; } } return body; } // ////////////////////////////////////////////////////////////////////////////// // / @brief ensures a hash index // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.ensureHashIndex = function () { 'use strict'; return this.ensureIndex(addIndexOptions({ type: 'hash', }, arguments)); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief ensures a unique constraint // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.ensureUniqueConstraint = function () { 'use strict'; return this.ensureIndex(addIndexOptions({ type: 'hash', unique: true }, arguments)); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief ensures a unique skip-list index // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.ensureUniqueSkiplist = function () { 'use strict'; return this.ensureIndex(addIndexOptions({ type: 'skiplist', unique: true }, arguments)); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief ensures a skip-list index // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.ensureSkiplist = function () { 'use strict'; return this.ensureIndex(addIndexOptions({ type: 'skiplist', unique: false }, arguments)); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief ensures a fulltext index // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.ensureFulltextIndex = function (field, minLength) { 'use strict'; if (! Array.isArray(field)) { field = [ field ]; } return this.ensureIndex({ type: 'fulltext', minLength: minLength || undefined, fields: field }); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief ensures a geo index // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.ensureGeoIndex = function (lat, lon) { 'use strict'; if (typeof lat !== 'string') { throw 'usage: ensureGeoIndex(, ) or ensureGeoIndex([, [, ]])'; } if (typeof lon === 'boolean') { return this.ensureIndex({ type: 'geo1', fields: [ lat ], geoJson: lon }); } if (lon === undefined) { return this.ensureIndex({ type: 'geo1', fields: [ lat ], geoJson: false }); } return this.ensureIndex({ type: 'geo2', fields: [ lat, lon ] }); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief ensures a geo constraint // / since ArangoDB 2.5, this is just a redirection to ensureGeoIndex // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.ensureGeoConstraint = function (lat, lon) { return this.ensureGeoIndex(lat, lon); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief ensures a vertex-centric index // ////////////////////////////////////////////////////////////////////////////// ArangoCollection.prototype.ensureVertexCentricIndex = function (...fields) { let options = fields.pop(); if (!typeof options === 'object' || Array.isArray(options)) { let err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code; err.errorMessage = 'usage: ensureVertexCentricIndex(<...fields>, options), options has to be an object'; throw err; } let dir = options.direction; if (typeof dir === 'string') { dir = dir.toLowerCase(); } if (fields.length === 0 && options.hasOwnProperty("fields") && Array.isArray(options.fields)) { // This copies the content of the array. fields = options.fields.slice(0); } if (fields.length === 0) { let err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code; err.errorMessage = 'usage: ensureVertexCentricIndex(<...fields>, options), we need a non empty list of fields'; throw err; } switch (dir) { case "outbound": fields.unshift("_from"); break; case "inbound": fields.unshift("_to"); break; default: let err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code; err.errorMessage = 'usage: ensureVertexCentricIndex(<...fields>, options), options.direction has to be "inbound" or "outbound"'; throw err; } options.fields = fields; if (!options.hasOwnProperty('type')) { options.type = 'hash'; } return this.ensureIndex(options); };