1
0
Fork 0
arangodb/js/common/modules/@arangodb/arango-collection-common.js

612 lines
19 KiB
JavaScript

/*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(<lat>, <lon>) or ensureGeoIndex(<loc>[, <geoJson>[, <pointsOnly>]])';
}
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);
};