1
0
Fork 0
arangodb/js/server/modules/@arangodb/aql.js

8277 lines
234 KiB
JavaScript

/*jshint strict: false, unused: false, bitwise: false, esnext: true */
/*global COMPARE_STRING, AQL_TO_BOOL, AQL_TO_NUMBER, AQL_TO_STRING, AQL_WARNING, AQL_QUERY_SLEEP */
/*global CPP_SHORTEST_PATH, CPP_NEIGHBORS, Set */
////////////////////////////////////////////////////////////////////////////////
/// @brief Aql, internal query functions
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2012 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 Jan Steemann
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var INTERNAL = require("internal");
var TRAVERSAL = require("@arangodb/graph/traversal");
var ArangoError = require("@arangodb").ArangoError;
var isCoordinator = require("@arangodb/cluster").isCoordinator();
var underscore = require("lodash");
var graphModule = require("@arangodb/general-graph");
////////////////////////////////////////////////////////////////////////////////
/// @brief cache for compiled regexes
////////////////////////////////////////////////////////////////////////////////
var RegexCache = { };
////////////////////////////////////////////////////////////////////////////////
/// @brief user functions cache
////////////////////////////////////////////////////////////////////////////////
var UserFunctions = { };
////////////////////////////////////////////////////////////////////////////////
/// @brief prefab traversal visitors
////////////////////////////////////////////////////////////////////////////////
var DefaultVisitors = {
"_AQL::HASATTRIBUTESVISITOR" : {
visitorReturnsResults: true,
func: function (config, result, vertex, path) {
if (typeof config.data === "object" && Array.isArray(config.data.attributes)) {
if (config.data.attributes.length === 0) {
return;
}
var allowNull = true; // default value
if (config.data.hasOwnProperty('allowNull')) {
allowNull = config.data.allowNull;
}
var i;
if (config.data.type === 'any') {
for (i = 0; i < config.data.attributes.length; ++i) {
if (! vertex.hasOwnProperty(config.data.attributes[i])) {
continue;
}
if (! allowNull && vertex[config.data.attributes[i]] === null) {
continue;
}
return CLONE({ vertex: vertex, path: path });
}
return;
}
for (i = 0; i < config.data.attributes.length; ++i) {
if (! vertex.hasOwnProperty(config.data.attributes[i])) {
return;
}
if (! allowNull &&
vertex[config.data.attributes[i]] === null) {
return;
}
}
return CLONE({ vertex: vertex, path: path });
}
}
},
"_AQL::PROJECTINGVISITOR" : {
visitorReturnsResults: true,
func: function (config, result, vertex) {
var values = { };
if (typeof config.data === "object" && Array.isArray(config.data.attributes)) {
config.data.attributes.forEach(function (attribute) {
values[attribute] = vertex[attribute];
});
}
return values;
}
},
"_AQL::IDVISITOR" : {
visitorReturnsResults: true,
func: function (config, result, vertex) {
return vertex._id;
}
},
"_AQL::KEYVISITOR" : {
visitorReturnsResults: true,
func: function (config, result, vertex) {
return vertex._key;
}
},
"_AQL::COUNTINGVISITOR" : {
visitorReturnsResults: false,
func: function (config, result) {
if (result.length === 0) {
result.push(0);
}
result[0]++;
}
}
};
////////////////////////////////////////////////////////////////////////////////
/// @brief type weight used for sorting and comparing
////////////////////////////////////////////////////////////////////////////////
var TYPEWEIGHT_NULL = 0;
var TYPEWEIGHT_BOOL = 1;
var TYPEWEIGHT_NUMBER = 2;
var TYPEWEIGHT_STRING = 4;
var TYPEWEIGHT_ARRAY = 8;
var TYPEWEIGHT_OBJECT = 16;
////////////////////////////////////////////////////////////////////////////////
/// @brief mapping of time unit names to short name, getter and setter
////////////////////////////////////////////////////////////////////////////////
var unitMapping = {
y: ["y", "getUTCFullYear", "setUTCFullYear"],
m: ["m", "getUTCMonth", "setUTCMonth"],
w: ["w", null, null],
d: ["d", "getUTCDate", "setUTCDate"],
h: ["h", "getUTCHours", "setUTCHours"],
i: ["i", "getUTCMinutes", "setUTCMinutes"],
s: ["s", "getUTCSeconds", "setUTCSeconds"],
f: ["f", "getUTCMilliseconds", "setUTCMilliseconds"]
};
// aliases
unitMapping.years = unitMapping.y;
unitMapping.year = unitMapping.y;
unitMapping.months = unitMapping.m;
unitMapping.month = unitMapping.m;
unitMapping.week = unitMapping.w;
unitMapping.weeks = unitMapping.w;
unitMapping.days = unitMapping.d;
unitMapping.day = unitMapping.d;
unitMapping.hours = unitMapping.h;
unitMapping.hour = unitMapping.h;
unitMapping.minutes = unitMapping.i;
unitMapping.minute = unitMapping.i;
unitMapping.seconds = unitMapping.s;
unitMapping.second = unitMapping.s;
unitMapping.milliseconds = unitMapping.f;
unitMapping.millisecond = unitMapping.f;
unitMapping.ms = unitMapping.f;
var unitMappingArray = [null, "y", "m", "w", "d", "h", "i", "s", "f"];
////////////////////////////////////////////////////////////////////////////////
/// @brief RegExp and cache for ISO duration strings
////////////////////////////////////////////////////////////////////////////////
// ISODurationRegex.exec("P1Y2M3W4DT5H6M7.890S")
// -> ["P1Y2M3W4DT5H6M7.890S", "1", "2", "3", "4", "5", "6", "7", "890"]
/* jshint -W101 */
var ISODurationRegex = /^P(?:(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)W)?(?:(\d+)D)?)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)(?:\.(\d+))?S)?)?$/i;
/* jshint +W101 */
var ISODurationCache = {};
////////////////////////////////////////////////////////////////////////////////
/// @brief substring ranges for DATE_COMPARE()
////////////////////////////////////////////////////////////////////////////////
// 0123_56_89_12_45_78_012_
var unitStrRanges = {
y: [0, 4],
m: [5, 7],
d: [8, 10],
h: [11, 13],
i: [14, 16],
s: [17, 19],
f: [20, 23]
};
unitStrRanges.years = unitStrRanges.y;
unitStrRanges.year = unitStrRanges.y;
unitStrRanges.months = unitStrRanges.m;
unitStrRanges.month = unitStrRanges.m;
unitStrRanges.days = unitStrRanges.d;
unitStrRanges.day = unitStrRanges.d;
unitStrRanges.hours = unitStrRanges.h;
unitStrRanges.hour = unitStrRanges.h;
unitStrRanges.minutes = unitStrRanges.i;
unitStrRanges.minute = unitStrRanges.i;
unitStrRanges.seconds = unitStrRanges.s;
unitStrRanges.second = unitStrRanges.s;
unitStrRanges.milliseconds = unitStrRanges.f;
unitStrRanges.millisecond = unitStrRanges.f;
unitStrRanges.ms = unitStrRanges.f;
////////////////////////////////////////////////////////////////////////////////
/// @brief offsets for day of year calculation
////////////////////////////////////////////////////////////////////////////////
var dayOfYearOffsets = [
0,
31, // + 31 Jan
59, // + 28 Feb*
90, // + 31 Mar
120, // + 30 Apr
151, // + 31 May
181, // + 30 Jun
212, // + 31 Jul
243, // + 31 Aug
273, // + 30 Sep
304, // + 31 Oct
334 // + 30 Nov
];
var dayOfLeapYearOffsets = [
0,
31, // + 31 Jan
59, // + 29 Feb*
91, // + 31 Mar
121, // + 30 Apr
152, // + 31 May
182, // + 30 Jun
213, // + 31 Jul
244, // + 31 Aug
274, // + 30 Sep
305, // + 31 Oct
335 // + 30 Nov
];
////////////////////////////////////////////////////////////////////////////////
/// @brief lookup array for days in month calculation (leap year aware)
////////////////////////////////////////////////////////////////////////////////
var daysInMonth = [
29, // Feb (in leap year)
31, // Jan
28, // Feb (in non-leap year)
31, // Mar
30, // Apr
31, // May
30, // Jun
31, // Jul
31, // Aug
30, // Sep
31, // Oct
30, // Nov
31 // Dec
];
////////////////////////////////////////////////////////////////////////////////
/// @brief English month names (1-based)
////////////////////////////////////////////////////////////////////////////////
var monthNames = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
];
////////////////////////////////////////////////////////////////////////////////
/// @brief English weekday names
////////////////////////////////////////////////////////////////////////////////
var weekdayNames = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
];
////////////////////////////////////////////////////////////////////////////////
/// @brief constants for date difference function
////////////////////////////////////////////////////////////////////////////////
// milliseconds per month, counting February as 28 days (compensating later)
var msPerMonth = [
26784e5, 24192e5, 26784e5, 2592e6, 26784e5, 2592e6,
26784e5, 26784e5, 2592e6, 26784e5, 2592e6, 26784e5
];
var msPerUnit = {
f: 1,
s: 1e3, // 1000
i: 6e4, // 1000 * 60
h: 36e5, // 1000 * 60 * 60
d: 864e5, // 1000 * 60 * 60 * 24
w: 6048e5, // 1000 * 60 * 60 * 24 * 7
m: 0, // evaluates to false
y: -1 // evaluates to true to distinguish it from months
};
// Aliases
msPerUnit.milliseconds = msPerUnit.f;
msPerUnit.millisecond = msPerUnit.f;
msPerUnit.ms = msPerUnit.f;
msPerUnit.seconds = msPerUnit.s;
msPerUnit.second = msPerUnit.s;
msPerUnit.minutes = msPerUnit.i;
msPerUnit.minute = msPerUnit.i;
msPerUnit.hours = msPerUnit.h;
msPerUnit.hour = msPerUnit.h;
msPerUnit.days = msPerUnit.d;
msPerUnit.day = msPerUnit.d;
msPerUnit.weeks = msPerUnit.w;
msPerUnit.week = msPerUnit.w;
msPerUnit.months = msPerUnit.m;
msPerUnit.month = msPerUnit.m;
msPerUnit.years = msPerUnit.y;
msPerUnit.year = msPerUnit.y;
////////////////////////////////////////////////////////////////////////////////
/// @brief clear caches
////////////////////////////////////////////////////////////////////////////////
function clearCaches () {
'use strict';
RegexCache = { 'i' : { }, '' : { } };
ISODurationCache = { };
}
////////////////////////////////////////////////////////////////////////////////
/// @brief add zeros for a total length of width chars (left padding by default)
////////////////////////////////////////////////////////////////////////////////
function zeropad (n, width, padRight) {
'use strict';
padRight = padRight || false;
n = "" + n;
if (padRight) {
return n.length >= width ? n : n + new Array(width - n.length + 1).join("0");
} else {
return n.length >= width ? n : new Array(width - n.length + 1).join("0") + n;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief raise a warning
////////////////////////////////////////////////////////////////////////////////
function WARN (func, error, data) {
'use strict';
if (error.code === INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code) {
AQL_WARNING(error.code, error.message.replace(/%s/, func));
}
else {
var prefix = "";
if (func !== null) {
prefix = "in function '" + func + "()': ";
}
if (typeof data === "string") {
AQL_WARNING(error.code, prefix + error.message.replace(/%s/, data));
}
else {
AQL_WARNING(error.code, prefix + error.message);
}
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief throw a runtime exception
////////////////////////////////////////////////////////////////////////////////
function THROW (func, error, data, moreMessage) {
'use strict';
var prefix = "";
if (func !== null && func !== "") {
prefix = "in function '" + func + "()': ";
}
var err = new ArangoError();
err.errorNum = error.code;
if (typeof data === "string") {
err.errorMessage = prefix + error.message.replace(/%s/, data);
}
else {
err.errorMessage = prefix + error.message;
}
if (moreMessage !== undefined) {
err.errorMessage += "; " + moreMessage;
}
throw err;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return a database-specific function prefix
////////////////////////////////////////////////////////////////////////////////
function DB_PREFIX () {
return INTERNAL.db._name();
}
////////////////////////////////////////////////////////////////////////////////
/// @brief reset the user functions and reload them from the database
////////////////////////////////////////////////////////////////////////////////
function reloadUserFunctions () {
'use strict';
var prefix = DB_PREFIX();
var c = INTERNAL.db._collection("_aqlfunctions");
if (c === null) {
// collection not found. now reset all user functions
UserFunctions = { };
UserFunctions[prefix] = { };
return;
}
var foundError = false;
var functions = { };
c.toArray().forEach(function (f) {
var key = f._key.replace(/:{1,}/g, '::');
var code;
if (f.code.match(/^\(?function\s+\(/)) {
code = f.code;
}
else {
code = "(function() { var callback = " + f.code + ";\n return callback; })();";
}
try {
var res = INTERNAL.executeScript(code, undefined, "(user function " + key + ")");
if (typeof res !== "function") {
foundError = true;
}
functions[key.toUpperCase()] = {
name: key,
func: res,
isDeterministic: f.isDeterministic || false
};
}
catch (err) {
// in case a single function is broken, we still continue with the other ones
// so that at least some functions remain usable
foundError = true;
}
});
// now reset the functions for all databases
// this ensures that functions of other databases will be reloaded next
// time (the reload does not necessarily need to be carried out in the
// database in which the function is registered)
UserFunctions = { };
UserFunctions[prefix] = functions;
if (foundError) {
THROW(null, INTERNAL.errors.ERROR_QUERY_FUNCTION_INVALID_CODE);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get a user-function by name
////////////////////////////////////////////////////////////////////////////////
function GET_USERFUNCTION (name, config) {
var prefix = DB_PREFIX(), reloaded = false;
var key = name.toUpperCase();
var func;
if (DefaultVisitors.hasOwnProperty(key)) {
var visitor = DefaultVisitors[key];
func = visitor.func;
config.visitorReturnsResults = visitor.visitorReturnsResults;
}
else {
if (! UserFunctions.hasOwnProperty(prefix)) {
reloadUserFunctions();
reloaded = true;
}
if (! UserFunctions[prefix].hasOwnProperty(key) && ! reloaded) {
// last chance
reloadUserFunctions();
}
if (! UserFunctions[prefix].hasOwnProperty(key)) {
THROW(null, INTERNAL.errors.ERROR_QUERY_FUNCTION_NOT_FOUND, name);
}
func = UserFunctions[prefix][key].func;
}
if (typeof func !== "function") {
THROW(null, INTERNAL.errors.ERROR_QUERY_FUNCTION_NOT_FOUND, name);
}
return func;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create a user-defined visitor from a function name
////////////////////////////////////////////////////////////////////////////////
function GET_VISITOR (name, config) {
var func = GET_USERFUNCTION(name, config);
return function (config, result, vertex, path) {
try {
if (config.visitorReturnsResults) {
var r = func.apply(null, arguments);
if (r !== undefined && r !== null) {
result.push(CLONE(FIX_VALUE(r)));
}
}
else {
func.apply(null, arguments);
}
}
catch (err) {
WARN(name, INTERNAL.errors.ERROR_QUERY_FUNCTION_RUNTIME_ERROR, AQL_TO_STRING(err));
}
};
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create a user-defined filter from a function name
////////////////////////////////////////////////////////////////////////////////
function GET_FILTER (name, config) {
var func = GET_USERFUNCTION(name, config);
return function (config, vertex, path) {
try {
var filterResult = func.apply(null, arguments);
return FIX_VALUE(filterResult);
}
catch (err) {
WARN(name, INTERNAL.errors.ERROR_QUERY_FUNCTION_RUNTIME_ERROR, AQL_TO_STRING(err));
}
};
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create a user-defined expand filter from a function name
////////////////////////////////////////////////////////////////////////////////
function GET_EXPANDFILTER (name, config) {
var func = GET_USERFUNCTION(name, config);
return function (config, vertex, edge, path) {
try {
var filterResult = func.apply(null, arguments);
return FIX_VALUE(filterResult);
}
catch (err) {
WARN(name, INTERNAL.errors.ERROR_QUERY_FUNCTION_RUNTIME_ERROR, AQL_TO_STRING(err));
}
};
}
////////////////////////////////////////////////////////////////////////////////
/// @brief normalize a function name
////////////////////////////////////////////////////////////////////////////////
function NORMALIZE_FNAME (functionName) {
'use strict';
var p = functionName.indexOf('::');
if (p === -1) {
return functionName;
}
return functionName.substr(p + 2);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief filter using a list of examples
////////////////////////////////////////////////////////////////////////////////
function FILTER (list, examples) {
'use strict';
var result = [ ], i;
if (examples === undefined || examples === null ||
(Array.isArray(examples) && examples.length === 0)) {
return list;
}
for (i = 0; i < list.length; ++i) {
var element = list[i];
if (AQL_MATCHES(element, examples, false)) {
result.push(element);
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief find a fulltext index for a certain attribute & collection
////////////////////////////////////////////////////////////////////////////////
function INDEX_FULLTEXT (collection, attribute) {
'use strict';
var indexes = collection.getIndexes(), i;
for (i = 0; i < indexes.length; ++i) {
var index = indexes[i];
if (index.type === "fulltext" && index.fields && index.fields[0] === attribute) {
return index.id;
}
}
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief find an index of a certain type for a collection
////////////////////////////////////////////////////////////////////////////////
function INDEX (collection, indexTypes) {
'use strict';
var indexes = collection.getIndexes(), i, j;
for (i = 0; i < indexes.length; ++i) {
var index = indexes[i];
for (j = 0; j < indexTypes.length; ++j) {
if (index.type === indexTypes[j]) {
return index;
}
}
}
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get access to a collection
////////////////////////////////////////////////////////////////////////////////
function COLLECTION (name, func) {
'use strict';
if (typeof name !== 'string') {
THROW(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, func);
}
var c;
if (name.substring(0, 1) === '_') {
// system collections need to be accessed slightly differently as they
// are not returned by the propertyGetter of db
c = INTERNAL.db._collection(name);
}
else {
c = INTERNAL.db[name];
}
if (c === null || c === undefined) {
THROW(func, INTERNAL.errors.ERROR_ARANGO_COLLECTION_NOT_FOUND, String(name));
}
return c;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief transforms a parameter into a list
////////////////////////////////////////////////////////////////////////////////
function TO_LIST (param, isStringHash) {
if (! param) {
param = isStringHash ? [ ] : [ { } ];
}
if (typeof param === "string") {
param = isStringHash ? [ param ] : { _id : param };
}
if (! Array.isArray(param)) {
param = [param];
}
return param;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief clone an object
////////////////////////////////////////////////////////////////////////////////
function CLONE (obj) {
'use strict';
if (obj === null || typeof(obj) !== "object") {
return obj;
}
var copy;
if (Array.isArray(obj)) {
copy = [ ];
obj.forEach(function (i) {
copy.push(CLONE(i));
});
}
else if (obj instanceof Object) {
copy = { };
Object.keys(obj).forEach(function(k) {
copy[k] = CLONE(obj[k]);
});
}
return copy;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief box a value into the AQL datatype system
////////////////////////////////////////////////////////////////////////////////
function FIX_VALUE (value) {
'use strict';
var type = typeof(value);
if (value === undefined ||
value === null ||
(type === 'number' && (isNaN(value) || ! isFinite(value)))) {
return null;
}
if (type === 'boolean' || type === 'string' || type === 'number') {
return value;
}
if (Array.isArray(value)) {
var i, n = value.length;
for (i = 0; i < n; ++i) {
value[i] = FIX_VALUE(value[i]);
}
return value;
}
if (type === 'object') {
var result = { };
Object.keys(value).forEach(function(k) {
if (typeof value[k] !== 'function') {
result[k] = FIX_VALUE(value[k]);
}
});
return result;
}
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the sort type of an operand
////////////////////////////////////////////////////////////////////////////////
function TYPEWEIGHT (value) {
'use strict';
if (value !== undefined && value !== null) {
if (Array.isArray(value)) {
return TYPEWEIGHT_ARRAY;
}
switch (typeof(value)) {
case 'boolean':
return TYPEWEIGHT_BOOL;
case 'number':
if (isNaN(value) || ! isFinite(value)) {
// not a number => null
return TYPEWEIGHT_NULL;
}
return TYPEWEIGHT_NUMBER;
case 'string':
return TYPEWEIGHT_STRING;
case 'object':
return TYPEWEIGHT_OBJECT;
}
}
return TYPEWEIGHT_NULL;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief compile a regex from a string pattern
////////////////////////////////////////////////////////////////////////////////
function CREATE_REGEX_PATTERN (chars) {
'use strict';
chars = AQL_TO_STRING(chars);
var i, n = chars.length, pattern = '';
var specialChar = /^([.*+?\^=!:${}()|\[\]\/\\])$/;
for (i = 0; i < n; ++i) {
var c = chars.charAt(i);
if (c.match(specialChar)) {
// character with special meaning in a regex
pattern += '\\' + c;
}
else if (c === '\t') {
pattern += '\\t';
}
else if (c === '\r') {
pattern += '\\r';
}
else if (c === '\n') {
pattern += '\\n';
}
else if (c === '\b') {
pattern += '\\b';
}
else if (c === '\f') {
pattern += '\\f';
}
else {
pattern += c;
}
}
return pattern;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief compile a regex from a string pattern
////////////////////////////////////////////////////////////////////////////////
function COMPILE_REGEX (regex, modifiers) {
'use strict';
regex = AQL_TO_STRING(regex);
var i, n = regex.length;
var escaped = false;
var pattern = '';
var specialChar = /^([.*+?\^=!:${}()|\[\]\/\\])$/;
for (i = 0; i < n; ++i) {
var c = regex.charAt(i);
if (c === '\\') {
if (escaped) {
// literal \
pattern += '\\\\';
}
escaped = ! escaped;
}
else {
if (c === '%') {
if (escaped) {
// literal %
pattern += '%';
}
else {
// wildcard
pattern += '(.|[\r\n])*';
}
}
else if (c === '_') {
if (escaped) {
// literal _
pattern += '_';
}
else {
// wildcard character
pattern += '(.|[\r\n])';
}
}
else if (c.match(specialChar)) {
// character with special meaning in a regex
pattern += '\\' + c;
}
else {
if (escaped) {
// found a backslash followed by no special character
pattern += '\\\\';
}
// literal character
pattern += c;
}
escaped = false;
}
}
return new RegExp('^' + pattern + '$', modifiers);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief call a user function
////////////////////////////////////////////////////////////////////////////////
function FCALL_USER (name, parameters) {
'use strict';
var prefix = DB_PREFIX(), reloaded = false;
if (! UserFunctions.hasOwnProperty(prefix)) {
reloadUserFunctions();
reloaded = true;
}
if (! UserFunctions[prefix].hasOwnProperty(name) && ! reloaded) {
// last chance
reloadUserFunctions();
}
if (! UserFunctions[prefix].hasOwnProperty(name)) {
THROW(null, INTERNAL.errors.ERROR_QUERY_FUNCTION_NOT_FOUND, name);
}
try {
return FIX_VALUE(UserFunctions[prefix][name].func.apply(null, parameters));
}
catch (err) {
WARN(name, INTERNAL.errors.ERROR_QUERY_FUNCTION_RUNTIME_ERROR, AQL_TO_STRING(err.stack || String(err)));
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief dynamically call a function
////////////////////////////////////////////////////////////////////////////////
function FCALL_DYNAMIC (func, applyDirect, values, name, args) {
var toCall;
name = AQL_TO_STRING(name).toUpperCase();
if (name.indexOf('::') > 0) {
// user-defined function
var prefix = DB_PREFIX(), reloaded = false;
if (! UserFunctions.hasOwnProperty(prefix)) {
reloadUserFunctions();
reloaded = true;
}
if (! UserFunctions[prefix].hasOwnProperty(name) && ! reloaded) {
// last chance
reloadUserFunctions();
}
if (! UserFunctions[prefix].hasOwnProperty(name)) {
THROW(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_NOT_FOUND, name);
}
toCall = UserFunctions[prefix][name].func;
}
else {
// built-in function
if (name === "CALL" || name === "APPLY") {
THROW(func, INTERNAL.errors.ERROR_QUERY_DISALLOWED_DYNAMIC_CALL, NORMALIZE_FNAME(name));
}
if (! exports.hasOwnProperty("AQL_" + name)) {
THROW(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_NOT_FOUND, NORMALIZE_FNAME(name));
}
toCall = exports["AQL_" + name];
}
if (applyDirect) {
try {
return FIX_VALUE(toCall.apply(null, args));
}
catch (err) {
WARN(name, INTERNAL.errors.ERROR_QUERY_FUNCTION_RUNTIME_ERROR, AQL_TO_STRING(err));
return null;
}
}
var type = TYPEWEIGHT(values), result, i;
if (type === TYPEWEIGHT_OBJECT) {
result = { };
for (i in values) {
if (values.hasOwnProperty(i)) {
args[0] = values[i];
result[i] = FIX_VALUE(toCall.apply(null, args));
}
}
return result;
}
else if (type === TYPEWEIGHT_ARRAY) {
result = [ ];
for (i = 0; i < values.length; ++i) {
args[0] = values[i];
result[i] = FIX_VALUE(toCall.apply(null, args));
}
return result;
}
WARN(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the numeric value or undefined if it is out of range
////////////////////////////////////////////////////////////////////////////////
function NUMERIC_VALUE (value) {
'use strict';
if (isNaN(value) || ! isFinite(value)) {
return 0;
}
return value;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief fix a value for a comparison
////////////////////////////////////////////////////////////////////////////////
function FIX (value) {
'use strict';
if (value === undefined) {
return null;
}
return value;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the values of an object in the order that they are defined
////////////////////////////////////////////////////////////////////////////////
function VALUES (value) {
'use strict';
var values = [ ];
Object.keys(value).forEach(function(k) {
values.push(value[k]);
});
return values;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief extract key names from an argument list
////////////////////////////////////////////////////////////////////////////////
function EXTRACT_KEYS (args, startArgument, func) {
'use strict';
var keys = { }, i, j, key, key2;
for (i = startArgument; i < args.length; ++i) {
key = args[i];
if (typeof key === 'string') {
keys[key] = true;
}
else if (typeof key === 'number') {
keys[String(key)] = true;
}
else if (Array.isArray(key)) {
for (j = 0; j < key.length; ++j) {
key2 = key[j];
if (typeof key2 === 'string') {
keys[key2] = true;
}
else {
WARN(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
}
}
}
return keys;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the keys of an array or object in a comparable way
////////////////////////////////////////////////////////////////////////////////
function KEYS (value, doSort) {
'use strict';
var keys;
if (Array.isArray(value)) {
var n = value.length, i;
keys = [ ];
for (i = 0; i < n; ++i) {
keys.push(i);
}
}
else {
keys = Object.keys(value);
if (doSort) {
// object keys need to be sorted by names
keys.sort();
}
}
return keys;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the keys of an array or object in a comparable way
////////////////////////////////////////////////////////////////////////////////
function KEYLIST (lhs, rhs) {
'use strict';
if (Array.isArray(lhs)) {
// lhs & rhs are lists
return KEYS(lhs.length > rhs.length ? lhs : rhs);
}
// lhs & rhs are arrays
var a, keys = KEYS(lhs);
for (a in rhs) {
if (rhs.hasOwnProperty(a) && ! lhs.hasOwnProperty(a)) {
keys.push(a);
}
}
// object keys need to be sorted by names
keys.sort();
return keys;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get an indexed value from an array or document (e.g. users[3])
////////////////////////////////////////////////////////////////////////////////
function GET_INDEX (value, index) {
'use strict';
if (TYPEWEIGHT(value) === TYPEWEIGHT_NULL) {
return null;
}
var result = null;
if (TYPEWEIGHT(value) === TYPEWEIGHT_OBJECT) {
result = value[String(index)];
}
else if (TYPEWEIGHT(value) === TYPEWEIGHT_ARRAY) {
var i = parseInt(index, 10);
if (i < 0) {
// negative indexes fetch the element from the end, e.g. -1 => value[value.length - 1];
i = value.length + i;
}
if (i >= 0 && i <= value.length - 1) {
result = value[i];
}
}
else {
return null;
}
if (TYPEWEIGHT(result) === TYPEWEIGHT_NULL) {
return null;
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief normalize a value for comparison, sorting etc.
////////////////////////////////////////////////////////////////////////////////
function NORMALIZE (value) {
'use strict';
if (value === null || value === undefined) {
return null;
}
if (typeof(value) !== "object") {
return value;
}
var result;
if (Array.isArray(value)) {
result = [ ];
value.forEach(function (v) {
result.push(NORMALIZE(v));
});
}
else {
result = { };
KEYS(value, true).forEach(function (a) {
result[a] = NORMALIZE(value[a]);
});
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get an attribute from a document (e.g. users.name)
////////////////////////////////////////////////////////////////////////////////
function DOCUMENT_MEMBER (value, attributeName) {
'use strict';
if (TYPEWEIGHT(value) !== TYPEWEIGHT_OBJECT) {
return null;
}
var result = value[attributeName];
if (TYPEWEIGHT(result) === TYPEWEIGHT_NULL) {
return null;
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get a document by its unique id or their unique ids
////////////////////////////////////////////////////////////////////////////////
function DOCUMENT_HANDLE (id) {
'use strict';
if (TYPEWEIGHT(id) === TYPEWEIGHT_ARRAY) {
var result = [ ], i;
for (i = 0; i < id.length; ++i) {
try {
result.push(INTERNAL.db._document(id[i]));
}
catch (e1) {
}
}
return result;
}
try {
return INTERNAL.db._document(id);
}
catch (e2) {
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get a document by its unique id or their unique ids
////////////////////////////////////////////////////////////////////////////////
function AQL_DOCUMENT (collection, id) {
'use strict';
// we're polymorphic
if (id === undefined) {
// called with a single parameter
var weight = TYPEWEIGHT(collection);
if (weight === TYPEWEIGHT_STRING || weight === TYPEWEIGHT_ARRAY) {
return DOCUMENT_HANDLE(collection);
}
}
if (TYPEWEIGHT(id) === TYPEWEIGHT_ARRAY) {
var c = COLLECTION(collection, "DOCUMENT");
var result = [ ], i;
for (i = 0; i < id.length; ++i) {
try {
result.push(c.document(id[i]));
}
catch (e1) {
}
}
return result;
}
try {
return COLLECTION(collection, "DOCUMENT").document(id);
}
catch (e2) {
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get all documents from the specified collection
////////////////////////////////////////////////////////////////////////////////
function GET_DOCUMENTS (collection, func) {
'use strict';
WARN(null, INTERNAL.errors.ERROR_QUERY_COLLECTION_USED_IN_EXPRESSION, AQL_TO_STRING(collection));
if (isCoordinator) {
return COLLECTION(collection, func).all().toArray();
}
return COLLECTION(collection, func).ALL(0, null).documents;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get names of all collections
////////////////////////////////////////////////////////////////////////////////
function AQL_COLLECTIONS () {
'use strict';
var result = [ ];
INTERNAL.db._collections().forEach(function (c) {
result.push({
_id : c._id,
name : c.name()
});
});
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the number of documents in a collection
/// this is an internal function that is not exposed to end users
////////////////////////////////////////////////////////////////////////////////
function AQL_COLLECTION_COUNT (name) {
'use strict';
if (typeof name !== 'string') {
THROW("COLLECTION_COUNT", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "COLLECTION_COUNT");
}
var c = INTERNAL.db._collection(name);
if (c === null || c === undefined) {
THROW("COLLECTION_COUNT", INTERNAL.errors.ERROR_ARANGO_COLLECTION_NOT_FOUND, String(name));
}
return c.count();
}
////////////////////////////////////////////////////////////////////////////////
/// @brief execute ternary operator
///
/// the condition should be a boolean value, returns either the truepart
/// or the falsepart
////////////////////////////////////////////////////////////////////////////////
function TERNARY_OPERATOR (condition, truePart, falsePart) {
'use strict';
if (condition) {
return truePart();
}
return falsePart();
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform logical and
///
/// both operands must be boolean values, returns a boolean, uses short-circuit
/// evaluation
////////////////////////////////////////////////////////////////////////////////
function LOGICAL_AND (lhs, rhs) {
'use strict';
var l = lhs();
if (! l) {
return l;
}
return rhs();
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform logical or
///
/// both operands must be boolean values, returns a boolean, uses short-circuit
/// evaluation
////////////////////////////////////////////////////////////////////////////////
function LOGICAL_OR (lhs, rhs) {
'use strict';
var l = lhs();
if (l) {
return l;
}
return rhs();
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform logical negation
///
/// the operand must be a boolean values, returns a boolean
////////////////////////////////////////////////////////////////////////////////
function LOGICAL_NOT (lhs) {
'use strict';
return ! AQL_TO_BOOL(lhs);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform equality check for arrays
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_ARRAY_FUNC (lhs, rhs, quantifier, func) {
'use strict';
if (TYPEWEIGHT(lhs) !== TYPEWEIGHT_ARRAY) {
return false;
}
var n = lhs.length, min, max;
if (quantifier === 1) {
// NONE
min = max = 0;
}
else if (quantifier === 2) {
// ALL
min = max = n;
}
else if (quantifier === 3) {
// ANY
min = (n === 0 ? 0 : 1);
max = n;
}
var left = n, matches = 0;
for (var i = 0; i < n; ++i) {
var result = func(lhs[i], rhs);
--left;
if (result) {
++matches;
if (matches > max) {
// too many matches
return false;
}
if (matches >= min && matches + left <= max) {
// enough matches
return true;
}
}
else {
if (matches + left < min) {
// too few matches
return false;
}
}
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform equality check
///
/// returns true if the operands are equal, false otherwise
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_EQUAL (lhs, rhs) {
'use strict';
var leftWeight = TYPEWEIGHT(lhs);
var rightWeight = TYPEWEIGHT(rhs);
if (leftWeight !== rightWeight) {
return false;
}
// lhs and rhs have the same type
if (leftWeight >= TYPEWEIGHT_ARRAY) {
// arrays and objects
var keys = KEYLIST(lhs, rhs), i, n = keys.length;
for (i = 0; i < n; ++i) {
var key = keys[i];
if (RELATIONAL_EQUAL(lhs[key], rhs[key]) === false) {
return false;
}
}
return true;
}
// primitive type
if (leftWeight === TYPEWEIGHT_NULL) {
return true;
}
if (leftWeight === TYPEWEIGHT_STRING) {
return COMPARE_STRING(lhs, rhs) === 0;
}
return (lhs === rhs);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform inequality check
///
/// returns true if the operands are unequal, false otherwise
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_UNEQUAL (lhs, rhs) {
'use strict';
var leftWeight = TYPEWEIGHT(lhs);
var rightWeight = TYPEWEIGHT(rhs);
if (leftWeight !== rightWeight) {
return true;
}
// lhs and rhs have the same type
if (leftWeight >= TYPEWEIGHT_ARRAY) {
// arrays and objects
var keys = KEYLIST(lhs, rhs), i, n = keys.length;
for (i = 0; i < n; ++i) {
var key = keys[i];
if (RELATIONAL_UNEQUAL(lhs[key], rhs[key]) === true) {
return true;
}
}
return false;
}
// primitive type
if (leftWeight === TYPEWEIGHT_NULL) {
return false;
}
if (leftWeight === TYPEWEIGHT_STRING) {
return COMPARE_STRING(lhs, rhs) !== 0;
}
return (lhs !== rhs);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform greater than check (inner function)
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_GREATER_REC (lhs, rhs) {
'use strict';
var leftWeight = TYPEWEIGHT(lhs);
var rightWeight = TYPEWEIGHT(rhs);
if (leftWeight > rightWeight) {
return true;
}
if (leftWeight < rightWeight) {
return false;
}
// lhs and rhs have the same type
if (leftWeight >= TYPEWEIGHT_ARRAY) {
// arrays and objects
var keys = KEYLIST(lhs, rhs), i, n = keys.length;
for (i = 0; i < n; ++i) {
var key = keys[i], result = RELATIONAL_GREATER_REC(lhs[key], rhs[key]);
if (result !== null) {
return result;
}
}
return null;
}
// primitive type
if (leftWeight === TYPEWEIGHT_NULL) {
return null;
}
if (leftWeight === TYPEWEIGHT_STRING) {
return COMPARE_STRING(lhs, rhs) > 0;
}
if (lhs === rhs) {
return null;
}
return (lhs > rhs);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform greater than check
///
/// returns true if the left operand is greater than the right operand
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_GREATER (lhs, rhs) {
'use strict';
var result = RELATIONAL_GREATER_REC(lhs, rhs);
if (result === null) {
result = false;
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform greater equal check (inner function)
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_GREATEREQUAL_REC (lhs, rhs) {
'use strict';
var leftWeight = TYPEWEIGHT(lhs);
var rightWeight = TYPEWEIGHT(rhs);
if (leftWeight > rightWeight) {
return true;
}
if (leftWeight < rightWeight) {
return false;
}
// lhs and rhs have the same type
if (leftWeight >= TYPEWEIGHT_ARRAY) {
// arrays and objects
var keys = KEYLIST(lhs, rhs), i, n = keys.length;
for (i = 0; i < n; ++i) {
var key = keys[i], result = RELATIONAL_GREATEREQUAL_REC(lhs[key], rhs[key]);
if (result !== null) {
return result;
}
}
return null;
}
// primitive type
if (leftWeight === TYPEWEIGHT_NULL) {
return null;
}
if (leftWeight === TYPEWEIGHT_STRING) {
return COMPARE_STRING(lhs, rhs) >= 0;
}
if (lhs === rhs) {
return null;
}
return (lhs >= rhs);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform greater equal check
///
/// returns true if the left operand is greater or equal to the right operand
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_GREATEREQUAL (lhs, rhs) {
'use strict';
var result = RELATIONAL_GREATEREQUAL_REC(lhs, rhs);
if (result === null) {
result = true;
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform less than check (inner function)
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_LESS_REC (lhs, rhs) {
'use strict';
var leftWeight = TYPEWEIGHT(lhs);
var rightWeight = TYPEWEIGHT(rhs);
if (leftWeight < rightWeight) {
return true;
}
if (leftWeight > rightWeight) {
return false;
}
// lhs and rhs have the same type
if (leftWeight >= TYPEWEIGHT_ARRAY) {
// arrays and objects
var keys = KEYLIST(lhs, rhs), i, n = keys.length;
for (i = 0; i < n; ++i) {
var key = keys[i], result = RELATIONAL_LESS_REC(lhs[key], rhs[key]);
if (result !== null) {
return result;
}
}
return null;
}
// primitive type
if (leftWeight === TYPEWEIGHT_NULL) {
return null;
}
if (leftWeight === TYPEWEIGHT_STRING) {
return COMPARE_STRING(lhs, rhs) < 0;
}
if (lhs === rhs) {
return null;
}
return (lhs < rhs);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform less than check
///
/// returns true if the left operand is less than the right operand
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_LESS (lhs, rhs) {
'use strict';
var result = RELATIONAL_LESS_REC(lhs, rhs);
if (result === null) {
result = false;
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform less equal check (inner function)
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_LESSEQUAL_REC (lhs, rhs) {
'use strict';
var leftWeight = TYPEWEIGHT(lhs);
var rightWeight = TYPEWEIGHT(rhs);
if (leftWeight < rightWeight) {
return true;
}
if (leftWeight > rightWeight) {
return false;
}
// lhs and rhs have the same type
if (leftWeight >= TYPEWEIGHT_ARRAY) {
// arrays and objects
var keys = KEYLIST(lhs, rhs), i, n = keys.length;
for (i = 0; i < n; ++i) {
var key = keys[i], result = RELATIONAL_LESSEQUAL_REC(lhs[key], rhs[key]);
if (result !== null) {
return result;
}
}
return null;
}
// primitive type
if (leftWeight === TYPEWEIGHT_NULL) {
return null;
}
if (leftWeight === TYPEWEIGHT_STRING) {
return COMPARE_STRING(lhs, rhs) <= 0;
}
if (lhs === rhs) {
return null;
}
return (lhs <= rhs);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform less equal check
///
/// returns true if the left operand is less or equal to the right operand
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_LESSEQUAL (lhs, rhs) {
'use strict';
var result = RELATIONAL_LESSEQUAL_REC(lhs, rhs);
if (result === null) {
result = true;
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform comparison
///
/// returns -1 if the left operand is less than the right operand, 1 if it is
/// greater, 0 if both operands are equal
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_CMP (lhs, rhs) {
'use strict';
var leftWeight = TYPEWEIGHT(lhs);
var rightWeight = TYPEWEIGHT(rhs);
if (leftWeight < rightWeight) {
return -1;
}
if (leftWeight > rightWeight) {
return 1;
}
// lhs and rhs have the same type
if (leftWeight >= TYPEWEIGHT_ARRAY) {
// arrays and objects
var keys = KEYLIST(lhs, rhs), i, n = keys.length;
for (i = 0; i < n; ++i) {
var key = keys[i], result = RELATIONAL_CMP(lhs[key], rhs[key]);
if (result !== 0) {
return result;
}
}
return 0;
}
// primitive type
if (leftWeight === TYPEWEIGHT_NULL) {
return 0;
}
if (leftWeight === TYPEWEIGHT_STRING) {
return COMPARE_STRING(lhs, rhs);
}
if (lhs < rhs) {
return -1;
}
if (lhs > rhs) {
return 1;
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform in list check
///
/// returns true if the left operand is contained in the right operand
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_IN (lhs, rhs) {
'use strict';
var rightWeight = TYPEWEIGHT(rhs);
if (rightWeight !== TYPEWEIGHT_ARRAY) {
WARN(null, INTERNAL.errors.ERROR_QUERY_ARRAY_EXPECTED);
return false;
}
var numRight = rhs.length, i;
for (i = 0; i < numRight; ++i) {
if (RELATIONAL_EQUAL(lhs, rhs[i])) {
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform not-in list check
///
/// returns true if the left operand is not contained in the right operand
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_NOT_IN (lhs, rhs) {
'use strict';
return ! RELATIONAL_IN(lhs, rhs);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform equality check for arrays
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_ARRAY_EQUAL (lhs, rhs, quantifier) {
'use strict';
return RELATIONAL_ARRAY_FUNC(lhs, rhs, quantifier, RELATIONAL_EQUAL);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform unequality check for arrays
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_ARRAY_UNEQUAL (lhs, rhs, quantifier) {
'use strict';
return RELATIONAL_ARRAY_FUNC(lhs, rhs, quantifier, RELATIONAL_UNEQUAL);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform greater check for arrays
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_ARRAY_GREATER (lhs, rhs, quantifier) {
'use strict';
return RELATIONAL_ARRAY_FUNC(lhs, rhs, quantifier, RELATIONAL_GREATER);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform greater equal check for arrays
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_ARRAY_GREATEREQUAL (lhs, rhs, quantifier) {
'use strict';
return RELATIONAL_ARRAY_FUNC(lhs, rhs, quantifier, RELATIONAL_GREATEREQUAL);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform less check for arrays
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_ARRAY_LESS (lhs, rhs, quantifier) {
'use strict';
return RELATIONAL_ARRAY_FUNC(lhs, rhs, quantifier, RELATIONAL_LESS);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform less equal check for arrays
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_ARRAY_LESSEQUAL (lhs, rhs, quantifier) {
'use strict';
return RELATIONAL_ARRAY_FUNC(lhs, rhs, quantifier, RELATIONAL_LESSEQUAL);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform in check for arrays
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_ARRAY_IN (lhs, rhs, quantifier) {
'use strict';
return RELATIONAL_ARRAY_FUNC(lhs, rhs, quantifier, RELATIONAL_IN);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform in check for arrays
////////////////////////////////////////////////////////////////////////////////
function RELATIONAL_ARRAY_NOT_IN (lhs, rhs, quantifier) {
'use strict';
return RELATIONAL_ARRAY_FUNC(lhs, rhs, quantifier, RELATIONAL_NOT_IN);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform unary plus operation
////////////////////////////////////////////////////////////////////////////////
function UNARY_PLUS (value) {
'use strict';
value = AQL_TO_NUMBER(value);
return AQL_TO_NUMBER(+ value);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform unary minus operation
////////////////////////////////////////////////////////////////////////////////
function UNARY_MINUS (value) {
'use strict';
value = AQL_TO_NUMBER(value);
return AQL_TO_NUMBER(- value);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform arithmetic plus or string concatenation
////////////////////////////////////////////////////////////////////////////////
function ARITHMETIC_PLUS (lhs, rhs) {
'use strict';
lhs = AQL_TO_NUMBER(lhs);
rhs = AQL_TO_NUMBER(rhs);
return AQL_TO_NUMBER(lhs + rhs);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform arithmetic minus
////////////////////////////////////////////////////////////////////////////////
function ARITHMETIC_MINUS (lhs, rhs) {
'use strict';
lhs = AQL_TO_NUMBER(lhs);
rhs = AQL_TO_NUMBER(rhs);
return AQL_TO_NUMBER(lhs - rhs);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform arithmetic multiplication
////////////////////////////////////////////////////////////////////////////////
function ARITHMETIC_TIMES (lhs, rhs) {
'use strict';
lhs = AQL_TO_NUMBER(lhs);
rhs = AQL_TO_NUMBER(rhs);
return AQL_TO_NUMBER(lhs * rhs);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform arithmetic division
////////////////////////////////////////////////////////////////////////////////
function ARITHMETIC_DIVIDE (lhs, rhs) {
'use strict';
lhs = AQL_TO_NUMBER(lhs);
rhs = AQL_TO_NUMBER(rhs);
if (rhs === 0 || rhs === null) {
WARN(null, INTERNAL.errors.ERROR_QUERY_DIVISION_BY_ZERO);
return 0;
}
return AQL_TO_NUMBER(lhs / rhs);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform arithmetic modulus
////////////////////////////////////////////////////////////////////////////////
function ARITHMETIC_MODULUS (lhs, rhs) {
'use strict';
lhs = AQL_TO_NUMBER(lhs);
rhs = AQL_TO_NUMBER(rhs);
if (rhs === 0 || rhs === null) {
WARN(null, INTERNAL.errors.ERROR_QUERY_DIVISION_BY_ZERO);
return 0;
}
return AQL_TO_NUMBER(lhs % rhs);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform string concatenation
////////////////////////////////////////////////////////////////////////////////
function AQL_CONCAT () {
'use strict';
var result = '', i, j;
for (i = 0; i < arguments.length; ++i) {
var element = arguments[i];
var weight = TYPEWEIGHT(element);
if (weight === TYPEWEIGHT_NULL) {
continue;
}
else if (weight === TYPEWEIGHT_ARRAY) {
for (j = 0; j < element.length; ++j) {
if (TYPEWEIGHT(element[j]) !== TYPEWEIGHT_NULL) {
result += AQL_TO_STRING(element[j]);
}
}
}
else {
result += AQL_TO_STRING(element);
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief perform string concatenation using a separator character
////////////////////////////////////////////////////////////////////////////////
function AQL_CONCAT_SEPARATOR () {
'use strict';
var separator, found = false, result = '', i, j;
for (i = 0; i < arguments.length; ++i) {
var element = arguments[i];
var weight = TYPEWEIGHT(element);
if (i > 0 && weight === TYPEWEIGHT_NULL) {
continue;
}
if (i === 0) {
separator = AQL_TO_STRING(element);
continue;
}
else if (found) {
result += separator;
}
if (weight === TYPEWEIGHT_ARRAY) {
found = false;
for (j = 0; j < element.length; ++j) {
if (TYPEWEIGHT(element[j]) !== TYPEWEIGHT_NULL) {
if (found) {
result += separator;
}
result += AQL_TO_STRING(element[j]);
found = true;
}
}
}
else {
result += AQL_TO_STRING(element);
found = true;
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the length of a string in characters (not bytes)
////////////////////////////////////////////////////////////////////////////////
function AQL_CHAR_LENGTH (value) {
'use strict';
return AQL_TO_STRING(value).length;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief convert a string to lower case
////////////////////////////////////////////////////////////////////////////////
function AQL_LOWER (value) {
'use strict';
return AQL_TO_STRING(value).toLowerCase();
}
////////////////////////////////////////////////////////////////////////////////
/// @brief convert a string to upper case
////////////////////////////////////////////////////////////////////////////////
function AQL_UPPER (value) {
'use strict';
return AQL_TO_STRING(value).toUpperCase();
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return a substring of the string
////////////////////////////////////////////////////////////////////////////////
function AQL_SUBSTRING (value, offset, count) {
'use strict';
if (TYPEWEIGHT(count) !== TYPEWEIGHT_NULL) {
count = AQL_TO_NUMBER(count);
}
return AQL_TO_STRING(value).substr(AQL_TO_NUMBER(offset), count);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief searches a substring in a string
////////////////////////////////////////////////////////////////////////////////
function AQL_CONTAINS (value, search, returnIndex) {
'use strict';
search = AQL_TO_STRING(search);
var result;
if (search.length === 0) {
result = -1;
}
else {
result = AQL_TO_STRING(value).indexOf(search);
}
if (AQL_TO_BOOL(returnIndex)) {
return result;
}
return (result !== -1);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief searches a substring in a string, using a regex
////////////////////////////////////////////////////////////////////////////////
function AQL_LIKE (value, regex, caseInsensitive) {
'use strict';
var modifiers = '';
if (caseInsensitive) {
modifiers += 'i';
}
regex = AQL_TO_STRING(regex);
if (RegexCache[modifiers][regex] === undefined) {
RegexCache[modifiers][regex] = COMPILE_REGEX(regex, modifiers);
}
try {
return RegexCache[modifiers][regex].test(AQL_TO_STRING(value));
}
catch (err) {
WARN("LIKE", INTERNAL.errors.ERROR_QUERY_INVALID_REGEX);
return false;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief returns the leftmost parts of a string
////////////////////////////////////////////////////////////////////////////////
function AQL_LEFT (value, length) {
'use strict';
return AQL_TO_STRING(value).substr(0, AQL_TO_NUMBER(length));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief returns the rightmost parts of a string
////////////////////////////////////////////////////////////////////////////////
function AQL_RIGHT (value, length) {
'use strict';
value = AQL_TO_STRING(value);
length = AQL_TO_NUMBER(length);
var left = value.length - length;
if (left < 0) {
left = 0;
}
return value.substr(left, length);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief returns a trimmed version of a string
////////////////////////////////////////////////////////////////////////////////
function AQL_TRIM (value, chars) {
'use strict';
if (chars === 1) {
return AQL_LTRIM(value);
}
else if (chars === 2) {
return AQL_RTRIM(value);
}
else if (chars === null || chars === undefined || chars === 0) {
return AQL_TO_STRING(value).replace(new RegExp("(^\\s+|\\s+$)", 'g'), '');
}
var pattern = CREATE_REGEX_PATTERN(chars);
return AQL_TO_STRING(value).replace(new RegExp("(^[" + pattern + "]+|[" + pattern + "]+$)", 'g'), '');
}
////////////////////////////////////////////////////////////////////////////////
/// @brief trim a value from the left
////////////////////////////////////////////////////////////////////////////////
function AQL_LTRIM (value, chars) {
'use strict';
if (chars === null || chars === undefined) {
chars = "^\\s+";
}
else {
chars = "^[" + CREATE_REGEX_PATTERN(chars) + "]+";
}
return AQL_TO_STRING(value).replace(new RegExp(chars, 'g'), '');
}
////////////////////////////////////////////////////////////////////////////////
/// @brief trim a value from the right
////////////////////////////////////////////////////////////////////////////////
function AQL_RTRIM (value, chars) {
'use strict';
if (chars === null || chars === undefined) {
chars = "\\s+$";
}
else {
chars = "[" + CREATE_REGEX_PATTERN(chars) + "]+$";
}
return AQL_TO_STRING(value).replace(new RegExp(chars, 'g'), '');
}
////////////////////////////////////////////////////////////////////////////////
/// @brief split a string using a separator
////////////////////////////////////////////////////////////////////////////////
function AQL_SPLIT (value, separator, limit) {
'use strict';
if (separator === null || separator === undefined) {
return [ AQL_TO_STRING(value) ];
}
if (TYPEWEIGHT(limit) === TYPEWEIGHT_NULL) {
limit = undefined;
}
else {
limit = AQL_TO_NUMBER(limit);
}
if (limit < 0) {
WARN("SPLIT", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
if (TYPEWEIGHT(separator) === TYPEWEIGHT_ARRAY) {
var patterns = [];
separator.forEach(function(s) {
patterns.push(CREATE_REGEX_PATTERN(AQL_TO_STRING(s)));
});
return AQL_TO_STRING(value).split(new RegExp(patterns.join("|"), "g"), limit);
}
return AQL_TO_STRING(value).split(AQL_TO_STRING(separator), limit);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief replace a search value inside a string
////////////////////////////////////////////////////////////////////////////////
function AQL_SUBSTITUTE (value, search, replace, limit) {
'use strict';
var pattern = "", patterns, replacements = { }, sWeight = TYPEWEIGHT(search);
value = AQL_TO_STRING(value);
if (sWeight === TYPEWEIGHT_OBJECT) {
patterns = [ ];
KEYS(search, false).forEach(function(k) {
patterns.push(CREATE_REGEX_PATTERN(k));
replacements[k] = AQL_TO_STRING(search[k]);
});
pattern = patterns.join('|');
limit = replace;
}
else if (sWeight === TYPEWEIGHT_STRING) {
pattern = CREATE_REGEX_PATTERN(search);
if (TYPEWEIGHT(replace) === TYPEWEIGHT_NULL) {
replacements[search] = "";
}
else {
replacements[search] = AQL_TO_STRING(replace);
}
}
else if (sWeight === TYPEWEIGHT_ARRAY) {
if (search.length === 0) {
// empty list
WARN("SUBSTITUTE", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return value;
}
patterns = [ ];
if (TYPEWEIGHT(replace) === TYPEWEIGHT_ARRAY) {
// replace each occurrence with a member from the second list
search.forEach(function(k, i) {
k = AQL_TO_STRING(k);
patterns.push(CREATE_REGEX_PATTERN(k));
if (i < replace.length) {
replacements[k] = AQL_TO_STRING(replace[i]);
}
else {
replacements[k] = "";
}
});
}
else {
// replace all occurrences with a constant string
if (TYPEWEIGHT(replace) === TYPEWEIGHT_NULL) {
replace = "";
}
else {
replace = AQL_TO_STRING(replace);
}
search.forEach(function(k, i) {
k = AQL_TO_STRING(k);
patterns.push(CREATE_REGEX_PATTERN(k));
replacements[k] = replace;
});
}
pattern = patterns.join('|');
}
if (pattern === "") {
return value;
}
if (TYPEWEIGHT(limit) === TYPEWEIGHT_NULL) {
limit = undefined;
}
else {
limit = AQL_TO_NUMBER(limit);
}
if (limit < 0) {
WARN("SUBSTITUTE", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
return AQL_TO_STRING(value).replace(new RegExp(pattern, 'g'), function(match) {
if (limit === undefined) {
return replacements[match];
}
if (limit > 0) {
--limit;
return replacements[match];
}
return match;
});
}
////////////////////////////////////////////////////////////////////////////////
/// @brief generates the MD5 value for a string
////////////////////////////////////////////////////////////////////////////////
function AQL_MD5 (value) {
'use strict';
return INTERNAL.md5(AQL_TO_STRING(value));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief generates the SHA1 value for a string
////////////////////////////////////////////////////////////////////////////////
function AQL_SHA1 (value) {
'use strict';
return INTERNAL.sha1(AQL_TO_STRING(value));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief generates a random token of the specified length
////////////////////////////////////////////////////////////////////////////////
function AQL_RANDOM_TOKEN (length) {
'use strict';
length = AQL_TO_NUMBER(length);
if (length <= 0 || length > 65536) {
THROW("RANDOM_TOKEN", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "RANDOM_TOKEN");
}
return INTERNAL.genRandomAlphaNumbers(length);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief finds search in value
////////////////////////////////////////////////////////////////////////////////
function AQL_FIND_FIRST (value, search, start, end) {
'use strict';
if (TYPEWEIGHT(start) !== TYPEWEIGHT_NULL) {
start = AQL_TO_NUMBER(start);
if (start < 0) {
return -1;
}
}
else {
start = 0;
}
if (TYPEWEIGHT(end) !== TYPEWEIGHT_NULL) {
end = AQL_TO_NUMBER(end);
if (end < start || end < 0) {
return -1;
}
}
else {
end = undefined;
}
if (end !== undefined) {
return AQL_TO_STRING(value).substr(0, end + 1).indexOf(AQL_TO_STRING(search), start);
}
return AQL_TO_STRING(value).indexOf(AQL_TO_STRING(search), start);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief finds search in value
////////////////////////////////////////////////////////////////////////////////
function AQL_FIND_LAST (value, search, start, end) {
'use strict';
if (TYPEWEIGHT(start) !== TYPEWEIGHT_NULL) {
start = AQL_TO_NUMBER(start);
}
else {
start = undefined;
}
if (TYPEWEIGHT(end) !== TYPEWEIGHT_NULL) {
end = AQL_TO_NUMBER(end);
if (end < start || end < 0) {
return -1;
}
}
else {
end = undefined;
}
var result;
if (start > 0 || end !== undefined) {
if (end === undefined) {
result = AQL_TO_STRING(value).substr(start).lastIndexOf(AQL_TO_STRING(search));
}
else {
result = AQL_TO_STRING(value).substr(start, end - start + 1).lastIndexOf(AQL_TO_STRING(search));
}
if (result !== -1) {
result += start;
}
}
else {
result = AQL_TO_STRING(value).lastIndexOf(AQL_TO_STRING(search));
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief cast to a bool
///
/// the operand can have any type, always returns a bool
////////////////////////////////////////////////////////////////////////////////
function AQL_TO_BOOL (value) {
'use strict';
switch (TYPEWEIGHT(value)) {
case TYPEWEIGHT_NULL:
return false;
case TYPEWEIGHT_BOOL:
return value;
case TYPEWEIGHT_NUMBER:
return (value !== 0);
case TYPEWEIGHT_STRING:
return (value !== '');
case TYPEWEIGHT_ARRAY:
case TYPEWEIGHT_OBJECT:
return true;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief cast to a number
///
/// the operand can have any type, returns a number or null
////////////////////////////////////////////////////////////////////////////////
function AQL_TO_NUMBER (value) {
'use strict';
switch (TYPEWEIGHT(value)) {
case TYPEWEIGHT_NULL:
// this covers Infinity and NaN
return 0;
case TYPEWEIGHT_BOOL:
return (value ? 1 : 0);
case TYPEWEIGHT_NUMBER:
return value;
case TYPEWEIGHT_STRING:
var result = NUMERIC_VALUE(Number(value));
return ((TYPEWEIGHT(result) === TYPEWEIGHT_NUMBER) ? result : null);
case TYPEWEIGHT_ARRAY:
if (value.length === 0) {
return 0;
}
if (value.length === 1) {
return AQL_TO_NUMBER(value[0]);
}
// fallthrough intentional
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief cast to a string
///
/// the operand can have any type, always returns a string
////////////////////////////////////////////////////////////////////////////////
function AQL_TO_STRING (value) {
'use strict';
switch (TYPEWEIGHT(value)) {
case TYPEWEIGHT_NULL:
return '';
case TYPEWEIGHT_BOOL:
return (value ? 'true' : 'false');
case TYPEWEIGHT_STRING:
return value;
case TYPEWEIGHT_NUMBER:
case TYPEWEIGHT_ARRAY:
case TYPEWEIGHT_OBJECT:
return value.toString();
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief cast to an array
///
/// the operand can have any type, always returns a list
////////////////////////////////////////////////////////////////////////////////
function AQL_TO_ARRAY (value) {
'use strict';
switch (TYPEWEIGHT(value)) {
case TYPEWEIGHT_NULL:
return [ ];
case TYPEWEIGHT_BOOL:
case TYPEWEIGHT_NUMBER:
case TYPEWEIGHT_STRING:
return [ value ];
case TYPEWEIGHT_ARRAY:
return value;
case TYPEWEIGHT_OBJECT:
return VALUES(value);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the array as is, or an empty array
////////////////////////////////////////////////////////////////////////////////
function AQL_ARRAYIZE (value) {
'use strict';
if (TYPEWEIGHT(value) !== TYPEWEIGHT_ARRAY) {
return [ ];
}
return value;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief test if value is of type null
///
/// returns a bool
////////////////////////////////////////////////////////////////////////////////
function AQL_IS_NULL (value) {
'use strict';
return (TYPEWEIGHT(value) === TYPEWEIGHT_NULL);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief test if value is of type bool
///
/// returns a bool
////////////////////////////////////////////////////////////////////////////////
function AQL_IS_BOOL (value) {
'use strict';
return (TYPEWEIGHT(value) === TYPEWEIGHT_BOOL);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief test if value is of type number
///
/// returns a bool
////////////////////////////////////////////////////////////////////////////////
function AQL_IS_NUMBER (value) {
'use strict';
return (TYPEWEIGHT(value) === TYPEWEIGHT_NUMBER);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief test if value is of type string
///
/// returns a bool
////////////////////////////////////////////////////////////////////////////////
function AQL_IS_STRING (value) {
'use strict';
return (TYPEWEIGHT(value) === TYPEWEIGHT_STRING);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief test if value is of type array
///
/// returns a bool
////////////////////////////////////////////////////////////////////////////////
function AQL_IS_ARRAY (value) {
'use strict';
return (TYPEWEIGHT(value) === TYPEWEIGHT_ARRAY);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief test if value is of type object
///
/// returns a bool
////////////////////////////////////////////////////////////////////////////////
function AQL_IS_OBJECT (value) {
'use strict';
return (TYPEWEIGHT(value) === TYPEWEIGHT_OBJECT);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief test if value is of a valid datestring
///
/// returns a bool
////////////////////////////////////////////////////////////////////////////////
function AQL_IS_DATESTRING (value) {
'use strict';
if (TYPEWEIGHT(value) !== TYPEWEIGHT_STRING) {
return false;
}
// argument is a string
// detect invalid dates ("foo" -> "fooZ" -> getTime() == NaN)
var date = new Date(value);
if (isNaN(date)) {
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief integer closest to value, not greater than value
////////////////////////////////////////////////////////////////////////////////
function AQL_FLOOR (value) {
'use strict';
return NUMERIC_VALUE(Math.floor(AQL_TO_NUMBER(value)));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief integer closest to value and not less than value
////////////////////////////////////////////////////////////////////////////////
function AQL_CEIL (value) {
'use strict';
return NUMERIC_VALUE(Math.ceil(AQL_TO_NUMBER(value)));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief integer closest to value
////////////////////////////////////////////////////////////////////////////////
function AQL_ROUND (value) {
'use strict';
return NUMERIC_VALUE(Math.round(AQL_TO_NUMBER(value)));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief absolute value
////////////////////////////////////////////////////////////////////////////////
function AQL_ABS (value) {
'use strict';
return NUMERIC_VALUE(Math.abs(AQL_TO_NUMBER(value)));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief a random value between 0 and 1
////////////////////////////////////////////////////////////////////////////////
function AQL_RAND () {
'use strict';
return Math.random();
}
////////////////////////////////////////////////////////////////////////////////
/// @brief square root
////////////////////////////////////////////////////////////////////////////////
function AQL_SQRT (value) {
'use strict';
return NUMERIC_VALUE(Math.sqrt(AQL_TO_NUMBER(value)));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief exponentation
////////////////////////////////////////////////////////////////////////////////
function AQL_POW (base, exp) {
'use strict';
return NUMERIC_VALUE(Math.pow(AQL_TO_NUMBER(base), AQL_TO_NUMBER(exp)));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the length of a list, document or string
////////////////////////////////////////////////////////////////////////////////
function AQL_LENGTH (value) {
'use strict';
var typeWeight = TYPEWEIGHT(value);
if (typeWeight === TYPEWEIGHT_ARRAY) {
return value.length;
}
else if (typeWeight === TYPEWEIGHT_OBJECT) {
return KEYS(value, false).length;
}
else if (typeWeight === TYPEWEIGHT_NULL) {
return 0;
}
else if (typeWeight === TYPEWEIGHT_BOOL) {
return value ? 1 : 0;
}
return AQL_TO_STRING(value).length;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the first element of a list
////////////////////////////////////////////////////////////////////////////////
function AQL_FIRST (value) {
'use strict';
if (TYPEWEIGHT(value) !== TYPEWEIGHT_ARRAY) {
WARN("FIRST", INTERNAL.errors.ERROR_QUERY_ARRAY_EXPECTED);
return null;
}
if (value.length === 0) {
return null;
}
return value[0];
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the last element of a list
////////////////////////////////////////////////////////////////////////////////
function AQL_LAST (value) {
'use strict';
if (TYPEWEIGHT(value) !== TYPEWEIGHT_ARRAY) {
WARN("LAST", INTERNAL.errors.ERROR_QUERY_ARRAY_EXPECTED);
return null;
}
if (value.length === 0) {
return null;
}
return value[value.length - 1];
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the position of an element in a list
////////////////////////////////////////////////////////////////////////////////
function AQL_POSITION (value, search, returnIndex) {
'use strict';
if (TYPEWEIGHT(value) !== TYPEWEIGHT_ARRAY) {
WARN("POSITION", INTERNAL.errors.ERROR_QUERY_ARRAY_EXPECTED);
return null;
}
returnIndex = returnIndex || false;
var i, n = value.length;
if (n > 0) {
for (i = 0; i < n; ++i) {
if (RELATIONAL_EQUAL(value[i], search)) {
return returnIndex ? i : true;
}
}
}
return returnIndex ? -1 : false;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the nth element in a list, or null if the item does not exist
////////////////////////////////////////////////////////////////////////////////
function AQL_NTH (value, position) {
'use strict';
if (TYPEWEIGHT(value) !== TYPEWEIGHT_ARRAY) {
WARN("NTH", INTERNAL.errors.ERROR_QUERY_ARRAY_EXPECTED);
return null;
}
position = AQL_TO_NUMBER(position);
if (position < 0 || position >= value.length) {
return null;
}
return value[position];
}
////////////////////////////////////////////////////////////////////////////////
/// @brief reverse the elements in a list or in a string
////////////////////////////////////////////////////////////////////////////////
function AQL_REVERSE (value) {
'use strict';
if (TYPEWEIGHT(value) === TYPEWEIGHT_STRING) {
return value.split("").reverse().join("");
}
if (TYPEWEIGHT(value) === TYPEWEIGHT_ARRAY) {
return CLONE(value).reverse();
}
WARN("REVERSE", INTERNAL.errors.ERROR_QUERY_ARRAY_EXPECTED);
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return a range of values
////////////////////////////////////////////////////////////////////////////////
function AQL_RANGE (from, to, step) {
'use strict';
from = AQL_TO_NUMBER(from) || 0;
to = AQL_TO_NUMBER(to) || 0;
if (step === undefined || step === null) {
if (from <= to) {
step = 1;
}
else {
step = -1;
}
}
step = AQL_TO_NUMBER(step);
// check if we would run into an endless loop
if (step === 0 || step === null) {
WARN("RANGE", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
if (from < to && step < 0) {
WARN("RANGE", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
if (from > to && step > 0) {
WARN("RANGE", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
var result = [ ], i;
if (step < 0 && to <= from) {
for (i = from; i >= to; i += step) {
result.push(i);
}
}
else {
for (i = from; i <= to; i += step) {
result.push(i);
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return a list of unique elements from the array
////////////////////////////////////////////////////////////////////////////////
function AQL_UNIQUE (values) {
'use strict';
if (TYPEWEIGHT(values) !== TYPEWEIGHT_ARRAY) {
WARN("UNIQUE", INTERNAL.errors.ERROR_QUERY_ARRAY_EXPECTED);
return null;
}
var keys = { }, result = [ ];
values.forEach(function (value) {
var normalized = NORMALIZE(value);
var key = JSON.stringify(normalized);
if (! keys.hasOwnProperty(key)) {
keys[key] = normalized;
}
});
Object.keys(keys).forEach(function(k) {
result.push(keys[k]);
});
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return a list of unique elements from the array
////////////////////////////////////////////////////////////////////////////////
function AQL_SORTED_UNIQUE (values) {
'use strict';
var unique = AQL_UNIQUE(values);
if (TYPEWEIGHT(unique) !== TYPEWEIGHT_ARRAY) {
return null;
}
unique.sort(RELATIONAL_CMP);
return unique;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create the union (all) of all arguments
////////////////////////////////////////////////////////////////////////////////
function AQL_UNION () {
'use strict';
var result = [ ], i;
for (i in arguments) {
if (arguments.hasOwnProperty(i)) {
var element = arguments[i];
if (TYPEWEIGHT(element) !== TYPEWEIGHT_ARRAY) {
WARN("UNION", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
var n = element.length, j;
for (j = 0; j < n; ++j) {
result.push(element[j]);
}
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create the union (distinct) of all arguments
////////////////////////////////////////////////////////////////////////////////
function AQL_UNION_DISTINCT () {
'use strict';
var keys = { }, i;
for (i in arguments) {
if (arguments.hasOwnProperty(i)) {
var element = arguments[i];
if (TYPEWEIGHT(element) !== TYPEWEIGHT_ARRAY) {
WARN("UNION_DISTINCT", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
var n = element.length, j;
for (j = 0; j < n; ++j) {
var normalized = NORMALIZE(element[j]);
var key = JSON.stringify(normalized);
if (! keys.hasOwnProperty(key)) {
keys[key] = normalized;
}
}
}
}
var result = [ ];
Object.keys(keys).forEach(function(k) {
result.push(keys[k]);
});
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief call a function for each element in the input list
////////////////////////////////////////////////////////////////////////////////
function AQL_CALL (name) {
'use strict';
var args = [ ], i;
for (i = 1; i < arguments.length; ++i) {
args.push(arguments[i]);
}
return FCALL_DYNAMIC("CALL", true, null, name, args);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief call a function for each element in the input list
////////////////////////////////////////////////////////////////////////////////
function AQL_APPLY (name, parameters) {
'use strict';
var args = [ ], i;
if (Array.isArray(parameters)) {
args = args.concat(parameters);
}
return FCALL_DYNAMIC("APPLY", true, null, name, args);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief removes elements from a list
////////////////////////////////////////////////////////////////////////////////
function AQL_REMOVE_VALUES (list, values) {
'use strict';
var type = TYPEWEIGHT(values);
if (type === TYPEWEIGHT_NULL) {
return list;
}
else if (type !== TYPEWEIGHT_ARRAY) {
WARN("REMOVE_VALUES", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
type = TYPEWEIGHT(list);
if (type === TYPEWEIGHT_NULL) {
return [ ];
}
else if (type === TYPEWEIGHT_ARRAY) {
var copy = [ ], i;
for (i = 0; i < list.length; ++i) {
if (RELATIONAL_IN(list[i], values)) {
continue;
}
copy.push(CLONE(list[i]));
}
return copy;
}
WARN("REMOVE_VALUES", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief removes an element from a list
////////////////////////////////////////////////////////////////////////////////
function AQL_REMOVE_VALUE (list, value, limit) {
'use strict';
var type = TYPEWEIGHT(list);
if (type === TYPEWEIGHT_NULL) {
return [ ];
}
else if (type === TYPEWEIGHT_ARRAY) {
if (TYPEWEIGHT(limit) === TYPEWEIGHT_NULL) {
limit = -1;
}
var copy = [ ], i;
for (i = 0; i < list.length; ++i) {
if (limit === -1 && RELATIONAL_CMP(list[i], value) === 0) {
continue;
}
else if (limit > 0 && RELATIONAL_CMP(list[i], value) === 0) {
--limit;
continue;
}
copy.push(CLONE(list[i]));
}
return copy;
}
WARN("REMOVE_VALUE", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief removes an element from a list
////////////////////////////////////////////////////////////////////////////////
function AQL_REMOVE_NTH (list, position) {
'use strict';
var type = TYPEWEIGHT(list);
if (type === TYPEWEIGHT_NULL) {
return [ ];
}
else if (type === TYPEWEIGHT_ARRAY) {
position = AQL_TO_NUMBER(position);
if (position >= list.length || position < - list.length) {
return list;
}
if (position === 0) {
return list.slice(1);
}
else if (position === - list.length) {
return list.slice(position + 1);
}
else if (position === list.length - 1) {
return list.slice(0, position);
}
else if (position < 0) {
return list.slice(0, list.length + position).concat(list.slice(list.length + position + 1));
}
return list.slice(0, position).concat(list.slice(position + 1));
}
WARN("REMOVE_NTH", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief adds an element to a list
////////////////////////////////////////////////////////////////////////////////
function AQL_PUSH (list, value, unique) {
'use strict';
var type = TYPEWEIGHT(list);
if (type === TYPEWEIGHT_NULL) {
return [ value ];
}
else if (type === TYPEWEIGHT_ARRAY) {
if (AQL_TO_BOOL(unique)) {
if (RELATIONAL_IN(value, list)) {
return list;
}
}
var copy = CLONE(list);
copy.push(value);
return copy;
}
WARN("PUSH", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief adds elements to a list
////////////////////////////////////////////////////////////////////////////////
function AQL_APPEND (list, values, unique) {
'use strict';
var type = TYPEWEIGHT(values);
if (type === TYPEWEIGHT_NULL) {
return list;
}
else if (type !== TYPEWEIGHT_ARRAY) {
values = [ values ];
}
if (values.length === 0) {
return list;
}
unique = AQL_TO_BOOL(unique);
if (values.length > 1 && unique) {
// make values unique themselves
values = AQL_UNIQUE(values);
}
type = TYPEWEIGHT(list);
if (type === TYPEWEIGHT_NULL) {
return values;
}
else if (type === TYPEWEIGHT_ARRAY) {
var copy = CLONE(list);
if (unique) {
var i;
for (i = 0; i < values.length; ++i) {
if (RELATIONAL_IN(values[i], list)) {
continue;
}
copy.push(values[i]);
}
return copy;
}
return copy.concat(values);
}
WARN("APPEND", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief pops an element from a list
////////////////////////////////////////////////////////////////////////////////
function AQL_POP (list) {
'use strict';
var type = TYPEWEIGHT(list);
if (type === TYPEWEIGHT_NULL) {
return null;
}
else if (type === TYPEWEIGHT_ARRAY) {
if (list.length === 0) {
return [ ];
}
var copy = CLONE(list);
copy.pop();
return copy;
}
WARN("POP", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief insert an element into a list
////////////////////////////////////////////////////////////////////////////////
function AQL_UNSHIFT (list, value, unique) {
'use strict';
var type = TYPEWEIGHT(list);
if (type === TYPEWEIGHT_NULL) {
return [ value ];
}
else if (type === TYPEWEIGHT_ARRAY) {
if (unique) {
if (RELATIONAL_IN(value, list)) {
return list;
}
}
var copy = CLONE(list);
copy.unshift(value);
return copy;
}
WARN("UNSHIFT", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief pops an element from a list
////////////////////////////////////////////////////////////////////////////////
function AQL_SHIFT (list) {
'use strict';
var type = TYPEWEIGHT(list);
if (type === TYPEWEIGHT_NULL) {
return null;
}
else if (type === TYPEWEIGHT_ARRAY) {
if (list.length === 0) {
return [ ];
}
var copy = CLONE(list);
copy.shift();
return copy;
}
WARN("SHIFT", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief extract a slice from an array
////////////////////////////////////////////////////////////////////////////////
function AQL_SLICE (value, from, to, nonNegative) {
'use strict';
if (TYPEWEIGHT(value) !== TYPEWEIGHT_ARRAY) {
WARN("SLICE", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
from = AQL_TO_NUMBER(from);
if (TYPEWEIGHT(to) !== TYPEWEIGHT_NULL) {
to = AQL_TO_NUMBER(to);
}
if (nonNegative && (from < 0 || to < 0)) {
return [ ];
}
if (TYPEWEIGHT(to) === TYPEWEIGHT_NULL) {
to = undefined;
}
else {
if (to >= 0) {
to += from;
}
}
return value.slice(from, to);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief subtract lists from other lists
////////////////////////////////////////////////////////////////////////////////
function AQL_MINUS () {
'use strict';
var keys = { }, i, first = true;
for (i in arguments) {
if (arguments.hasOwnProperty(i)) {
var element = arguments[i];
if (TYPEWEIGHT(element) !== TYPEWEIGHT_ARRAY) {
WARN("MINUS", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
var n = element.length, j;
for (j = 0; j < n; ++j) {
var normalized = NORMALIZE(element[j]);
var key = JSON.stringify(normalized);
var contained = keys.hasOwnProperty(key);
if (first) {
if (! contained) {
keys[key] = normalized;
}
}
else if (contained) {
delete keys[key];
}
}
first = false;
}
}
var result = [ ];
Object.keys(keys).forEach(function(k) {
result.push(keys[k]);
});
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create the intersection of all arguments
////////////////////////////////////////////////////////////////////////////////
function AQL_INTERSECTION () {
'use strict';
var result = [ ], i, first = true, keys = { };
var func = function (value) {
var normalized = NORMALIZE(value);
keys[JSON.stringify(normalized)] = normalized;
};
for (i in arguments) {
if (arguments.hasOwnProperty(i)) {
var element = arguments[i];
if (TYPEWEIGHT(element) !== TYPEWEIGHT_ARRAY) {
WARN("INTERSECTION", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
if (first) {
element.forEach(func);
first = false;
}
else {
var j, newKeys = { };
for (j = 0; j < element.length; ++j) {
var normalized = NORMALIZE(element[j]);
var key = JSON.stringify(normalized);
if (keys.hasOwnProperty(key)) {
newKeys[key] = normalized;
}
}
keys = newKeys;
newKeys = null;
}
}
}
Object.keys(keys).forEach(function(k) {
result.push(keys[k]);
});
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief flatten a list of lists
////////////////////////////////////////////////////////////////////////////////
function AQL_FLATTEN (values, maxDepth, depth) {
'use strict';
if (TYPEWEIGHT(values) !== TYPEWEIGHT_ARRAY) {
WARN("FLATTEN", INTERNAL.errors.ERROR_QUERY_ARRAY_EXPECTED);
return null;
}
maxDepth = AQL_TO_NUMBER(maxDepth);
if (TYPEWEIGHT(maxDepth) === TYPEWEIGHT_NULL || maxDepth < 1) {
maxDepth = 1;
}
if (TYPEWEIGHT(depth) === TYPEWEIGHT_NULL) {
depth = 0;
}
var value, result = [ ];
var i, n;
var p = function(v) {
result.push(v);
};
for (i = 0, n = values.length; i < n; ++i) {
value = values[i];
if (depth < maxDepth && TYPEWEIGHT(value) === TYPEWEIGHT_ARRAY) {
AQL_FLATTEN(value, maxDepth, depth + 1).forEach(p);
}
else {
result.push(value);
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief maximum of all values
////////////////////////////////////////////////////////////////////////////////
function AQL_MAX (values) {
'use strict';
if (TYPEWEIGHT(values) !== TYPEWEIGHT_ARRAY) {
WARN("MAX", INTERNAL.errors.ERROR_QUERY_ARRAY_EXPECTED);
return null;
}
var value, result = null;
var i, n;
for (i = 0, n = values.length; i < n; ++i) {
value = values[i];
if (TYPEWEIGHT(value) !== TYPEWEIGHT_NULL) {
if (result === null || RELATIONAL_GREATER(value, result)) {
result = value;
}
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief minimum of all values
////////////////////////////////////////////////////////////////////////////////
function AQL_MIN (values) {
'use strict';
if (TYPEWEIGHT(values) !== TYPEWEIGHT_ARRAY) {
WARN("MIN", INTERNAL.errors.ERROR_QUERY_ARRAY_EXPECTED);
return null;
}
var value, result = null;
var i, n;
for (i = 0, n = values.length; i < n; ++i) {
value = values[i];
if (TYPEWEIGHT(value) !== TYPEWEIGHT_NULL) {
if (result === null || RELATIONAL_LESS(value, result)) {
result = value;
}
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief sum of all values
////////////////////////////////////////////////////////////////////////////////
function AQL_SUM (values) {
'use strict';
if (TYPEWEIGHT(values) !== TYPEWEIGHT_ARRAY) {
WARN("SUM", INTERNAL.errors.ERROR_QUERY_ARRAY_EXPECTED);
return null;
}
var i, n;
var value, typeWeight, result = 0;
for (i = 0, n = values.length; i < n; ++i) {
value = values[i];
typeWeight = TYPEWEIGHT(value);
if (typeWeight !== TYPEWEIGHT_NULL) {
if (typeWeight !== TYPEWEIGHT_NUMBER) {
WARN("SUM", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
result += value;
}
}
return NUMERIC_VALUE(result);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief average of all values
////////////////////////////////////////////////////////////////////////////////
function AQL_AVERAGE (values) {
'use strict';
if (TYPEWEIGHT(values) !== TYPEWEIGHT_ARRAY) {
WARN("AVERAGE", INTERNAL.errors.ERROR_QUERY_ARRAY_EXPECTED);
return null;
}
var current, typeWeight, sum = 0;
var i, j, n;
for (i = 0, j = 0, n = values.length; i < n; ++i) {
current = values[i];
typeWeight = TYPEWEIGHT(current);
if (typeWeight !== TYPEWEIGHT_NULL) {
if (typeWeight !== TYPEWEIGHT_NUMBER) {
WARN("AVERAGE", INTERNAL.errors.ERROR_QUERY_INVALID_ARITHMETIC_VALUE);
return null;
}
sum += current;
j++;
}
}
if (j === 0) {
return null;
}
return NUMERIC_VALUE(sum / j);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief median of all values
////////////////////////////////////////////////////////////////////////////////
function AQL_MEDIAN (values) {
'use strict';
if (TYPEWEIGHT(values) !== TYPEWEIGHT_ARRAY) {
WARN("MEDIAN", INTERNAL.errors.ERROR_QUERY_ARRAY_EXPECTED);
return null;
}
var copy = [ ], current, typeWeight;
var i, n;
for (i = 0, n = values.length; i < n; ++i) {
current = values[i];
typeWeight = TYPEWEIGHT(current);
if (typeWeight !== TYPEWEIGHT_NULL) {
if (typeWeight !== TYPEWEIGHT_NUMBER) {
WARN("MEDIAN", INTERNAL.errors.ERROR_QUERY_INVALID_ARITHMETIC_VALUE);
return null;
}
copy.push(current);
}
}
if (copy.length === 0) {
return null;
}
copy.sort(RELATIONAL_CMP);
var midpoint = copy.length / 2;
if (copy.length % 2 === 0) {
return NUMERIC_VALUE((copy[midpoint - 1] + copy[midpoint]) / 2);
}
return NUMERIC_VALUE(copy[Math.floor(midpoint)]);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief returns the pth percentile of all values
////////////////////////////////////////////////////////////////////////////////
function AQL_PERCENTILE (values, p, method) {
'use strict';
if (TYPEWEIGHT(values) !== TYPEWEIGHT_ARRAY) {
WARN("PERCENTILE", INTERNAL.errors.ERROR_QUERY_ARRAY_EXPECTED);
return null;
}
if (TYPEWEIGHT(p) !== TYPEWEIGHT_NUMBER) {
WARN("PERCENTILE", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
if (p <= 0 || p > 100) {
WARN("PERCENTILE", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
if (method === null || method === undefined) {
method = "rank";
}
if (method !== "interpolation" && method !== "rank") {
WARN("PERCENTILE", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
var copy = [ ], current, typeWeight;
var i, n;
for (i = 0, n = values.length; i < n; ++i) {
current = values[i];
typeWeight = TYPEWEIGHT(current);
if (typeWeight !== TYPEWEIGHT_NULL) {
if (typeWeight !== TYPEWEIGHT_NUMBER) {
WARN("PERCENTILE", INTERNAL.errors.ERROR_QUERY_INVALID_ARITHMETIC_VALUE);
return null;
}
copy.push(current);
}
}
if (copy.length === 0) {
return null;
}
else if (copy.length === 1) {
return copy[0];
}
copy.sort(RELATIONAL_CMP);
var idx, pos;
if (method === "interpolation") {
// interpolation method
idx = p * (copy.length + 1) / 100;
pos = Math.floor(idx);
var delta = idx - pos;
if (pos >= copy.length) {
return NUMERIC_VALUE(copy[copy.length - 1]);
}
return NUMERIC_VALUE(delta * (copy[pos] - copy[pos - 1]) + copy[pos - 1]);
}
else {
// rank method
idx = p * (copy.length) / 100;
pos = Math.ceil(idx);
if (pos >= copy.length) {
return NUMERIC_VALUE(copy[copy.length - 1]);
}
return NUMERIC_VALUE(copy[pos - 1]);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief variance of all values
////////////////////////////////////////////////////////////////////////////////
function VARIANCE (values) {
'use strict';
if (TYPEWEIGHT(values) !== TYPEWEIGHT_ARRAY) {
WARN("VARIANCE", INTERNAL.errors.ERROR_QUERY_ARRAY_EXPECTED);
return null;
}
var mean = 0, m2 = 0, current, typeWeight, delta;
var i, j, n;
for (i = 0, j = 0, n = values.length; i < n; ++i) {
current = values[i];
typeWeight = TYPEWEIGHT(current);
if (typeWeight !== TYPEWEIGHT_NULL) {
if (typeWeight !== TYPEWEIGHT_NUMBER) {
WARN("VARIANCE", INTERNAL.errors.ERROR_QUERY_INVALID_ARITHMETIC_VALUE);
return null;
}
delta = current - mean;
mean += delta / ++j;
m2 += delta * (current - mean);
}
}
return {
n: j,
value: m2
};
}
////////////////////////////////////////////////////////////////////////////////
/// @brief sample variance of all values
////////////////////////////////////////////////////////////////////////////////
function AQL_VARIANCE_SAMPLE (values) {
'use strict';
var result = VARIANCE(values);
if (result === null || result === undefined) {
return null;
}
if (result.n < 2) {
return null;
}
return NUMERIC_VALUE(result.value / (result.n - 1));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief population variance of all values
////////////////////////////////////////////////////////////////////////////////
function AQL_VARIANCE_POPULATION (values) {
'use strict';
var result = VARIANCE(values);
if (result === null || result === undefined) {
return null;
}
if (result.n < 1) {
return null;
}
return NUMERIC_VALUE(result.value / result.n);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief standard deviation of all values
////////////////////////////////////////////////////////////////////////////////
function AQL_STDDEV_SAMPLE (values) {
'use strict';
var result = VARIANCE(values);
if (result === null || result === undefined) {
return null;
}
if (result.n < 2) {
return null;
}
return NUMERIC_VALUE(Math.sqrt(result.value / (result.n - 1)));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief standard deviation of all values
////////////////////////////////////////////////////////////////////////////////
function AQL_STDDEV_POPULATION (values) {
'use strict';
var result = VARIANCE(values);
if (result === null || result === undefined) {
return null;
}
if (result.n < 1) {
return null;
}
return NUMERIC_VALUE(Math.sqrt(result.value / result.n));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return at most <limit> documents near a certain point
////////////////////////////////////////////////////////////////////////////////
function AQL_NEAR (collection, latitude, longitude, limit, distanceAttribute) {
'use strict';
if (TYPEWEIGHT(limit) === TYPEWEIGHT_NULL) {
// use default value
limit = 100;
}
else {
if (TYPEWEIGHT(limit) !== TYPEWEIGHT_NUMBER) {
THROW("NEAR", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
}
limit = AQL_TO_NUMBER(limit);
}
var weight = TYPEWEIGHT(distanceAttribute);
if (weight !== TYPEWEIGHT_NULL && weight !== TYPEWEIGHT_STRING) {
THROW("NEAR", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
}
if (isCoordinator) {
var query = COLLECTION(collection, "NEAR").near(latitude, longitude);
query._distance = distanceAttribute;
return query.limit(limit).toArray();
}
var idx = INDEX(COLLECTION(collection, "NEAR"), [ "geo1", "geo2" ]);
if (idx === null) {
THROW("NEAR", INTERNAL.errors.ERROR_QUERY_GEO_INDEX_MISSING, collection);
}
return COLLECTION(collection, "NEAR").NEAR(idx.id, latitude, longitude, limit, distanceAttribute);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return documents within <radius> around a certain point
////////////////////////////////////////////////////////////////////////////////
function AQL_WITHIN (collection, latitude, longitude, radius, distanceAttribute) {
'use strict';
var weight = TYPEWEIGHT(distanceAttribute);
if (weight !== TYPEWEIGHT_NULL && weight !== TYPEWEIGHT_STRING) {
THROW("WITHIN", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
}
weight = TYPEWEIGHT(radius);
if (weight !== TYPEWEIGHT_NULL && weight !== TYPEWEIGHT_NUMBER) {
THROW("WITHIN", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
}
radius = AQL_TO_NUMBER(radius);
if (isCoordinator) {
var query = COLLECTION(collection, "WITHIN").within(latitude, longitude, radius);
query._distance = distanceAttribute;
return query.toArray();
}
var idx = INDEX(COLLECTION(collection, "WITHIN"), [ "geo1", "geo2" ]);
if (idx === null) {
THROW("WITHIN", INTERNAL.errors.ERROR_QUERY_GEO_INDEX_MISSING, collection);
}
return COLLECTION(collection, "WITHIN").WITHIN(idx.id, latitude, longitude, radius, distanceAttribute);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return documents within a bounding rectangle
////////////////////////////////////////////////////////////////////////////////
function AQL_WITHIN_RECTANGLE (collection, latitude1, longitude1, latitude2, longitude2) {
'use strict';
if (TYPEWEIGHT(latitude1) !== TYPEWEIGHT_NUMBER ||
TYPEWEIGHT(longitude1) !== TYPEWEIGHT_NUMBER ||
TYPEWEIGHT(latitude2) !== TYPEWEIGHT_NUMBER ||
TYPEWEIGHT(longitude2) !== TYPEWEIGHT_NUMBER) {
WARN("WITHIN_RECTANGLE", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
return COLLECTION(collection, "WITHIN_RECTANGLE").withinRectangle(
latitude1,
longitude1,
latitude2,
longitude2
).toArray();
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return true if a point is contained inside a polygon
////////////////////////////////////////////////////////////////////////////////
function AQL_IS_IN_POLYGON (points, latitude, longitude) {
'use strict';
if (TYPEWEIGHT(points) !== TYPEWEIGHT_ARRAY) {
WARN("POINT_IN_POLYGON", INTERNAL.errors.ERROR_QUERY_ARRAY_EXPECTED);
return false;
}
var searchLat, searchLon, pointLat, pointLon, geoJson = false;
if (TYPEWEIGHT(latitude) === TYPEWEIGHT_ARRAY) {
geoJson = AQL_TO_BOOL(longitude);
if (geoJson) {
// first list value is longitude, then latitude
searchLat = latitude[1];
searchLon = latitude[0];
pointLat = 1;
pointLon = 0;
}
else {
// first list value is latitude, then longitude
searchLat = latitude[0];
searchLon = latitude[1];
pointLat = 0;
pointLon = 1;
}
}
else if (TYPEWEIGHT(latitude) === TYPEWEIGHT_NUMBER &&
TYPEWEIGHT(longitude) === TYPEWEIGHT_NUMBER) {
searchLat = latitude;
searchLon = longitude;
pointLat = 0;
pointLon = 1;
}
else {
WARN("POINT_IN_POLYGON", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return false;
}
var i, j = points.length - 1;
var oddNodes = false;
for (i = 0; i < points.length; ++i) {
if (TYPEWEIGHT(points[i]) !== TYPEWEIGHT_ARRAY) {
continue;
}
if (((points[i][pointLat] < searchLat && points[j][pointLat] >= searchLat) ||
(points[j][pointLat] < searchLat && points[i][pointLat] >= searchLat)) &&
(points[i][pointLon] <= searchLon || points[j][pointLon] <= searchLon)) {
oddNodes ^= ((points[i][pointLon] + (searchLat - points[i][pointLat]) /
(points[j][pointLat] - points[i][pointLat]) *
(points[j][pointLon] - points[i][pointLon])) < searchLon);
}
j = i;
}
if (oddNodes) {
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return documents that match a fulltext query
////////////////////////////////////////////////////////////////////////////////
function AQL_FULLTEXT (collection, attribute, query, limit) {
'use strict';
var idx = INDEX_FULLTEXT(COLLECTION(collection, "FULLTEXT"), attribute);
if (idx === null) {
THROW("FULLTEXT", INTERNAL.errors.ERROR_QUERY_FULLTEXT_INDEX_MISSING, collection);
}
if (isCoordinator) {
if (limit !== undefined && limit !== null && limit > 0) {
return COLLECTION(collection, "FULLTEXT").fulltext(attribute, query, idx).limit(limit).toArray();
}
return COLLECTION(collection, "FULLTEXT").fulltext(attribute, query, idx).toArray();
}
return COLLECTION(collection, "FULLTEXT").FULLTEXT(idx, query, limit).documents;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the first alternative that's not null until there are no more
/// alternatives. if neither of the alternatives is a value other than null,
/// then null will be returned
///
/// the operands can have any type
////////////////////////////////////////////////////////////////////////////////
function AQL_NOT_NULL () {
'use strict';
var i;
for (i in arguments) {
if (arguments.hasOwnProperty(i)) {
var element = arguments[i];
if (TYPEWEIGHT(element) !== TYPEWEIGHT_NULL) {
return element;
}
}
}
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the first alternative that's a list until there are no more
/// alternatives. if neither of the alternatives is a list, then null will be
/// returned
///
/// the operands can have any type
////////////////////////////////////////////////////////////////////////////////
function AQL_FIRST_LIST () {
'use strict';
var i;
for (i in arguments) {
if (arguments.hasOwnProperty(i)) {
var element = arguments[i];
if (TYPEWEIGHT(element) === TYPEWEIGHT_ARRAY) {
return element;
}
}
}
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the first alternative that's a document until there are no
/// more alternatives. if neither of the alternatives is a document, then null
/// will be returned
///
/// the operands can have any type
////////////////////////////////////////////////////////////////////////////////
function AQL_FIRST_DOCUMENT () {
'use strict';
var i;
for (i in arguments) {
if (arguments.hasOwnProperty(i)) {
var element = arguments[i];
if (TYPEWEIGHT(element) === TYPEWEIGHT_OBJECT) {
return element;
}
}
}
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the parts of a document identifier separately
///
/// returns a document with the attributes `collection` and `key` or fails if
/// the individual parts cannot be determined.
////////////////////////////////////////////////////////////////////////////////
function AQL_PARSE_IDENTIFIER (value) {
'use strict';
if (TYPEWEIGHT(value) === TYPEWEIGHT_STRING) {
var parts = value.split('/');
if (parts.length === 2) {
return {
collection: parts[0],
key: parts[1]
};
}
// fall through intentional
}
else if (TYPEWEIGHT(value) === TYPEWEIGHT_OBJECT) {
if (value.hasOwnProperty('_id')) {
return AQL_PARSE_IDENTIFIER(value._id);
}
// fall through intentional
}
WARN("PARSE_IDENTIFIER", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief validates if a document or object is from the specified collection
////////////////////////////////////////////////////////////////////////////////
function AQL_IS_SAME_COLLECTION (collection, value) {
'use strict';
if (TYPEWEIGHT(value) === TYPEWEIGHT_OBJECT) {
if (value.hasOwnProperty('_id')) {
value = value._id;
}
}
if (TYPEWEIGHT(value) === TYPEWEIGHT_STRING) {
var pos = value.indexOf('/');
if (pos !== -1) {
return value.substr(0, pos) === collection;
}
// fall through intentional
}
WARN("AQL_IS_SAME_COLLECTION", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief check whether a document has a specific attribute
////////////////////////////////////////////////////////////////////////////////
function AQL_HAS (element, name) {
'use strict';
if (TYPEWEIGHT(element) !== TYPEWEIGHT_OBJECT) {
return false;
}
return element.hasOwnProperty(AQL_TO_STRING(name));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the attribute names of a document as a list
////////////////////////////////////////////////////////////////////////////////
function AQL_ATTRIBUTES (element, removeInternal, sort) {
'use strict';
if (TYPEWEIGHT(element) !== TYPEWEIGHT_OBJECT) {
WARN("ATTRIBUTES", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
if (removeInternal) {
var result = [ ];
Object.keys(element).forEach(function(k) {
if (k[0] !== '_') {
result.push(k);
}
});
if (sort) {
result.sort();
}
return result;
}
return KEYS(element, sort);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the attribute values of a document as a list
////////////////////////////////////////////////////////////////////////////////
function AQL_VALUES (element, removeInternal) {
'use strict';
if (TYPEWEIGHT(element) !== TYPEWEIGHT_OBJECT) {
WARN("VALUES", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
var result = [ ], a;
for (a in element) {
if (element.hasOwnProperty(a)) {
if (a[0] !== '_' || ! removeInternal) {
result.push(element[a]);
}
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief assemble a document from two lists
////////////////////////////////////////////////////////////////////////////////
function AQL_ZIP (keys, values) {
'use strict';
if (TYPEWEIGHT(keys) !== TYPEWEIGHT_ARRAY ||
TYPEWEIGHT(values) !== TYPEWEIGHT_ARRAY ||
keys.length !== values.length) {
WARN("ZIP", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
var result = { }, i, n = keys.length;
for (i = 0; i < n; ++i) {
result[AQL_TO_STRING(keys[i])] = values[i];
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief unset specific attributes from a document
////////////////////////////////////////////////////////////////////////////////
function AQL_UNSET (value) {
'use strict';
if (TYPEWEIGHT(value) !== TYPEWEIGHT_OBJECT) {
WARN("UNSET", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
var result = { }, keys = EXTRACT_KEYS(arguments, 1, "UNSET");
// copy over all that is left
for (var k in value) {
if (value.hasOwnProperty(k) && ! keys.hasOwnProperty(k)) {
result[k] = CLONE(value[k]);
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief unset specific attributes from a document, recursively
////////////////////////////////////////////////////////////////////////////////
function AQL_UNSET_RECURSIVE (value) {
'use strict';
if (TYPEWEIGHT(value) !== TYPEWEIGHT_OBJECT) {
WARN("UNSET", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
var keys = EXTRACT_KEYS(arguments, 1, "UNSET");
// copy over all that is left
var func = function (value) {
var result = { };
for (var k in value) {
if (value.hasOwnProperty(k) && ! keys.hasOwnProperty(k)) {
if (TYPEWEIGHT(value[k]) === TYPEWEIGHT_OBJECT) {
result[k] = func(value[k], keys);
}
else {
result[k] = CLONE(value[k]);
}
}
}
return result;
};
return func(value);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief keep specific attributes from a document
////////////////////////////////////////////////////////////////////////////////
function AQL_KEEP (value) {
'use strict';
if (TYPEWEIGHT(value) !== TYPEWEIGHT_OBJECT) {
WARN("KEEP", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
var result = { }, keys = EXTRACT_KEYS(arguments, 1, "KEEP");
// copy over all that is left
for (var k in value) {
if (value.hasOwnProperty(k) && keys.hasOwnProperty(k)) {
result[k] = CLONE(value[k]);
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief merge all arguments
////////////////////////////////////////////////////////////////////////////////
function AQL_MERGE () {
'use strict';
var result = { };
var add = function (element) {
for (var k in element) {
if (element.hasOwnProperty(k)) {
result[k] = element[k];
}
}
};
var i, element;
if (arguments.length === 1) {
element = arguments[0];
if (TYPEWEIGHT(element) !== TYPEWEIGHT_ARRAY) {
WARN("MERGE", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
for (i = 0; i < element.length; ++i) {
if (TYPEWEIGHT(element[i]) !== TYPEWEIGHT_OBJECT) {
WARN("MERGE", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
add(element[i]);
}
return result;
}
var j = 0;
for (i in arguments) {
if (arguments.hasOwnProperty(i)) {
element = arguments[i];
if (TYPEWEIGHT(element) !== TYPEWEIGHT_OBJECT) {
WARN("MERGE", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
if (j === 0) {
result = element;
}
else {
add(element);
}
++j;
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief merge all arguments recursively
////////////////////////////////////////////////////////////////////////////////
function AQL_MERGE_RECURSIVE () {
'use strict';
var result = { }, i, recurse;
recurse = function (old, element) {
var r = CLONE(old);
Object.keys(element).forEach(function(k) {
if (r.hasOwnProperty(k) && TYPEWEIGHT(element[k]) === TYPEWEIGHT_OBJECT) {
r[k] = recurse(r[k], element[k]);
}
else {
r[k] = element[k];
}
});
return r;
};
for (i in arguments) {
if (arguments.hasOwnProperty(i)) {
var element = arguments[i];
if (TYPEWEIGHT(element) !== TYPEWEIGHT_OBJECT) {
WARN("MERGE_RECURSIVE", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
result = recurse(result, element);
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief translate a value, using a lookup document
////////////////////////////////////////////////////////////////////////////////
function AQL_TRANSLATE (value, lookup, defaultValue) {
'use strict';
if (TYPEWEIGHT(lookup) !== TYPEWEIGHT_OBJECT) {
WARN("TRANSLATE", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
if (defaultValue === undefined) {
defaultValue = value;
}
var key = AQL_TO_STRING(value);
if (lookup.hasOwnProperty(key)) {
return lookup[key];
}
return defaultValue;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief compare an object against a list of examples and return whether the
/// object matches any of the examples. returns the example index or a bool,
/// depending on the value of the control flag (3rd) parameter
////////////////////////////////////////////////////////////////////////////////
function AQL_MATCHES (element, examples, returnIndex) {
'use strict';
if (TYPEWEIGHT(element) !== TYPEWEIGHT_OBJECT) {
return false;
}
if (! Array.isArray(examples)) {
examples = [ examples ];
}
if (examples.length === 0) {
WARN("MATCHES", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return false;
}
returnIndex = returnIndex || false;
var i;
for (i = 0; i < examples.length; ++i) {
var example = examples[i];
var result = true;
if (TYPEWEIGHT(example) !== TYPEWEIGHT_OBJECT) {
WARN("MATCHES", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
continue;
}
var keys = KEYS(example), key, j;
for (j = 0; j < keys.length; ++j) {
key = keys[j];
if (! RELATIONAL_EQUAL(element[key], example[key])) {
result = false;
break;
}
}
if (result) {
// 3rd parameter determines whether we return the index or a bool flag
return (returnIndex ? i : true);
}
}
return (returnIndex ? -1 : false);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief passthru the argument
///
/// this function is marked as non-deterministic so its argument withstands
/// query optimisation. this function can be used for testing
////////////////////////////////////////////////////////////////////////////////
function AQL_PASSTHRU (value) {
'use strict';
return value;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief test helper function
/// this is no actual function the end user should call
////////////////////////////////////////////////////////////////////////////////
function AQL_TEST_INTERNAL (test, what) {
'use strict';
if (test === 'MODIFY_ARRAY') {
what[0] = 1;
what[1] = 42;
what[2] = [ 1, 2 ];
what[3].push([ 1, 2 ]);
what[4] = { a: 9, b: 2 };
what.push("foo");
what.push("bar");
what.pop();
}
else if (test === 'MODIFY_OBJECT') {
what.a = 1;
what.b = 3;
what.c = [ 1, 2 ];
what.d.push([ 1, 2 ]);
what.e.f = { a: 1, b: 2 };
delete what.f;
what.g = "foo";
}
else if (test === 'DEADLOCK') {
var err = new ArangoError();
err.errorNum = INTERNAL.errors.ERROR_DEADLOCK.code;
err.errorMessage = INTERNAL.errors.ERROR_DEADLOCK.message;
throw err;
}
return what;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief sleep
///
/// sleep for the specified duration
////////////////////////////////////////////////////////////////////////////////
function AQL_SLEEP (duration) {
'use strict';
duration = AQL_TO_NUMBER(duration);
if (TYPEWEIGHT(duration) !== TYPEWEIGHT_NUMBER || duration < 0) {
WARN("SLEEP", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
AQL_QUERY_SLEEP(duration);
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the current user
/// note: this might be null if the query is not executed in a context that
/// has a user
////////////////////////////////////////////////////////////////////////////////
function AQL_CURRENT_USER () {
'use strict';
if (INTERNAL.getCurrentRequest) {
var req = INTERNAL.getCurrentRequest();
if (typeof req === 'object') {
return req.user;
}
}
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the current database name
/// has a user
////////////////////////////////////////////////////////////////////////////////
function AQL_CURRENT_DATABASE () {
'use strict';
return INTERNAL.db._name();
}
////////////////////////////////////////////////////////////////////////////////
/// @brief always fail
///
/// this function is non-deterministic so it is not executed at query
/// optimisation time. this function can be used for testing
////////////////////////////////////////////////////////////////////////////////
function AQL_FAIL (message) {
'use strict';
if (TYPEWEIGHT(message) === TYPEWEIGHT_STRING) {
THROW("FAIL", INTERNAL.errors.ERROR_QUERY_FAIL_CALLED, message);
}
THROW("FAIL", INTERNAL.errors.ERROR_QUERY_FAIL_CALLED, "");
}
////////////////////////////////////////////////////////////////////////////////
/// @brief helper function for date creation
////////////////////////////////////////////////////////////////////////////////
function MAKE_DATE (args, func) {
'use strict';
var weight;
var i, n = args.length;
if (n === 1) {
// called with one argument only
weight = TYPEWEIGHT(args[0]);
if (weight !== TYPEWEIGHT_NUMBER) {
if (weight !== TYPEWEIGHT_STRING) {
WARN(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
// argument is a string
// append zulu time specifier if no other present
if (! args[0].match(/([zZ]|[+\-]\d+(:\d+)?)$/) ||
(args[0].match(/-\d+(:\d+)?$/) && ! args[0].match(/[tT ]/))) {
args[0] += 'Z';
}
}
// detect invalid dates ("foo" -> "fooZ" -> getTime() == NaN)
var date = new Date(args[0]);
if (isNaN(date)) {
WARN(func, INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE, func);
return null;
}
return date;
}
// called with more than one argument
if (n < 3) {
WARN(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH, func);
return null;
}
for (i = 0; i < n; ++i) {
weight = TYPEWEIGHT(args[i]);
if (weight === TYPEWEIGHT_NULL) {
args[i] = 0;
}
else {
if (weight === TYPEWEIGHT_STRING) {
args[i] = parseInt(args[i], 10);
}
else if (weight !== TYPEWEIGHT_NUMBER) {
WARN(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
if (args[i] < 0) {
WARN(func, INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE, func);
return null;
}
if (i === 1) {
// an exception for handling months: months are 0-based in JavaScript,
// but 1-based in AQL
args[i]--;
}
}
}
var result = new Date(Date.UTC.apply(null, args));
if (TYPEWEIGHT(result) !== TYPEWEIGHT_NULL) {
return result;
}
// avoid returning NaN here
return null;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the number of milliseconds since the Unix epoch
///
/// this function is evaluated on every call
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_NOW () {
'use strict';
return Date.now();
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the timestamp of the date passed (in milliseconds)
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_TIMESTAMP () {
'use strict';
try {
return MAKE_DATE(arguments, "DATE_TIMESTAMP").getTime();
}
catch (err) {
WARN("DATE_TIMESTAMP", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the ISO string representation of the date passed
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_ISO8601 () {
'use strict';
try {
var dt = MAKE_DATE(arguments, "DATE_ISO8601");
if (dt === null) {
return dt;
}
return dt.toISOString();
}
catch (err) {
WARN("DATE_ISO8601", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the weekday of the date passed (0 = Sunday, 1 = Monday etc.)
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_DAYOFWEEK (value) {
'use strict';
try {
return MAKE_DATE([ value ], "DATE_DAYOFWEEK").getUTCDay();
}
catch (err) {
WARN("DATE_DAYOFWEEK", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the year of the date passed
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_YEAR (value) {
'use strict';
try {
return MAKE_DATE([ value ], "DATE_YEAR").getUTCFullYear();
}
catch (err) {
WARN("DATE_YEAR", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the month of the date passed
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_MONTH (value) {
'use strict';
try {
return MAKE_DATE([ value ], "DATE_MONTH").getUTCMonth() + 1;
}
catch (err) {
WARN("DATE_MONTH", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the day of the date passed
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_DAY (value) {
'use strict';
try {
return MAKE_DATE([ value ], "DATE_DAY").getUTCDate();
}
catch (err) {
WARN("DATE_DAY", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the hours of the date passed
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_HOUR (value) {
'use strict';
try {
return MAKE_DATE([ value ], "DATE_HOUR").getUTCHours();
}
catch (err) {
WARN("DATE_HOUR", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the minutes of the date passed
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_MINUTE (value) {
'use strict';
try {
return MAKE_DATE([ value ], "DATE_MINUTE").getUTCMinutes();
}
catch (err) {
WARN("DATE_MINUTE", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the seconds of the date passed
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_SECOND (value) {
'use strict';
try {
return MAKE_DATE([ value ], "DATE_SECOND").getUTCSeconds();
}
catch (err) {
WARN("DATE_SECOND", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the milliseconds of the date passed
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_MILLISECOND (value) {
'use strict';
try {
return MAKE_DATE([ value ], "DATE_MILLISECOND").getUTCMilliseconds();
}
catch (err) {
WARN("DATE_MILLISECOND", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the day of the year of the date passed
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_DAYOFYEAR (value) {
'use strict';
try {
var date = MAKE_DATE([ value ], "DATE_DAYOFYEAR");
var m = date.getUTCMonth();
var d = date.getUTCDate();
var ly = AQL_DATE_LEAPYEAR(date.getTime());
// we could duplicate the leap year code here to avoid an extra MAKE_DATE() call...
//var yr = date.getUTCFullYear();
//var ly = !((yr % 4) || (!(yr % 100) && (yr % 400)));
return (ly ? (dayOfLeapYearOffsets[m] + d) : (dayOfYearOffsets[m] + d));
}
catch (err) {
WARN("DATE_DAYOFYEAR", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the ISO week date of the date passed (1..53)
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_ISOWEEK (value) {
'use strict';
try {
var date = MAKE_DATE([ value ], "DATE_ISOWEEK");
date.setUTCHours(0, 0, 0, 0);
date.setUTCDate(date.getUTCDate() + 4 - (date.getUTCDay() || 7));
return Math.ceil((((date - Date.UTC(date.getUTCFullYear(), 0, 1)) / 864e5) + 1) / 7);
}
catch (err) {
WARN("DATE_ISOWEEK", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return if year of the date passed is a leap year
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_LEAPYEAR (value) {
'use strict';
try {
var yr = MAKE_DATE([ value ], "DATE_LEAPYEAR").getUTCFullYear();
return ((yr % 4 === 0) && (yr % 100 !== 0)) || (yr % 400 === 0);
}
catch (err) {
WARN("DATE_LEAPYEAR", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the ISO week date of the date passed (1..53)
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_QUARTER (value) {
'use strict';
try {
return MAKE_DATE([ value ], "DATE_QUARTER").getUTCMonth() / 3 + 1 | 0;
}
catch (err) {
WARN("DATE_QUARTER", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return number of days in month of date passed (leap year aware)
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_DAYS_IN_MONTH (value) {
'use strict';
try {
var date = MAKE_DATE([ value ], "DATE_DAYS_IN_MONTH");
var month = date.getUTCMonth() + 1;
var ly = AQL_DATE_LEAPYEAR(date.getTime());
return daysInMonth[month === 2 && ly ? 0 : month];
}
catch (err) {
WARN("DATE_DAYS_IN_MONTH", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief internal function to add to or subtract from given date
////////////////////////////////////////////////////////////////////////////////
function DATE_CALC (value, amount, unit, func) {
'use strict';
try {
// TODO: check if isNaN? If called by AQL FOR-loop, should query throw
// and terminate immediately, or return a bunch of 'null's? If it shall
// stop, then best handled in MAKE_DATE() itself I guess.
var date = MAKE_DATE([ value ], func);
if (date === null) {
WARN(func, INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
var sign = (func === "DATE_ADD" || func === undefined) ? 1 : -1;
var m;
// if amount is not a number, then it must be an ISO duration string
if (TYPEWEIGHT(unit) === TYPEWEIGHT_NULL) {
if (TYPEWEIGHT(amount) !== TYPEWEIGHT_STRING) {
WARN(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
// ISO duration cache
var duration;
if (ISODurationCache[amount] === undefined) {
duration = ISODurationRegex.exec(amount);
if (!duration) {
WARN(func, INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
ISODurationCache[amount] = duration;
} else {
duration = ISODurationCache[amount];
}
// ensure ms has a length of 3
if (duration[8] && duration[8].length !== 3) {
duration[8] = zeropad(duration[8], 3, true).substring(0, 3);
}
// add or subtract component by component, from ms to year
for (var d = duration.length - 1; d >= 1; d--) {
if (duration[d]) {
// convert weeks to days
if (d === 3) {
m = unitMapping[unitMappingArray[4]];
date[m[2]](date[m[1]]() + duration[d] * sign * 7);
} else {
m = unitMapping[unitMappingArray[d]];
date[m[2]](date[m[1]]() + duration[d] * sign);
}
}
}
return date.toISOString();
} else {
if (TYPEWEIGHT(unit) !== TYPEWEIGHT_STRING) {
WARN(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
if (TYPEWEIGHT(amount) !== TYPEWEIGHT_NUMBER) {
WARN(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
m = unitMapping[unit.toLowerCase()]; // we're sure unit is a string here
if (m === "undefined") {
WARN(func, INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
} else if (m[0] === "w") {
m = unitMapping.d;
date[m[2]](date[m[1]]() + amount * 7 * sign);
} else {
date[m[2]](date[m[1]]() + amount * sign);
}
return date.toISOString();
}
}
catch (err) {
WARN(func, INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return date passed with added amount of time units
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_ADD (value, amount, unit) {
'use strict';
return DATE_CALC(value, amount, unit, "DATE_ADD");
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return date passed with subtracted amount of time units
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_SUBTRACT (value, amount, unit) {
'use strict';
return DATE_CALC(value, amount, unit, "DATE_SUBTRACT");
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return date difference in given unit, optionally with fractions
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_DIFF (value1, value2, unit, asFloat) {
'use strict';
var date1 = MAKE_DATE([ value1 ], "DATE_DIFF");
var date2 = MAKE_DATE([ value2 ], "DATE_DIFF");
// Don't return any number if either or both dates are NaN.
if (date1 === null || date2 === null) {
// warning issued by MAKE_DATE() already (duplicate warnings in other date function calls???)
return null;
}
asFloat = (asFloat === undefined) ? false : asFloat;
if (date1 === date2) {
return 0;
}
try {
var divisor = msPerUnit[unit.toLowerCase()];
if (divisor === undefined) {
WARN("DATE_DIFF", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
// simple calculation if not month or year
if (divisor > 0) {
return (asFloat) ? ((date2 - date1) / divisor) : (~~((date2 - date1) / divisor));
}
var year1 = date1.getUTCFullYear();
var month1 = date1.getUTCMonth();
var year2 = date2.getUTCFullYear();
var month2 = date2.getUTCMonth();
// ms in given month, leap year compensated if February and leap year
var month1ms = msPerMonth[month1] + ((month1 === 1 && AQL_DATE_LEAPYEAR(date1.getTime())) ? msPerUnit.d : 0);
// ms since beginning of month
var month1msOffset = date1 - Date.UTC(year1, month1);
var month2ms = msPerMonth[month2] + ((month2 === 1 && AQL_DATE_LEAPYEAR(date2.getTime())) ? msPerUnit.d : 0);
var month2msOffset = date2 - Date.UTC(year2, month2);
// Diff between YYYY-MM parts only.
var diff = (year2 * 12 + month2) - (year1 * 12 + month1);
// Add diff between month offsets relative to the mean of ms in both months
diff += ((month2msOffset - month1msOffset) / ((month1ms + month2ms) / 2));
// return 1/12th of month if unit is year
if (asFloat) {
return divisor ? diff / 12 : diff;
} else {
// round towards zero, regardless of sign
return divisor ? ~~(diff / 12) : ~~diff;
}
}
catch (err) {
WARN("DATE_DIFF", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief compare two partial dates, using a substring of the dates
////////////////////////////////////////////////////////////////////////////////
function AQL_DATE_COMPARE (value1, value2, unitRangeStart, unitRangeEnd) {
try {
// TODO: Should we handle leap years, so leapling birthdays occur every year?
// It may result in unexpected behavior if it's used for something else but
// birthday checking however. Probably best to leave compensation up to user query.
var date1 = MAKE_DATE([ value1 ], "DATE_COMPARE");
var date2 = MAKE_DATE([ value2 ], "DATE_COMPARE");
if (TYPEWEIGHT(date1) === TYPEWEIGHT_NULL || TYPEWEIGHT(date2) === TYPEWEIGHT_NULL) {
return null;
}
if (unitRangeEnd === undefined) {
unitRangeEnd = unitRangeStart;
}
var start = unitStrRanges[unitRangeStart][0];
var end = unitStrRanges[unitRangeEnd][1];
if (start === undefined || end === undefined) {
WARN("DATE_COMPARE", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
var yr1 = date1.getUTCFullYear();
if ((yr1 < 0 || yr1 > 9999) && start !== 0) {
start += 3;
}
var yr2 = date2.getUTCFullYear();
if (yr2 < 0 || yr2 > 9999) {
end += 3;
}
var substr1 = date1.toISOString().slice(start, end);
var substr2 = date2.toISOString().slice(start, end);
// if unitRangeEnd > unitRangeStart, substrings will be empty
if (!substr1 || !substr2) {
WARN("DATE_COMPARE", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
return substr1 === substr2;
} catch (err) {
WARN("DATE_COMPARE", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief format a date (numerical values only)
////////////////////////////////////////////////////////////////////////////////
// special escape sequence first, rest ordered by length
var dateMapRegExp = [
"%&", "%yyyyyy", "%yyyy", "%mmmm", "%wwww", "%mmm", "%www", "%fff", "%xxx",
"%yy", "%mm", "%dd", "%hh", "%ii", "%ss", "%kk", "%t", "%z", "%w", "%y", "%m",
"%d", "%h", "%i", "%s", "%f", "%x", "%k", "%l", "%q", "%a", "%%", "%"
].join("|");
function AQL_DATE_FORMAT (value, format) {
'use strict';
try {
var date = MAKE_DATE([ value ], "DATE_FORMAT");
var dateStr = date.toISOString();
var yr = date.getUTCFullYear();
var offset = yr < 0 || yr > 9999 ? 3 : 0;
var dateMap = {
"%t": function() { return date.getTime(); },
"%z": function() { return dateStr; },
"%w": function() { return AQL_DATE_DAYOFWEEK(dateStr); },
"%y": function() { return date.getUTCFullYear(); },
// there's no really sensible way to handle negative years, but better not drop the sign
"%yy": function() { return (yr < 0 ? "-" : "") + dateStr.slice(2 + offset, 4 + offset); },
// preserves full negative years (-000753 is not reduced to -753 or -0753)
"%yyyy": function() { return dateStr.slice(0, 4 + offset); },
// zero-pad 4 digit years to length of 6 and add "+" prefix, keep negative as-is
"%yyyyyy": function() {
return (yr >= 0 && yr <= 9999)
? "+" + zeropad(dateStr.slice(0, 4 + offset), 6)
: dateStr.slice(0, 7);
},
"%m": function() { return date.getUTCMonth() + 1; },
"%mm": function() { return dateStr.slice(5 + offset, 7 + offset); },
"%d": function() { return date.getUTCDate(); },
"%dd": function() { return dateStr.slice(8 + offset, 10 + offset); },
"%h": function() { return date.getUTCHours(); },
"%hh": function() { return dateStr.slice(11 + offset, 13 + offset); },
"%i": function() { return date.getUTCMinutes(); },
"%ii": function() { return dateStr.slice(14 + offset, 16 + offset); },
"%s": function() { return date.getUTCSeconds(); },
"%ss": function() { return dateStr.slice(17 + offset, 19 + offset); },
"%f": function() { return date.getUTCMilliseconds(); },
"%fff": function() { return dateStr.slice(20 + offset, 23 + offset); },
"%x": function() { return AQL_DATE_DAYOFYEAR(dateStr); },
"%xxx": function() { return zeropad(AQL_DATE_DAYOFYEAR(dateStr), 3); },
"%k": function() { return AQL_DATE_ISOWEEK(dateStr); },
"%kk": function() { return zeropad(AQL_DATE_ISOWEEK(dateStr), 2); },
"%l": function() { return +AQL_DATE_LEAPYEAR(dateStr); },
"%q": function() { return AQL_DATE_QUARTER(dateStr); },
"%a": function() { return AQL_DATE_DAYS_IN_MONTH(dateStr); },
"%mmm": function() { return monthNames[date.getUTCMonth()].substring(0, 3); },
"%mmmm": function() { return monthNames[date.getUTCMonth()]; },
"%www": function() { return weekdayNames[AQL_DATE_DAYOFWEEK(dateStr)].substring(0, 3); },
"%wwww": function() { return weekdayNames[AQL_DATE_DAYOFWEEK(dateStr)]; },
"%&": function() { return ""; }, // Allow for literal "m" after "%m" ("%mm" -> %m%&m)
"%%": function() { return "%"; }, // Allow for literal "%y" using "%%y"
"%": function() { return ""; }
};
var exp = new RegExp(dateMapRegExp, "gi");
format = format.replace(exp, function(match){
return dateMap[match.toLowerCase()]();
});
return format;
} catch (err) {
WARN("DATE_FORMAT", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @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
////////////////////////////////////////////////////////////////////////////////
function SUBNODES (searchAttributes, vertexId, visited, edges, vertices, level) {
'use strict';
var result = [ ];
if (level >= searchAttributes.minLength) {
result.push({
vertices : vertices,
edges : edges,
source : vertices[0],
destination : vertices[vertices.length - 1]
});
}
if (level + 1 > searchAttributes.maxLength) {
return result;
}
var subEdges = GET_SUB_EDGES(
searchAttributes.edgeCollection, searchAttributes.direction, vertexId
);
var i, j, k;
for (i = 0; i < subEdges.length; ++i) {
var subEdge = subEdges[i];
var targets = [ ];
if ((searchAttributes.direction === 1 || searchAttributes.direction === 3) &&
(subEdge._to !== vertexId)) {
targets.push(subEdge._to);
}
if ((searchAttributes.direction === 2 || searchAttributes.direction === 3) &&
(subEdge._from !== vertexId)) {
targets.push(subEdge._from);
}
for (j = 0; j < targets.length; ++j) {
var targetId = targets[j];
if (! searchAttributes.followCycles) {
if (visited[targetId]) {
continue;
}
visited[targetId] = true;
}
var clonedEdges = CLONE(edges);
var clonedVertices = CLONE(vertices);
try {
clonedVertices.push(INTERNAL.db._document(targetId));
clonedEdges.push(subEdge);
}
catch (e) {
// avoid "document not found error" in case referenced vertices were deleted
}
var connected = SUBNODES(searchAttributes,
targetId,
CLONE(visited),
clonedEdges,
clonedVertices,
level + 1);
for (k = 0; k < connected.length; ++k) {
result.push(connected[k]);
}
if (! searchAttributes.followCycles) {
delete visited[targetId];
}
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief find all paths through a graph
////////////////////////////////////////////////////////////////////////////////
function AQL_PATHS (vertices, edgeCollection, direction, options) {
'use strict';
direction = direction || "outbound";
followCycles = followCycles || false;
if (typeof options === "boolean") {
options = { followCycles : options };
}
else if (typeof options !== "object" || Array.isArray(options)) {
options = { };
}
var followCycles = options.followCycles || false;
var minLength = options.minLength || 0;
var maxLength = options.maxLength || 10;
if (TYPEWEIGHT(vertices) !== TYPEWEIGHT_ARRAY) {
WARN("PATHS", INTERNAL.errors.ERROR_QUERY_ARRAY_EXPECTED);
return null;
}
// validate arguments
var searchDirection;
if (direction === "outbound") {
searchDirection = 1;
}
else if (direction === "inbound") {
searchDirection = 2;
}
else if (direction === "any") {
searchDirection = 3;
}
else {
WARN("PATHS", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
if (minLength < 0 || maxLength < 0 || minLength > maxLength) {
WARN("PATHS", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
var searchAttributes = {
edgeCollection : COLLECTION(edgeCollection, "PATHS"),
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 = { };
visited[vertex._id] = true;
var connected = SUBNODES(searchAttributes, vertex._id, visited, [ ], [ vertex ], 0);
for (j = 0; j < connected.length; ++j) {
result.push(connected[j]);
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock JSF_aql_general_graph_paths
////////////////////////////////////////////////////////////////////////////////
function AQL_GRAPH_PATHS (graphName, options) {
'use strict';
var searchDirection;
if (! options) {
options = {};
}
var direction = options.direction || "outbound";
var followCycles = options.followCycles || false;
var minLength = options.minLength || 0;
var maxLength = options.maxLength || 10;
// check graph exists and load edgeDefintions
var graph = DOCUMENT_HANDLE("_graphs/" + graphName);
if (! graph) {
THROW("GRAPH_PATHS", INTERNAL.errors.ERROR_GRAPH_INVALID_GRAPH, "GRAPH_PATHS");
}
var startCollections = [], edgeCollections = [];
// 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 {
WARN("GRAPH_PATHS", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
if (edgeCollections.indexOf(def.collection) === -1) {
edgeCollections.push(COLLECTION(def.collection, "GRAPH_PATHS"));
}
});
if (minLength < 0 || maxLength < 0 || minLength > maxLength) {
WARN("GRAPH_PATHS", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
var result = [ ];
startCollections.forEach(function (startCollection) {
var searchAttributes = {
edgeCollection : edgeCollections,
minLength : minLength,
maxLength : maxLength,
direction : searchDirection,
followCycles : followCycles
};
var vertices = GET_DOCUMENTS(startCollection, "GRAPH_PATHS");
var n = vertices.length, i, j;
for (i = 0; i < n; ++i) {
var vertex = vertices[i];
var visited = { };
visited[vertex._id] = true;
var connected = SUBNODES(searchAttributes, vertex._id, visited, [ ], [ vertex ], 0);
for (j = 0; j < connected.length; ++j) {
result.push(connected[j]);
}
}
});
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief visitor callback function for traversal
////////////////////////////////////////////////////////////////////////////////
function TRAVERSAL_VISITOR (config, result, vertex, path) {
'use strict';
if (config.trackPaths) {
result.push(CLONE({ vertex: vertex, path: path }));
}
else {
result.push(CLONE({ vertex: vertex }));
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief visitor callback function for traversal
////////////////////////////////////////////////////////////////////////////////
function SHORTEST_PATH_VISITOR (config, result, vertex, path) {
'use strict';
if (this.includeData) {
result.push({
vertices: CLONE(path.vertices),
edges: CLONE(path.edges),
distance: path.edges.length
});
} else {
result.push({
vertices: underscore.pluck(path.vertices, "_id"),
edges: underscore.pluck(path.edges, "_id"),
distance: path.edges.length
});
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief visitor callback function for neighbors
////////////////////////////////////////////////////////////////////////////////
function TRAVERSAL_NEIGHBOR_VISITOR (config, result, vertex, path) {
'use strict';
// The this has to be bound explicitly!
// It contains additional options.
if (path.edges.length >= this.minDepth) {
result.push(CLONE(vertex));
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief visitor callback function for edges
////////////////////////////////////////////////////////////////////////////////
function TRAVERSAL_EDGE_VISITOR (config, result, vertex, path) {
'use strict';
// The this has to be bound explicitly!
// It contains additional options.
if (this.hasOwnProperty("minDepth") && path.edges.length < this.minDepth) {
return;
}
if (this.hasOwnProperty("neighborExamples") && !AQL_MATCHES(vertex, this.neighborExamples, false)) {
return;
}
result.push(CLONE(path.edges[path.edges.length - 1]));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief visitor callback function for tree traversal
////////////////////////////////////////////////////////////////////////////////
function TRAVERSAL_TREE_VISITOR (config, result, vertex, path) {
'use strict';
if (result.length === 0) {
result.push({ });
}
var current = result[0], connector = config.connect, i;
for (i = 0; i < path.vertices.length; ++i) {
var v = path.vertices[i];
if (typeof current[connector] === "undefined") {
current[connector] = [ ];
}
var found = false, j;
for (j = 0; j < current[connector].length; ++j) {
if (current[connector][j]._id === v._id) {
current = current[connector][j];
found = true;
break;
}
}
if (! found) {
current[connector].push(CLONE(v));
current = current[connector][current[connector].length - 1];
}
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief expander callback function for traversal
////////////////////////////////////////////////////////////////////////////////
function TRAVERSAL_EDGE_EXAMPLE_FILTER (config, vertex, edge, path) {
'use strict';
if (config.edgeCollectionRestriction) {
if (typeof config.edgeCollectionRestriction === "string" ) {
config.edgeCollectionRestriction = [config.edgeCollectionRestriction];
}
if (config.edgeCollectionRestriction.indexOf(edge._id.split("/")[0]) === -1) {
return false;
}
}
if (config.expandEdgeExamples) {
var e = AQL_MATCHES(edge, config.expandEdgeExamples);
return AQL_MATCHES(edge, config.expandEdgeExamples);
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief vertex filter callback function for traversal
////////////////////////////////////////////////////////////////////////////////
function TRAVERSAL_VERTEX_FILTER (config, vertex, path) {
'use strict';
if (config.filterVertexExamples && ! AQL_MATCHES(vertex, config.filterVertexExamples)) {
if (config.filterVertexCollections
&& config.vertexFilterMethod.indexOf("exclude") === -1
&& config.filterVertexCollections.indexOf(vertex._id.split("/")[0]) === -1
) {
if (config.vertexFilterMethod.indexOf("prune") === -1) {
return ["exclude"];
}
return ["prune", "exclude"];
}
return config.vertexFilterMethod;
}
if (config.filterVertexCollections
&& config.filterVertexCollections.indexOf(vertex._id.split("/")[0]) === -1
) {
return ["exclude"];
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief check typeweights of params.followEdges/params.filterVertices
////////////////////////////////////////////////////////////////////////////////
function TRAVERSAL_CHECK_EXAMPLES_TYPEWEIGHTS (examples, func) {
'use strict';
if (TYPEWEIGHT(examples) === TYPEWEIGHT_STRING) {
// a callback function was supplied. this is considered valid
return true;
}
if (TYPEWEIGHT(examples) !== TYPEWEIGHT_ARRAY) {
WARN(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return false;
}
if (examples.length === 0) {
WARN(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return false;
}
var i;
for (i = 0; i < examples.length; ++i) {
if (TYPEWEIGHT(examples[i]) !== TYPEWEIGHT_OBJECT) {
WARN(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return false;
}
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief tranform key to id
////////////////////////////////////////////////////////////////////////////////
function TO_ID (vertex, collection) {
'use strict';
if (typeof vertex === 'object' && vertex !== null && vertex.hasOwnProperty('_id')) {
return vertex._id;
}
if (typeof vertex === 'string' && vertex.indexOf('/') === -1 && collection) {
return collection + '/' + vertex;
}
return vertex;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief Create basic traversal config
////////////////////////////////////////////////////////////////////////////////
function TRAVERSAL_CONFIG (func, datasource, startVertex, endVertex, direction, params) {
if (params === undefined) {
params = { };
}
// check followEdges property
if (params.followEdges) {
if (! TRAVERSAL_CHECK_EXAMPLES_TYPEWEIGHTS(params.followEdges, func)) {
return null;
}
}
// check filterVertices property
if (params.filterVertices) {
if (! TRAVERSAL_CHECK_EXAMPLES_TYPEWEIGHTS(params.filterVertices, func)) {
return null;
}
}
if (typeof params.visitor !== "function") {
WARN(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
if (params.maxDepth === undefined) {
// we need to have at least SOME limit to prevent endless iteration
params.maxDepth = 256;
}
var config = {
distance: params.distance,
connect: params.connect,
trackPaths: params.paths || false,
visitor: params.visitor,
visitorReturnsResults: params.visitorReturnsResults || false,
maxDepth: params.maxDepth,
minDepth: params.minDepth,
maxIterations: params.maxIterations,
uniqueness: params.uniqueness,
strategy: params.strategy,
order: params.order,
itemOrder: params.itemOrder,
weight : params.weight,
defaultWeight : params.defaultWeight,
prefill : params.prefill,
data: params.data,
datasource: datasource,
expander: direction,
direction: direction,
startVertex: startVertex,
endVertex: endVertex
};
if (typeof params.filter === "function") {
config.filter = params.filter;
}
if (params.followEdges) {
if (typeof params.followEdges === 'string') {
config.expandFilter = GET_EXPANDFILTER(params.followEdges, params);
}
else {
config.expandFilter = TRAVERSAL_EDGE_EXAMPLE_FILTER;
config.expandEdgeExamples = params.followEdges;
}
}
if (params.edgeCollectionRestriction) {
config.expandFilter = TRAVERSAL_EDGE_EXAMPLE_FILTER;
config.edgeCollectionRestriction = params.edgeCollectionRestriction;
}
if (params.filterVertices) {
if (typeof params.filterVertices === 'string') {
config.filter = GET_FILTER(params.filterVertices, params);
}
else {
config.filter = TRAVERSAL_VERTEX_FILTER;
config.filterVertexExamples = params.filterVertices;
config.vertexFilterMethod = params.vertexFilterMethod || ["prune", "exclude"];
}
}
if (params.filterVertexCollections) {
config.filter = config.filter || TRAVERSAL_VERTEX_FILTER;
config.vertexFilterMethod = config.vertexFilterMethod || ["prune", "exclude"];
config.filterVertexCollections = params.filterVertexCollections;
}
if (params._sort) {
config.sort = function (l, r) { return l._key < r._key ? -1 : 1; };
}
// start vertex
var v = null;
try {
v = INTERNAL.db._document(startVertex);
}
catch (err1) {
}
// end vertex
var e;
if (endVertex !== undefined) {
if (typeof endVertex === "string") {
try {
e = INTERNAL.db._document(endVertex);
}
catch (err2) {
}
} else {
e = endVertex;
}
}
return {
config: config,
endVertex: e,
vertex: v
};
}
////////////////////////////////////////////////////////////////////////////////
/// @brief traverse a graph
////////////////////////////////////////////////////////////////////////////////
function TRAVERSAL_FUNC (func,
datasource,
startVertex,
endVertex,
direction,
params) {
'use strict';
var info = TRAVERSAL_CONFIG(func, datasource, startVertex, endVertex, direction, params);
var result = [ ];
if (info.vertex !== null) {
var traverser = new TRAVERSAL.Traverser(info.config);
traverser.traverse(result, info.vertex, info.endVertex);
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief CHECK IF RESTRICTION LIST MATCHES
////////////////////////////////////////////////////////////////////////////////
function FILTER_RESTRICTION (list, restrictionList) {
if (! restrictionList) {
return list;
}
if (typeof restrictionList === "string") {
restrictionList = [restrictionList];
}
var result = [];
restrictionList.forEach(function (r) {
if (list.indexOf(r) !== -1) {
result.push(r);
}
});
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get all document _ids matching the given examples
////////////////////////////////////////////////////////////////////////////////
function DOCUMENT_IDS_BY_EXAMPLE (func, collectionList, example) {
var res = [ ];
if (example === "null" || example === null || ! example) {
collectionList.forEach(function (c) {
res = res.concat(COLLECTION(c, func).toArray().map(function(t) { return t._id; }));
});
return res;
}
if (typeof example === "string") {
// Assume it is an _id. Has to fail later on
return [ example ];
}
if (! Array.isArray(example)) {
example = [ example ];
}
var tmp = [ ];
example.forEach(function (e) {
if (typeof e === "string") {
// We have an _id already
res.push(e);
}
else if (e !== null) {
tmp.push(e);
}
});
collectionList.forEach(function (c) {
tmp.forEach(function (e) {
res = res.concat(COLLECTION(c, func).byExample(e).toArray().map(function(t) {
return t._id;
}));
});
});
return res;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief getAllDocsByExample
////////////////////////////////////////////////////////////////////////////////
function DOCUMENTS_BY_EXAMPLE (func, collectionList, example) {
var res = [ ];
if (example === "null" || example === null || ! example) {
collectionList.forEach(function (c) {
res = res.concat(COLLECTION(c, func).toArray());
});
return res;
}
if (typeof example === "string") {
example = [ { _id : example } ];
}
if (! Array.isArray(example)) {
example = [ example ];
}
var tmp = [ ];
example.forEach(function (e) {
if (typeof e === "string") {
tmp.push({ _id : e });
}
else if (e !== null) {
tmp.push(e);
}
});
collectionList.forEach(function (c) {
tmp.forEach(function (e) {
res = res.concat(COLLECTION(c, func).byExample(e).toArray());
});
});
return res;
}
function RESOLVE_GRAPH_TO_COLLECTIONS (graph, options, funcname) {
var collections = {};
collections.fromCollections = [];
collections.toCollection = [];
collections.edgeCollections = [];
graph.edgeDefinitions.forEach(function (def) {
if (options.direction === "outbound") {
collections.edgeCollections = collections.edgeCollections.concat(
FILTER_RESTRICTION(def.collection, options.edgeCollectionRestriction)
);
collections.fromCollections = collections.fromCollections.concat(
FILTER_RESTRICTION(def.from, options.startVertexCollectionRestriction)
);
collections.toCollection = collections.toCollection.concat(
FILTER_RESTRICTION(def.to, options.endVertexCollectionRestriction)
);
}
else if (options.direction === "inbound") {
collections.edgeCollections = collections.edgeCollections.concat(
FILTER_RESTRICTION(def.collection, options.edgeCollectionRestriction)
);
collections.fromCollections = collections.fromCollections.concat(
FILTER_RESTRICTION(def.to, options.endVertexCollectionRestriction)
);
collections.toCollection = collections.toCollection.concat(
FILTER_RESTRICTION(def.from, options.startVertexCollectionRestriction)
);
}
else if (options.direction === "any") {
collections.edgeCollections = collections.edgeCollections.concat(
FILTER_RESTRICTION(def.collection, options.edgeCollectionRestriction)
);
collections.fromCollections = collections.fromCollections.concat(
FILTER_RESTRICTION(def.from.concat(def.to), options.startVertexCollectionRestriction)
);
collections.toCollection = collections.toCollection.concat(
FILTER_RESTRICTION(def.from.concat(def.to), options.endVertexCollectionRestriction)
);
}
else {
WARN(funcname, INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
// TODO: check if we need to return more data here
return collections;
}
});
collections.orphanCollections = FILTER_RESTRICTION(
graph.orphanCollections, options.orphanCollectionRestriction
);
return collections;
}
function RESOLVE_GRAPH_TO_FROM_VERTICES (graphname, options, funcname) {
var graph = DOCUMENT_HANDLE("_graphs/" + graphname), collections;
if (! graph) {
THROW(funcname, INTERNAL.errors.ERROR_GRAPH_INVALID_GRAPH, funcname);
}
collections = RESOLVE_GRAPH_TO_COLLECTIONS(graph, options, funcname);
var removeDuplicates = function(elem, pos, self) {
return self.indexOf(elem) === pos;
};
if (options.includeOrphans) {
collections.fromCollections = collections.fromCollections.concat(collections.orphanCollections);
}
return DOCUMENTS_BY_EXAMPLE(funcname,
collections.fromCollections.filter(removeDuplicates), options.fromVertexExample
);
}
function RESOLVE_GRAPH_TO_TO_VERTICES (graphname, options, funcname) {
var graph = DOCUMENT_HANDLE("_graphs/" + graphname), collections ;
if (! graph) {
THROW(funcname, INTERNAL.errors.ERROR_GRAPH_INVALID_GRAPH, funcname);
}
collections = RESOLVE_GRAPH_TO_COLLECTIONS(graph, options, funcname);
var removeDuplicates = function(elem, pos, self) {
return self.indexOf(elem) === pos;
};
return DOCUMENTS_BY_EXAMPLE(funcname,
collections.toCollection.filter(removeDuplicates), options.toVertexExample
);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief GET ALL EDGE and VERTEX COLLECTION ACCORDING TO DIRECTION
////////////////////////////////////////////////////////////////////////////////
function RESOLVE_GRAPH_START_VERTICES (graphName, options, funcname) {
// check graph exists and load edgeDefintions
var graph = DOCUMENT_HANDLE("_graphs/" + graphName), collections ;
if (! graph) {
THROW(funcname, INTERNAL.errors.ERROR_GRAPH_INVALID_GRAPH, funcname);
}
collections = RESOLVE_GRAPH_TO_COLLECTIONS(graph, options, funcname);
var removeDuplicates = function(elem, pos, self) {
return self.indexOf(elem) === pos;
};
return DOCUMENTS_BY_EXAMPLE(funcname,
collections.fromCollections.filter(removeDuplicates), options.fromVertexExample
);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief GET ALL EDGE and VERTEX COLLECTION ACCORDING TO DIRECTION
////////////////////////////////////////////////////////////////////////////////
function RESOLVE_GRAPH_TO_DOCUMENTS (graphname, options, funcname) {
// check graph exists and load edgeDefintions
var graph = DOCUMENT_HANDLE("_graphs/" + graphname), collections ;
if (! graph) {
THROW(funcname, INTERNAL.errors.ERROR_GRAPH_INVALID_GRAPH, funcname);
}
collections = RESOLVE_GRAPH_TO_COLLECTIONS(graph, options, funcname);
var removeDuplicates = function(elem, pos, self) {
return self.indexOf(elem) === pos;
};
var result = {
fromVertices : DOCUMENTS_BY_EXAMPLE(funcname,
collections.fromCollections.filter(removeDuplicates), options.fromVertexExample
),
toVertices : DOCUMENTS_BY_EXAMPLE(funcname,
collections.toCollection.filter(removeDuplicates), options.toVertexExample
),
edges : DOCUMENTS_BY_EXAMPLE(funcname,
collections.edgeCollections.filter(removeDuplicates), options.edgeExamples
),
edgeCollections : collections.edgeCollections,
fromCollections : collections.fromCollections,
toCollection : collections.toCollection
};
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief calculate distance of an edge
////////////////////////////////////////////////////////////////////////////////
function DETERMINE_WEIGHT (edge, weight, defaultWeight) {
if (! weight) {
return 1;
}
if (typeof edge[weight] === "number") {
return edge[weight];
}
if (defaultWeight) {
return defaultWeight;
}
return Infinity;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief visitor callback function for traversal
////////////////////////////////////////////////////////////////////////////////
function TRAVERSAL_DISTANCE_VISITOR (config, result, vertex, path) {
'use strict';
if (config.endVertex && config.endVertex === vertex._id) {
var dist = 0;
path.edges.forEach(function (e) {
if (config.weight) {
if (typeof e[config.weight] === "number") {
dist = dist + e[config.weight];
} else if (config.defaultWeight) {
dist = dist + config.defaultWeight;
}
} else {
dist++;
}
});
result.push(
CLONE({ vertex: vertex, distance: dist , path: path , startVertex : config.startVertex})
);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief visitor callback function for traversal
////////////////////////////////////////////////////////////////////////////////
// NOTE Inject includeData property in this.
function TRAVERSAL_DIJKSTRA_VISITOR (config, result, node, path) {
'use strict';
var vertex = node.vertex;
var res;
if (this.hasOwnProperty("includeData")
&& this.includeData === true) {
res = {
vertices: path.vertices,
edges: path.edges,
distance: node.dist
};
} else {
res = {
vertices: underscore.pluck(path.vertices, "_id"),
edges: underscore.pluck(path.edges, "_id"),
distance: node.dist
};
}
result.push(CLONE(res));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief helper function to determine parameters for SHORTEST_PATH and
/// GRAPH_SHORTEST_PATH
////////////////////////////////////////////////////////////////////////////////
function SHORTEST_PATH_PARAMS (params) {
'use strict';
if (params === undefined) {
params = { };
}
else {
params = CLONE(params);
}
params.strategy = "dijkstra";
params.itemorder = "forward";
// add user-defined visitor, if specified
if (params.hasOwnProperty("visitor")) {
if (typeof params.visitor === "string") {
params.visitor = GET_VISITOR(params.visitor, params);
}
} else {
// params.visitor = TRAVERSAL_VISITOR;
params.visitor = SHORTEST_PATH_VISITOR.bind({
includeData: params.includeData || false
});
}
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 params;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief shortest path algorithm
////////////////////////////////////////////////////////////////////////////////
function AQL_SHORTEST_PATH (vertexCollection,
edgeCollection,
startVertex,
endVertex,
direction,
params) {
'use strict';
var opts = {
direction: direction
};
params = params || {};
var vertexCollections = [vertexCollection];
var edgeCollections = [edgeCollection];
// JS Fallback cases. CPP implementation not working here
if (
params.hasOwnProperty("distance") ||
params.hasOwnProperty("filterVertices") ||
params.hasOwnProperty("followEdges") ||
isCoordinator
) {
params = SHORTEST_PATH_PARAMS(params);
var a = TRAVERSAL_FUNC("SHORTEST_PATH",
TRAVERSAL.collectionDatasourceFactory(COLLECTION(edgeCollection, "SHORTEST_PATH")),
TO_ID(startVertex, vertexCollection),
TO_ID(endVertex, vertexCollection),
direction,
params);
// Did not want to modify traversal dijkstra search.
// Might have unforseen side effects.
// Internal function visits all vertices on the path.
// So we reset the path on every step again.
return a[a.length - 1];
}
// Fall through to new CPP version.
if (params.hasOwnProperty("weight") && params.hasOwnProperty("defaultWeight")) {
opts.weight = params.weight;
opts.defaultWeight = params.defaultWeight;
}
if (params.hasOwnProperty("includeData")) {
opts.includeData = params.includeData;
}
if (params.hasOwnProperty("followEdges")) {
opts.followEdges = params.followEdges;
}
if (params.hasOwnProperty("filterVertices")) {
opts.filterVertices = params.filterVertices;
}
var i, c;
if (params.hasOwnProperty("vertexCollections") && Array.isArray(params.vertexCollections)) {
for (i = 0; i < params.vertexCollections.length; ++i) {
c = params.vertexCollections[i];
if (typeof c === "string") {
vertexCollections.push(c);
}
}
}
if (params.hasOwnProperty("edgeCollections") && Array.isArray(params.edgeCollections)) {
for (i = 0; i < params.edgeCollections.length; ++i) {
c = params.edgeCollections[i];
if (typeof c === "string") {
edgeCollections.push(c);
}
}
}
// newRes has the format: { vertices: [Doc], edges: [Doc], distance: Number}
// oldResult had the format: [ {vertex: Doc} ]
return CPP_SHORTEST_PATH(vertexCollections, edgeCollections,
TO_ID(startVertex, vertexCollection),
TO_ID(endVertex, vertexCollection),
opts
);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief shortest path algorithm
////////////////////////////////////////////////////////////////////////////////
function CALCULATE_SHORTEST_PATHES_WITH_FLOYD_WARSHALL (graphData, options) {
'use strict';
var graph = graphData, result = [];
graph.fromVerticesIDs = {};
graph.fromVertices.forEach(function (a) {
graph.fromVerticesIDs[a._id] = a;
});
graph.toVerticesIDs = {};
graph.toVertices.forEach(function (a) {
graph.toVerticesIDs[a._id] = a;
});
graph.docStore = {};
var paths = {};
var vertices = {};
graph.edges.forEach(function(e) {
if (options.direction === "outbound") {
if (!paths[e._from]) {
paths[e._from] = {};
}
paths[e._from][e._to] = {distance : DETERMINE_WEIGHT(e, options.weight,
options.defaultWeight), paths : [{edges : [e], vertices : [e._from, e._to]}]};
} else if (options.direction === "inbound") {
if (!paths[e._to]) {
paths[e._to] = {};
}
paths[e._to][e._from] = {distance : DETERMINE_WEIGHT(e, options.weight,
options.defaultWeight), paths : [{edges : [e], vertices : [e._to, e._from]}]};
} else {
if (!paths[e._from]) {
paths[e._from] = {};
}
if (!paths[e._to]) {
paths[e._to] = {};
}
if (paths[e._from][e._to]) {
paths[e._from][e._to].distance =
Math.min(paths[e._from][e._to].distance, DETERMINE_WEIGHT(e, options.weight,
options.defaultWeight));
} else {
paths[e._from][e._to] = {distance : DETERMINE_WEIGHT(e, options.weight,
options.defaultWeight), paths : [{edges : [e], vertices : [e._from, e._to]}]};
}
if (paths[e._to][e._from]) {
paths[e._to][e._from].distance =
Math.min(paths[e._to][e._from].distance, DETERMINE_WEIGHT(e, options.weight,
options.defaultWeight));
} else {
paths[e._to][e._from] = {distance : DETERMINE_WEIGHT(e, options.weight,
options.defaultWeight), paths : [{edges : [e], vertices : [e._to, e._from]}]};
}
}
if (options.noPaths) {
try {
delete paths[e._to][e._from].paths;
delete paths[e._from][e._to].paths;
} catch (ignore) {
}
}
vertices[e._to] = 1;
vertices[e._from] = 1;
});
var removeDuplicates = function(elem, pos, self) {
return self.indexOf(elem) === pos;
};
Object.keys(graph.fromVerticesIDs).forEach(function (v) {
vertices[v] = 1;
});
var allVertices = Object.keys(vertices);
allVertices.forEach(function (k) {
allVertices.forEach(function (i) {
allVertices.forEach(function (j) {
if (i === j ) {
if (!paths[i]) {
paths[i] = {};
}
paths[i][j] = null;
return;
}
if (paths[i] && paths[i][k] && paths[i][k].distance >=0
&& paths[i][k].distance < Infinity &&
paths[k] && paths[k][j] && paths[k][j].distance >=0
&& paths[k][j].distance < Infinity &&
( !paths[i][j] ||
paths[i][k].distance + paths[k][j].distance <= paths[i][j].distance
)
) {
if (!paths[i][j]) {
paths[i][j] = {paths : [], distance : paths[i][k].distance + paths[k][j].distance};
}
if (paths[i][k].distance + paths[k][j].distance < paths[i][j].distance) {
paths[i][j].distance = paths[i][k].distance+paths[k][j].distance;
paths[i][j].paths = [];
}
if (!options.noPaths) {
paths[i][k].paths.forEach(function (p1) {
paths[k][j].paths.forEach(function (p2) {
paths[i][j].paths.push({
edges : p1.edges.concat(p2.edges),
vertices: p1.vertices.concat(p2.vertices).filter(removeDuplicates)
});
});
});
}
}
});
});
});
var transformPath = function (paths) {
paths.forEach(function (p) {
var vTmp = [];
p.vertices.forEach(function (v) {
if (graph.fromVerticesIDs[v]) {
vTmp.push(graph.fromVerticesIDs[v]);
} else if (graph.toVerticesIDs[v]) {
vTmp.push(graph.toVerticesIDs[v]);
} else if (graph.docStore[v]) {
vTmp.push(graph.docStore[v]);
} else {
graph.docStore[v] = DOCUMENT_HANDLE(v);
vTmp.push(graph.docStore[v]);
}
});
p.vertices = vTmp;
});
return paths;
};
Object.keys(paths).forEach(function (from) {
if (!graph.fromVerticesIDs[from]) {
return;
}
Object.keys(paths[from]).forEach(function (to) {
if (!graph.toVerticesIDs[to]) {
return;
}
if (from === to) {
return;
}
result.push({
vertices: paths[from][to].paths[0].vertices,
edges: underscore.pluck(paths[from][to].paths[0].edges, "_id"),
distance : paths[from][to].distance
});
});
});
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief helper function to determine parameters for TRAVERSAL and
/// GRAPH_TRAVERSAL
////////////////////////////////////////////////////////////////////////////////
function TRAVERSAL_PARAMS (params, defaultVisitor) {
'use strict';
if (params === undefined) {
params = { };
}
params = CLONE(params);
// add user-defined visitor, if specified
if (params.hasOwnProperty("visitor")) {
if (typeof params.visitor === "string") {
params.visitor = GET_VISITOR(params.visitor, params);
}
} else {
params.visitor = defaultVisitor || TRAVERSAL_VISITOR;
}
return params;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief merge list of edges with list of examples
////////////////////////////////////////////////////////////////////////////////
function MERGE_EXAMPLES_WITH_EDGES (examples, edges) {
var result = [], filter;
if (examples === "null") {
examples = [{}];
}
if (!examples) {
examples = [{}];
}
if (typeof examples === "string") {
examples = {_id : examples};
}
if (!Array.isArray(examples)) {
examples = [examples];
}
if (examples.length === 0 || (
examples.length === 1 &&
Object.keys(examples).length === 0)) {
return edges;
}
edges.forEach(function(edge) {
examples.forEach(function(example) {
filter = CLONE(example);
if (!(filter._id || filter._key)) {
filter._id = edge._id;
}
result.push(filter);
});
});
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief Creates parameters for a dijkstra based shortest path traversal
////////////////////////////////////////////////////////////////////////////////
function CREATE_DIJKSTRA_PARAMS (graphName, options) {
var params = TRAVERSAL_PARAMS(options,
TRAVERSAL_DIJKSTRA_VISITOR.bind({
includeData: options.includeData || false
})
),
factory = TRAVERSAL.generalGraphDatasourceFactory(graphName);
params.paths = true;
if (options.edgeExamples) {
params.followEdges = options.edgeExamples;
if (! Array.isArray(params.followEdges)) {
params.followEdges = [ params.followEdges ];
}
}
if (options.edgeCollectionRestriction) {
params.edgeCollectionRestriction = options.edgeCollectionRestriction;
}
params.weight = options.weight;
params.defaultWeight = options.defaultWeight;
params = SHORTEST_PATH_PARAMS(params);
params.strategy = "dijkstramulti";
// merge other options
for (var att in options) {
if (options.hasOwnProperty(att) &&
!params.hasOwnProperty(att)) {
params[att] = options[att];
}
}
var toVertices = RESOLVE_GRAPH_TO_TO_VERTICES(graphName, options);
var toVerticesObject = {};
var i;
for (i = 0; i < toVertices.length; ++i) {
toVerticesObject[TO_ID(toVertices[i])] = false;
}
return {
params: params,
factory: factory,
fromVertices: RESOLVE_GRAPH_TO_FROM_VERTICES(graphName, options),
toVertices: toVerticesObject
};
}
////////////////////////////////////////////////////////////////////////////////
/// @brief calculate shortest paths by dijkstra
////////////////////////////////////////////////////////////////////////////////
function CALCULATE_SHORTEST_PATHES_WITH_DIJKSTRA (graphName, options) {
var info = CREATE_DIJKSTRA_PARAMS(graphName, options);
var result = [];
info.fromVertices.forEach(function (v) {
var e = TRAVERSAL_FUNC("GRAPH_SHORTEST_PATH",
info.factory,
TO_ID(v),
JSON.parse(JSON.stringify(info.toVertices)),
options.direction,
info.params
);
e = underscore.filter(e, function(x) {
return x.edges.length > 0;
});
result = result.concat(e);
});
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief checks if an example is set
////////////////////////////////////////////////////////////////////////////////
function IS_EXAMPLE_SET (example) {
return (
example && (
(Array.isArray(example) && example.length > 0) ||
(typeof example === "object" && Object.keys(example) > 0) ||
typeof example === "string"
)
);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock JSF_aql_general_graph_shortest_paths
////////////////////////////////////////////////////////////////////////////////
function AQL_GRAPH_SHORTEST_PATH (graphName,
startVertexExample,
endVertexExample,
options) {
'use strict';
if (! options) {
options = { };
}
if (! options.direction) {
options.direction = 'any';
}
if (isCoordinator) {
// Fallback to old JS function if we are in the cluster.
options.fromVertexExample = startVertexExample;
options.toVertexExample = endVertexExample;
if (! options.algorithm) {
if (! IS_EXAMPLE_SET(startVertexExample) && ! IS_EXAMPLE_SET(endVertexExample)) {
options.algorithm = "Floyd-Warshall";
}
}
if (options.algorithm === "Floyd-Warshall") {
return CALCULATE_SHORTEST_PATHES_WITH_FLOYD_WARSHALL(
RESOLVE_GRAPH_TO_DOCUMENTS(graphName, options, "GRAPH_SHORTEST_PATH"),
options);
}
let tmp = CALCULATE_SHORTEST_PATHES_WITH_DIJKSTRA(graphName, options);
if (options.stopAtFirstMatch && options.stopAtFirstMatch === true && tmp.length > 0) {
let tmpDist = Infinity;
let tmpRes = {};
for (let i = 0; i < tmp.length; ++i) {
if (tmp[i].distance < tmpDist) {
tmpDist = tmp[i].distance;
tmpRes = tmp[i];
}
}
return [tmpRes];
}
return tmp;
}
if (options.hasOwnProperty('edgeExamples')) {
// these two are the same (edgeExamples & filterEdges)...
options.filterEdges = options.edgeExamples;
}
let graph = graphModule._graph(graphName);
let edgeCollections = graph._edgeCollections().map(function (c) { return c.name();});
if (options.hasOwnProperty("edgeCollectionRestriction")) {
if (!Array.isArray(options.edgeCollectionRestriction)) {
if (typeof options.edgeCollectionRestriction === "string") {
if (!underscore.contains(edgeCollections, options.edgeCollectionRestriction)) {
// Short circuit collection not in graph, cannot find results.
return [];
}
edgeCollections = [options.edgeCollectionRestriction];
}
} else {
edgeCollections = underscore.intersection(edgeCollections, options.edgeCollectionRestriction);
}
}
let vertexCollections = graph._vertexCollections().map(function (c) { return c.name();});
let startVertices;
if (options.hasOwnProperty("startVertexCollectionRestriction")
&& Array.isArray(options.startVertexCollectionRestriction)) {
startVertices = DOCUMENT_IDS_BY_EXAMPLE(
"GRAPH_SHORTEST_PATH", options.startVertexCollectionRestriction, startVertexExample);
}
else if (options.hasOwnProperty("startVertexCollectionRestriction")
&& typeof options.startVertexCollectionRestriction === 'string') {
startVertices = DOCUMENT_IDS_BY_EXAMPLE("GRAPH_SHORTEST_PATH",
[ options.startVertexCollectionRestriction ], startVertexExample);
}
else {
startVertices = DOCUMENT_IDS_BY_EXAMPLE(
"GRAPH_SHORTEST_PATH", vertexCollections, startVertexExample);
}
if (startVertices.length === 0) {
return [];
}
let endVertices;
if (options.hasOwnProperty("endVertexCollectionRestriction")
&& Array.isArray(options.endVertexCollectionRestriction)) {
endVertices = DOCUMENT_IDS_BY_EXAMPLE(
"GRAPH_SHORTEST_PATH", options.endVertexCollectionRestriction, endVertexExample);
}
else if (options.hasOwnProperty("endVertexCollectionRestriction")
&& typeof options.endVertexCollectionRestriction === 'string') {
endVertices = DOCUMENT_IDS_BY_EXAMPLE(
"GRAPH_SHORTEST_PATH", [ options.endVertexCollectionRestriction ], endVertexExample);
}
else {
endVertices = DOCUMENT_IDS_BY_EXAMPLE(
"GRAPH_SHORTEST_PATH", vertexCollections, endVertexExample);
}
if (endVertices.length === 0) {
return [];
}
let res = [];
// Compute all shortest_path pairs.
for (let i = 0; i < startVertices.length; ++i) {
for (let j = 0; j < endVertices.length; ++j) {
if (startVertices[i] !== endVertices[j]) {
let p = CPP_SHORTEST_PATH(vertexCollections, edgeCollections,
startVertices[i], endVertices[j], options
);
if (p !== null) {
res.push(p);
}
}
}
}
if (options.stopAtFirstMatch && options.stopAtFirstMatch === true && res.length > 0) {
let tmpDist = Infinity;
let tmpRes = {};
for (let i = 0; i < res.length; ++i) {
if (res[i].distance < tmpDist) {
tmpDist = res[i].distance;
tmpRes = res[i];
}
}
return [tmpRes];
}
return res;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief traverse a graph
////////////////////////////////////////////////////////////////////////////////
function AQL_TRAVERSAL (vertexCollection,
edgeCollection,
startVertex,
direction,
params) {
'use strict';
params = TRAVERSAL_PARAMS(params);
return TRAVERSAL_FUNC("TRAVERSAL",
TRAVERSAL.collectionDatasourceFactory(COLLECTION(edgeCollection, "TRAVERSAL")),
TO_ID(startVertex, vertexCollection),
undefined,
direction,
params);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock JSF_aql_general_graph_traversal
////////////////////////////////////////////////////////////////////////////////
function AQL_GRAPH_TRAVERSAL (graphName,
startVertexExample,
direction,
options) {
'use strict';
var result = [];
options = TRAVERSAL_PARAMS(options);
options.fromVertexExample = startVertexExample;
options.direction = direction;
var startVertices = RESOLVE_GRAPH_START_VERTICES(graphName, options, "GRAPH_TRAVERSAL");
var factory = TRAVERSAL.generalGraphDatasourceFactory(graphName);
startVertices.forEach(function (f) {
result.push(TRAVERSAL_FUNC("GRAPH_TRAVERSAL",
factory,
TO_ID(f),
undefined,
direction,
options));
});
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief helper function to determine parameters for TRAVERSAL_TREE and
/// GRAPH_TRAVERSAL_TREE
////////////////////////////////////////////////////////////////////////////////
function TRAVERSAL_TREE_PARAMS (params, connectName, func) {
'use strict';
if (params === undefined) {
params = { };
}
else {
params = CLONE(params);
}
// add user-defined visitor, if specified
if (typeof params.visitor === "string") {
params.visitor = GET_VISITOR(params.visitor, params);
}
else {
params.visitor = TRAVERSAL_TREE_VISITOR;
}
params.connect = AQL_TO_STRING(connectName);
if (params.connect === "") {
THROW(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
return params;
}
////////////////////////////////////////////////////////////////////////////////
/// @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 AQL_TRAVERSAL_TREE (vertexCollection,
edgeCollection,
startVertex,
direction,
connectName,
params) {
'use strict';
params = TRAVERSAL_TREE_PARAMS(params, connectName, "TRAVERSAL_TREE");
if (params === null) {
return null;
}
var result = TRAVERSAL_FUNC("TRAVERSAL_TREE",
TRAVERSAL.collectionDatasourceFactory(COLLECTION(edgeCollection, "TRAVERSAL_TREE")),
TO_ID(startVertex, vertexCollection),
undefined,
direction,
params);
if (result.length === 0) {
return [ ];
}
return [ result[0][params.connect] ];
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock JSF_aql_general_graph_distance
////////////////////////////////////////////////////////////////////////////////
function AQL_GRAPH_DISTANCE_TO (graphName,
startVertexExample,
endVertexExample,
options) {
'use strict';
if (! options) {
options = {};
}
else {
options = CLONE(options);
}
options.includeData = false;
if (! options.algorithm) {
options.algorithm = "dijkstra";
}
let paths = AQL_GRAPH_SHORTEST_PATH(
graphName, startVertexExample, endVertexExample, options
);
let result = [];
for (let i = 0; i < paths.length; ++i) {
let p = paths[i];
result.push({
startVertex: p.vertices[0],
vertex: p.vertices[p.vertices.length - 1],
distance: p.distance
});
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock JSF_aql_general_graph_traversal_tree
////////////////////////////////////////////////////////////////////////////////
function AQL_GRAPH_TRAVERSAL_TREE (graphName,
startVertexExample,
direction,
connectName,
options) {
'use strict';
var result = [];
options = TRAVERSAL_TREE_PARAMS(options, connectName, "GRAPH_TRAVERSAL_TREE");
if (options === null) {
return null;
}
options.fromVertexExample = startVertexExample;
options.direction = direction;
var startVertices = RESOLVE_GRAPH_START_VERTICES(graphName, options, "GRAPH_TRAVERSAL_TREE");
var factory = TRAVERSAL.generalGraphDatasourceFactory(graphName);
startVertices.forEach(function (f) {
var r = TRAVERSAL_FUNC("GRAPH_TRAVERSAL_TREE",
factory,
TO_ID(f),
undefined,
direction,
options);
if (r.length > 0) {
result.push([ r[0][options.connect] ]);
}
});
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return connected edges
////////////////////////////////////////////////////////////////////////////////
function AQL_EDGES (edgeCollection,
vertex,
direction,
examples,
options) {
'use strict';
var c = COLLECTION(edgeCollection, "EDGES"), result;
// validate arguments
if (direction === "outbound") {
result = FILTER(c.outEdges(vertex), examples);
if (options && options.includeVertices) {
for (let i = 0; i < result.length; ++i) {
try {
result[i] = { edge: CLONE(result[i]), vertex: DOCUMENT_HANDLE(result[i]._to) };
}
catch (err) {
}
}
}
}
else if (direction === "inbound") {
result = FILTER(c.inEdges(vertex), examples);
if (options && options.includeVertices) {
for (let i = 0; i < result.length; ++i) {
try {
result[i] = { edge: CLONE(result[i]), vertex: DOCUMENT_HANDLE(result[i]._from) };
}
catch (err) {
}
}
}
}
else if (direction === "any") {
if (options && options.includeVertices) {
if (Array.isArray(vertex)) {
result = [];
let tmp;
for (let h = 0; h < vertex.length; ++h) {
tmp = FILTER(c.inEdges(vertex), examples);
for (let i = 0; i < tmp.length; ++i) {
try {
tmp[i] = { edge: CLONE(tmp[i]), vertex: DOCUMENT_HANDLE(tmp[i]._from) };
}
catch (err) {
}
}
result = result.concat(tmp);
tmp = FILTER(c.outEdges(vertex), examples);
for (let i = 0; i < tmp.length; ++i) {
try {
tmp[i] = { edge: CLONE(tmp[i]), vertex: DOCUMENT_HANDLE(tmp[i]._to) };
}
catch (err) {
}
}
result = result.concat(tmp);
}
} else {
result = [];
let tmp = FILTER(c.inEdges(vertex), examples);
for (let i = 0; i < tmp.length; ++i) {
try {
tmp[i] = { edge: CLONE(tmp[i]), vertex: DOCUMENT_HANDLE(tmp[i]._from) };
}
catch (err) {
}
}
result = result.concat(tmp);
tmp = FILTER(c.outEdges(vertex), examples);
for (let i = 0; i < tmp.length; ++i) {
try {
tmp[i] = { edge: CLONE(tmp[i]), vertex: DOCUMENT_HANDLE(tmp[i]._to) };
}
catch (err) {
}
}
result = result.concat(tmp);
}
} else {
result = FILTER(c.edges(vertex), examples);
}
}
else {
WARN("EDGES", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return connected neighbors
////////////////////////////////////////////////////////////////////////////////
function AQL_NEIGHBORS (vertexCollection,
edgeCollection,
vertex,
direction,
examples,
options) {
'use strict';
vertex = TO_ID(vertex, vertexCollection);
var collectionFromVertex = vertex.slice(0, vertexCollection.length);
if (collectionFromVertex !== vertexCollection) {
THROW("NEIGHBORS",
INTERNAL.errors.ERROR_GRAPH_INVALID_PARAMETER,
"",
"specified vertex collection '" + collectionFromVertex +
"' does not match start vertex collection '" + vertexCollection + "'");
}
options = CLONE(options) || {};
// Fallback to JS if we are in the cluster
// Improve the examples. LocalServer can match String -> _id
if (examples !== undefined) {
if (typeof examples === "string") {
examples = [{_id: examples}];
}
if (Array.isArray(examples)) {
examples = examples.map(function(e) {
if (typeof e === "string") {
return {_id: e};
}
return e;
});
}
}
let edges = AQL_EDGES(edgeCollection, vertex, direction, examples, { includeVertices: options.includeData });
let vertices = [];
let distinct = new Set();
if (options.includeData) {
for (let i = 0; i < edges.length; ++i) {
let id;
if (direction === "outbound") {
id = edges[i].edge._to;
}
else if (direction === "inbound") {
id = edges[i].edge._from;
}
else {
// any
if (edges[i].edge._from === vertex) {
id = edges[i].edge._to;
}
else {
id = edges[i].edge._from;
}
}
if (!distinct.has(id)) {
distinct.add(id);
vertices.push(edges[i].vertex);
}
}
}
else {
for (let i = 0; i < edges.length; ++i) {
let id;
if (direction === "outbound") {
id = edges[i]._to;
}
else if (direction === "inbound") {
id = edges[i]._from;
}
else {
// any
if (edges[i]._from === vertex) {
id = edges[i]._to;
}
else {
id = edges[i]._from;
}
}
if (!distinct.has(id)) {
distinct.add(id);
vertices.push(id);
}
}
}
distinct.clear();
return vertices;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock JSF_aql_general_graph_neighbors
////////////////////////////////////////////////////////////////////////////////
function AQL_GRAPH_NEIGHBORS (graphName,
vertexExample,
options) {
'use strict';
options = CLONE(options) || {};
if (! options.hasOwnProperty("direction")) {
options.direction = 'any';
}
var params = {};
if (isCoordinator) {
// JS Fallback for features not yet included in CPP Neighbor version
options.fromVertexExample = vertexExample;
if (options.hasOwnProperty("neighborExamples") && typeof options.neighborExamples === "string") {
options.neighborExamples = {_id : options.neighborExamples};
}
var neighbors = [],
factory = TRAVERSAL.generalGraphDatasourceFactory(graphName);
params = TRAVERSAL_PARAMS();
params.paths = true;
params.uniqueness = {
vertices: "global",
edges: "global"
};
params.minDepth = 1; // Make sure we exclude from depth 1
params.maxDepth = options.maxDepth === undefined ? 1 : options.maxDepth;
params.maxIterations = options.maxIterations;
// add user-defined visitor, if specified
// (NOT SUPPORTED RIGHT NOW, return format will break)
/*
if (typeof options.visitor === "string") {
params.visitor = GET_VISITOR(options.visitor, options);
params.visitorReturnsResults = options.visitorReturnsResults || false;
}
else {
*/
// Injecting additional options into the Visitor
params.visitor = TRAVERSAL_NEIGHBOR_VISITOR.bind({
minDepth: options.minDepth === undefined ? 1 : options.minDepth,
includeData: options.includeData || false
});
// }
var fromVertices = RESOLVE_GRAPH_TO_FROM_VERTICES(graphName, options, "GRAPH_NEIGHBORS");
if (options.edgeExamples) {
params.followEdges = options.edgeExamples;
}
if (options.edgeCollectionRestriction) {
params.edgeCollectionRestriction = options.edgeCollectionRestriction;
}
if (options.vertexCollectionRestriction) {
params.filterVertexCollections = options.vertexCollectionRestriction;
}
if (options.endVertexCollectionRestriction) {
params.filterVertexCollections = options.endVertexCollectionRestriction;
}
// merge other options
Object.keys(options).forEach(function (att) {
if (! params.hasOwnProperty(att)) {
params[att] = options[att];
}
});
fromVertices.forEach(function (v) {
var e = TRAVERSAL_FUNC("GRAPH_NEIGHBORS",
factory,
v._id,
undefined,
options.direction,
params);
neighbors = neighbors.concat(e);
});
var result = [];
let uniqueIds = new Set();
neighbors.forEach(function (n) {
if (!uniqueIds.has(n._id)) {
uniqueIds.add(n._id);
if (FILTER([n], options.neighborExamples).length > 0) {
if (options.includeData) {
result.push(n);
} else {
result.push(n._id);
}
}
}
});
uniqueIds.clear();
return result;
}
params.direction = options.direction;
if (options.hasOwnProperty("edgeExamples")) {
if (!Array.isArray(options.edgeExamples)) {
params.filterEdges = [options.edgeExamples];
} else {
params.filterEdges = options.edgeExamples;
}
}
if (options.hasOwnProperty("neighborExamples")) {
if (!Array.isArray(options.neighborExamples)) {
params.filterVertices = [options.neighborExamples];
} else {
params.filterVertices = options.neighborExamples;
}
}
let graph = graphModule._graph(graphName);
let edgeCollections = graph._edgeCollections().map(function (c) { return c.name();});
if (options.hasOwnProperty("edgeCollectionRestriction")) {
if (!Array.isArray(options.edgeCollectionRestriction)) {
if (typeof options.edgeCollectionRestriction === "string") {
if (!underscore.contains(edgeCollections, options.edgeCollectionRestriction)) {
// Short circuit collection not in graph, cannot find results.
return [];
}
edgeCollections = [options.edgeCollectionRestriction];
}
} else {
edgeCollections = underscore.intersection(edgeCollections, options.edgeCollectionRestriction);
}
}
let vertexCollections = graph._vertexCollections().map(function (c) { return c.name();});
let startVertices = DOCUMENT_IDS_BY_EXAMPLE("GRAPH_NEIGHBORS", vertexCollections, vertexExample);
if (startVertices.length === 0) {
return [];
}
if (options.hasOwnProperty("vertexCollectionRestriction")) {
if (!Array.isArray(options.vertexCollectionRestriction)) {
if (typeof options.vertexCollectionRestriction === "string") {
if (!underscore.contains(vertexCollections, options.vertexCollectionRestriction)) {
// Short circuit collection not in graph, cannot find results.
return [];
}
vertexCollections = [options.vertexCollectionRestriction];
}
} else {
vertexCollections = underscore.intersection(vertexCollections, options.vertexCollectionRestriction);
}
}
if (options.hasOwnProperty("minDepth")) {
params.minDepth = options.minDepth;
}
if (options.hasOwnProperty("maxDepth")) {
params.maxDepth = options.maxDepth;
}
if (options.hasOwnProperty("includeData")) {
params.includeData = options.includeData;
}
return CPP_NEIGHBORS(vertexCollections, edgeCollections, startVertices, params);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock JSF_aql_general_graph_edges
////////////////////////////////////////////////////////////////////////////////
function AQL_GRAPH_EDGES (graphName,
vertexExample,
options) {
'use strict';
options = CLONE(options) || {};
if (! options.hasOwnProperty("direction")) {
options.direction = 'any';
}
var params = {};
// JS Fallback for features not yet included in CPP Neighbor version
options.fromVertexExample = vertexExample;
if (options.hasOwnProperty("neighborExamples") && typeof options.neighborExamples === "string") {
options.neighborExamples = {_id : options.neighborExamples};
}
var edges = [],
factory = TRAVERSAL.generalGraphDatasourceFactory(graphName);
params = TRAVERSAL_PARAMS();
params.paths = true;
params.uniqueness = {
vertices: "none",
edges: "global"
};
params.minDepth = 1; // Make sure we exclude from depth 1
params.maxDepth = options.maxDepth === undefined ? 1 : options.maxDepth;
params.maxIterations = options.maxIterations;
// add user-defined visitor, if specified
// (NOT SUPPORTED RIGHT NOW, return format will break)
/*
if (typeof options.visitor === "string") {
params.visitor = GET_VISITOR(options.visitor, options);
params.visitorReturnsResults = options.visitorReturnsResults || false;
}
else {
*/
// Injecting additional options into the Visitor
var visitorConfig = {
minDepth: options.minDepth === undefined ? 1 : options.minDepth
};
if (options.hasOwnProperty("neighborExamples")) {
visitorConfig.neighborExamples = options.neighborExamples;
}
params.visitor = TRAVERSAL_EDGE_VISITOR.bind(visitorConfig);
var fromVertices = RESOLVE_GRAPH_TO_FROM_VERTICES(graphName, options, "GRAPH_EDGES");
if (options.edgeExamples) {
params.followEdges = options.edgeExamples;
}
if (options.edgeCollectionRestriction) {
params.edgeCollectionRestriction = options.edgeCollectionRestriction;
}
if (options.vertexCollectionRestriction) {
params.filterVertexCollections = options.vertexCollectionRestriction;
}
if (options.endVertexCollectionRestriction) {
params.filterVertexCollections = options.endVertexCollectionRestriction;
}
// merge other options
Object.keys(options).forEach(function (att) {
if (! params.hasOwnProperty(att)) {
params[att] = options[att];
}
});
fromVertices.forEach(function (v) {
var e = TRAVERSAL_FUNC("GRAPH_EDGES",
factory,
v._id,
undefined,
options.direction,
params);
edges = edges.concat(e);
});
var result = [];
let uniqueIds = new Set();
edges.forEach(function (n) {
if (!uniqueIds.has(n._id)) {
uniqueIds.add(n._id);
if (options.includeData) {
result.push(n);
} else {
result.push(n._id);
}
}
});
uniqueIds.clear();
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock JSF_aql_general_graph_vertices
////////////////////////////////////////////////////////////////////////////////
function AQL_GRAPH_VERTICES (graphName,
vertexExamples,
options) {
'use strict';
options = CLONE(options) || {};
if (! options.direction) {
options.direction = 'any';
}
if (options.vertexCollectionRestriction) {
if (options.direction === "inbound") {
options.endVertexCollectionRestriction = options.vertexCollectionRestriction;
} else if (options.direction === "outbound") {
options.startVertexCollectionRestriction = options.vertexCollectionRestriction;
} else {
options.includeOrphans = true;
options.endVertexCollectionRestriction = options.vertexCollectionRestriction;
options.startVertexCollectionRestriction = options.vertexCollectionRestriction;
options.orphanCollectionRestriction = options.vertexCollectionRestriction;
}
}
options.fromVertexExample = vertexExamples;
return RESOLVE_GRAPH_TO_FROM_VERTICES(graphName, options, "GRAPH_VERTICES");
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock JSF_aql_general_graph_common_neighbors
////////////////////////////////////////////////////////////////////////////////
function AQL_GRAPH_COMMON_NEIGHBORS (graphName,
vertex1Examples,
vertex2Examples,
options1,
options2) {
'use strict';
options1 = options1 || {};
options2 = options2 || {};
if (options1.includeData) {
options2.includeData = true;
}
else if (options2.includeData) {
options1.includeData = true;
}
let graph = graphModule._graph(graphName);
let vertexCollections = graph._vertexCollections().map(function (c) { return c.name();});
let vertices1 = DOCUMENT_IDS_BY_EXAMPLE("GRAPH_COMMON_NEIGHBORS", vertexCollections, vertex1Examples);
let vertices2;
if (vertex1Examples === vertex2Examples) {
vertices2 = vertices1;
}
else {
vertices2 = DOCUMENT_IDS_BY_EXAMPLE("GRAPH_COMMON_NEIGHBORS", vertexCollections, vertex2Examples);
}
// Use ES6 Map. Higher performance then Object.
let tmpNeighborsLeft = new Map();
let tmpNeighborsRight = new Map();
let result = [];
// Iterate over left side vertex list as left.
// Calculate its neighbors as ln
// For each entry iterate over right side vertex list as right.
// Calculate their neighbors as rn
// For each store one entry in result {left: left, right: right, neighbors: intersection(ln, rn)}
// All Neighbors are cached in tmpNeighborsLeft and tmpNeighborsRight resp.
for (let i = 0; i < vertices1.length; ++i) {
let left = vertices1[i];
let itemNeighbors;
if(tmpNeighborsLeft.has(left)) {
itemNeighbors = tmpNeighborsLeft.get(left);
}
else {
itemNeighbors = AQL_GRAPH_NEIGHBORS(graphName, left, options1);
tmpNeighborsLeft.set(left, itemNeighbors);
}
for (let j = 0; j < vertices2.length; ++j) {
let right = vertices2[j];
if (left === right) {
continue;
}
let rNeighbors;
if(tmpNeighborsRight.has(right)) {
rNeighbors = tmpNeighborsRight.get(right);
}
else {
rNeighbors = AQL_GRAPH_NEIGHBORS(graphName, right, options2);
tmpNeighborsRight.set(right, rNeighbors);
}
let neighbors;
if (! options1.includeData) {
neighbors = underscore.intersection(itemNeighbors, rNeighbors);
}
else {
// create a quick lookup table for left hand side
let lKeys = { };
for (let i = 0; i < itemNeighbors.length; ++i) {
lKeys[itemNeighbors[i]._id] = true;
}
// check which elements of the right-hand side are also present in the left hand side lookup map
neighbors = [];
for (let i = 0; i < rNeighbors.length; ++i) {
if (lKeys.hasOwnProperty(rNeighbors[i]._id)) {
neighbors.push(rNeighbors[i]);
}
}
}
if (neighbors.length > 0) {
result.push({left, right, neighbors});
}
}
}
// Legacy Format
// result.push(tmpRes);
tmpNeighborsLeft.clear();
tmpNeighborsRight.clear();
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock JSF_aql_general_graph_common_properties
////////////////////////////////////////////////////////////////////////////////
function AQL_GRAPH_COMMON_PROPERTIES (graphName,
vertex1Examples,
vertex2Examples,
options) {
'use strict';
options = CLONE(options) || {};
options.fromVertexExample = vertex1Examples;
options.toVertexExample = vertex2Examples;
options.direction = 'any';
options.ignoreProperties = TO_LIST(options.ignoreProperties, true);
options.startVertexCollectionRestriction = options.vertex1CollectionRestriction;
options.endVertexCollectionRestriction = options.vertex2CollectionRestriction;
var g = RESOLVE_GRAPH_TO_FROM_VERTICES(graphName, options, "GRAPH_COMMON_PROPERTIES");
var g2 = RESOLVE_GRAPH_TO_TO_VERTICES(graphName, options, "GRAPH_COMMON_PROPERTIES");
var res = [];
var t = {};
g.forEach(function (n1) {
Object.keys(n1).forEach(function (key) {
if (key.indexOf("_") === 0 || options.ignoreProperties.indexOf(key) !== -1) {
return;
}
if (!t[JSON.stringify({key : key , value : n1[key]})]) {
t[JSON.stringify({key : key , value : n1[key]})] = {from : [], to : []};
}
t[JSON.stringify({key : key , value : n1[key]})].from.push(n1);
});
});
g2.forEach(function (n1) {
Object.keys(n1).forEach(function (key) {
if (key.indexOf("_") === 0) {
return;
}
if (!t[JSON.stringify({key : key , value : n1[key]})]) {
return;
}
t[JSON.stringify({key : key , value : n1[key]})].to.push(n1);
});
});
var tmp = {};
Object.keys(t).forEach(function (r) {
t[r].from.forEach(function (f) {
if (!tmp[f._id]) {
tmp[f._id] = [];
tmp[f._id + "|keys"] = [];
}
t[r].to.forEach(function (t) {
if (t._id === f._id) {
return;
}
if (tmp[f._id + "|keys"].indexOf(t._id) === -1) {
tmp[f._id + "|keys"].push(t._id);
var obj = {_id : t._id};
Object.keys(f).forEach(function (fromDoc) {
if (t[fromDoc] !== undefined && t[fromDoc] === f[fromDoc]) {
obj[fromDoc] = t[fromDoc];
}
});
tmp[f._id].push(obj);
}
});
});
});
Object.keys(tmp).forEach(function (r) {
if (tmp[r].length === 0 || r.indexOf("|keys") !== -1) {
return;
}
var a = {};
a[r] = tmp[r];
res.push(a);
});
return res;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief Prepares and executes a dijkstra search with predefined result object
/// The result object will be handed over in each traversal step
////////////////////////////////////////////////////////////////////////////////
function RUN_DIJKSTRA_WITH_RESULT_HANDLE (func, graphName, options, result, afterEach) {
result = result || [];
var dijkstraParams = CREATE_DIJKSTRA_PARAMS(graphName, options);
dijkstraParams.fromVertices.forEach(function (v) {
var info = TRAVERSAL_CONFIG(
func,
dijkstraParams.factory,
TO_ID(v),
JSON.parse(JSON.stringify(dijkstraParams.toVertices)),
options.direction,
dijkstraParams.params
);
if (info.vertex !== null) {
var traverser = new TRAVERSAL.Traverser(info.config);
traverser.traverse(result, info.vertex, info.endVertex);
if (typeof afterEach === "function") {
afterEach(result, info.vertex);
}
}
});
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief visitor callback function for absolute eccentricity traversal
////////////////////////////////////////////////////////////////////////////////
function TRAVERSAL_ABSOLUTE_ECCENTRICITY_VISITOR (config, result, node, path) {
'use strict';
result[path.vertices[0]._id] = Math.max(node.dist, result[path.vertices[0]._id] || 0);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock JSF_aql_general_graph_absolute_eccentricity
////////////////////////////////////////////////////////////////////////////////
function AQL_GRAPH_ABSOLUTE_ECCENTRICITY (graphName, vertexExample, options) {
'use strict';
options = CLONE(options) || {};
if (! options.direction) {
options.direction = 'any';
}
if (! options.algorithm) {
options.algorithm = "dijkstra";
}
options.fromVertexExample = vertexExample;
options.toVertexExample = {};
options.visitor = TRAVERSAL_ABSOLUTE_ECCENTRICITY_VISITOR;
return RUN_DIJKSTRA_WITH_RESULT_HANDLE(
"ABSOLUTE_ECCENTRICITY",
graphName,
options,
{}
);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief visitor callback function for absolute eccentricity traversal
////////////////////////////////////////////////////////////////////////////////
function TRAVERSAL_ECCENTRICITY_VISITOR (config, result, node, path) {
'use strict';
if (path.edges.length > 0) {
result.current = Math.min(1 / node.dist, result.current || 1);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock JSF_aql_general_graph_eccentricity
////////////////////////////////////////////////////////////////////////////////
function AQL_GRAPH_ECCENTRICITY (graphName, options) {
'use strict';
options = CLONE(options) || {};
if (! options.direction) {
options.direction = 'any';
}
if (! options.algorithm) {
options.algorithm = "dijkstra";
}
options.fromVertexExample = {};
options.toVertexExample = {};
options.visitor = TRAVERSAL_ECCENTRICITY_VISITOR;
var result = RUN_DIJKSTRA_WITH_RESULT_HANDLE(
"ECCENTRICITY",
graphName,
options,
{
vertices: {},
max: 0
},
function (result, vertex) {
if (result.current === undefined) {
result.vertices[vertex._id] = 0;
} else {
if (result.current > result.max) {
result.max = result.current;
}
result.vertices[vertex._id] = result.current;
delete result.current;
}
}
);
var list = result.vertices;
Object.keys(list).forEach(function (r) {
list[r] /= result.max;
});
return list;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief visitor callback function for absolute closeness traversal
////////////////////////////////////////////////////////////////////////////////
function TRAVERSAL_ABSOLUTE_CLOSENESS_VISITOR (config, result, node, path) {
'use strict';
result[path.vertices[0]._id] = result[path.vertices[0]._id] || 0;
result[path.vertices[0]._id] += node.dist;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock JSF_aql_general_graph_absolute_closeness
////////////////////////////////////////////////////////////////////////////////
function AQL_GRAPH_ABSOLUTE_CLOSENESS (graphName, vertexExample, options) {
'use strict';
options = CLONE(options) || {};
if (! options.direction) {
options.direction = 'any';
}
if (! options.algorithm) {
options.algorithm = "dijkstra";
}
options.fromVertexExample = vertexExample;
options.toVertexExample = {};
options.visitor = TRAVERSAL_ABSOLUTE_CLOSENESS_VISITOR;
return RUN_DIJKSTRA_WITH_RESULT_HANDLE(
"ABSOLUTE_CLOSENESS",
graphName,
options,
{}
);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief visitor callback function for absolute closeness traversal
////////////////////////////////////////////////////////////////////////////////
function TRAVERSAL_CLOSENESS_VISITOR (config, result, node, path) {
'use strict';
result.current += node.dist;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock JSF_aql_general_graph_closeness
////////////////////////////////////////////////////////////////////////////////
function AQL_GRAPH_CLOSENESS (graphName, options) {
'use strict';
options = CLONE(options) || {};
if (! options.direction) {
options.direction = 'any';
}
if (! options.algorithm) {
options.algorithm = "dijkstra";
}
options.fromVertexExample = {};
options.toVertexExample = {};
options.visitor = TRAVERSAL_CLOSENESS_VISITOR;
var result = RUN_DIJKSTRA_WITH_RESULT_HANDLE(
"CLOSENESS",
graphName,
options,
{
vertices: {},
current: 0,
max: 0
},
function(result, vertex) {
if (result.current !== 0) {
result.current = 1 / result.current;
}
if (result.current > result.max) {
result.max = result.current;
}
result.vertices[vertex._id] = result.current;
result.current = 0;
}
);
var list = result.vertices;
Object.keys(list).forEach(function (r) {
list[r] /= result.max;
});
return list;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock JSF_aql_general_graph_absolute_betweenness
////////////////////////////////////////////////////////////////////////////////
function AQL_GRAPH_ABSOLUTE_BETWEENNESS (graphName, options) {
'use strict';
if (typeof options !== "object" || Array.isArray(options)) {
options = {};
} else {
options = CLONE(options);
}
if (! options.direction) {
options.direction = 'any';
}
options.algorithm = "Floyd-Warshall";
// Make sure we ONLY extract _ids
options.includeData = false;
let graph = graphModule._graph(graphName);
let vertexCollections = graph._vertexCollections().map(function (c) { return c.name();});
let vertexIds = DOCUMENT_IDS_BY_EXAMPLE("GRAPH_ABSOLUTE_BETWEENNESS", vertexCollections, {});
let result = {};
let distanceMap = AQL_GRAPH_SHORTEST_PATH(graphName, vertexIds , vertexIds, options);
for (let k = 0; k < vertexIds.length; k++) {
result[vertexIds[k]] = 0;
}
distanceMap.forEach(function(d) {
let startVertex = d.vertices[0];
let l = d.vertices.length;
let targetVertex = d.vertices[l - 1];
let val = 1 / l;
d.vertices.forEach(function (v) {
if (v === startVertex || v === targetVertex) {
return;
}
result[v] += val;
});
});
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock JSF_aql_general_graph_betweenness
////////////////////////////////////////////////////////////////////////////////
function AQL_GRAPH_BETWEENNESS (graphName, options) {
'use strict';
options = CLONE(options) || {};
var result = AQL_GRAPH_ABSOLUTE_BETWEENNESS(graphName, options), max = 0;
Object.keys(result).forEach(function (r) {
if (result[r] > max) {
max = result[r];
}
});
if (max === 0) {
return result;
}
Object.keys(result).forEach(function (r) {
result[r] = result[r] / max;
});
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock JSF_aql_general_graph_radius
////////////////////////////////////////////////////////////////////////////////
function AQL_GRAPH_RADIUS (graphName, options) {
'use strict';
options = CLONE(options) || {};
if (! options.direction) {
options.direction = 'any';
}
if (! options.algorithm) {
options.algorithm = "Floyd-Warshall";
}
var result = AQL_GRAPH_ABSOLUTE_ECCENTRICITY(graphName, {}, options), min = Infinity;
Object.keys(result).forEach(function (r) {
if (result[r] === 0) {
return;
}
if (result[r] < min) {
min = result[r];
}
});
return min;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief was docuBlock JSF_aql_general_graph_diameter
////////////////////////////////////////////////////////////////////////////////
function AQL_GRAPH_DIAMETER (graphName, options) {
'use strict';
options = CLONE(options) || {};
if (! options.direction) {
options.direction = 'any';
}
if (! options.algorithm) {
options.algorithm = "Floyd-Warshall";
}
var result = AQL_GRAPH_ABSOLUTE_ECCENTRICITY(graphName, {}, options),
max = 0;
Object.keys(result).forEach(function (r) {
if (result[r] > max) {
max = result[r];
}
});
return max;
}
exports.FCALL_USER = FCALL_USER;
exports.KEYS = KEYS;
exports.GET_INDEX = GET_INDEX;
exports.DOCUMENT_MEMBER = DOCUMENT_MEMBER;
exports.GET_DOCUMENTS = GET_DOCUMENTS;
exports.TERNARY_OPERATOR = TERNARY_OPERATOR;
exports.LOGICAL_AND = LOGICAL_AND;
exports.LOGICAL_OR = LOGICAL_OR;
exports.LOGICAL_NOT = LOGICAL_NOT;
exports.RELATIONAL_EQUAL = RELATIONAL_EQUAL;
exports.RELATIONAL_UNEQUAL = RELATIONAL_UNEQUAL;
exports.RELATIONAL_GREATER = RELATIONAL_GREATER;
exports.RELATIONAL_GREATEREQUAL = RELATIONAL_GREATEREQUAL;
exports.RELATIONAL_LESS = RELATIONAL_LESS;
exports.RELATIONAL_LESSEQUAL = RELATIONAL_LESSEQUAL;
exports.RELATIONAL_ARRAY_IN = RELATIONAL_ARRAY_IN;
exports.RELATIONAL_ARRAY_NOT_IN = RELATIONAL_ARRAY_NOT_IN;
exports.RELATIONAL_ARRAY_EQUAL = RELATIONAL_ARRAY_EQUAL;
exports.RELATIONAL_ARRAY_UNEQUAL = RELATIONAL_ARRAY_UNEQUAL;
exports.RELATIONAL_ARRAY_GREATER = RELATIONAL_ARRAY_GREATER;
exports.RELATIONAL_ARRAY_GREATEREQUAL = RELATIONAL_ARRAY_GREATEREQUAL;
exports.RELATIONAL_ARRAY_LESS = RELATIONAL_ARRAY_LESS;
exports.RELATIONAL_ARRAY_LESSEQUAL = RELATIONAL_ARRAY_LESSEQUAL;
exports.RELATIONAL_CMP = RELATIONAL_CMP;
exports.RELATIONAL_IN = RELATIONAL_IN;
exports.RELATIONAL_NOT_IN = RELATIONAL_NOT_IN;
exports.UNARY_PLUS = UNARY_PLUS;
exports.UNARY_MINUS = UNARY_MINUS;
exports.ARITHMETIC_PLUS = ARITHMETIC_PLUS;
exports.ARITHMETIC_MINUS = ARITHMETIC_MINUS;
exports.ARITHMETIC_TIMES = ARITHMETIC_TIMES;
exports.ARITHMETIC_DIVIDE = ARITHMETIC_DIVIDE;
exports.ARITHMETIC_MODULUS = ARITHMETIC_MODULUS;
exports.AQL_DOCUMENT = AQL_DOCUMENT;
exports.AQL_COLLECTIONS = AQL_COLLECTIONS;
exports.AQL_COLLECTION_COUNT = AQL_COLLECTION_COUNT;
exports.AQL_CONCAT = AQL_CONCAT;
exports.AQL_CONCAT_SEPARATOR = AQL_CONCAT_SEPARATOR;
exports.AQL_CHAR_LENGTH = AQL_CHAR_LENGTH;
exports.AQL_LOWER = AQL_LOWER;
exports.AQL_UPPER = AQL_UPPER;
exports.AQL_SUBSTRING = AQL_SUBSTRING;
exports.AQL_CONTAINS = AQL_CONTAINS;
exports.AQL_LIKE = AQL_LIKE;
exports.AQL_LEFT = AQL_LEFT;
exports.AQL_RIGHT = AQL_RIGHT;
exports.AQL_TRIM = AQL_TRIM;
exports.AQL_LTRIM = AQL_LTRIM;
exports.AQL_RTRIM = AQL_RTRIM;
exports.AQL_SPLIT = AQL_SPLIT;
exports.AQL_SUBSTITUTE = AQL_SUBSTITUTE;
exports.AQL_MD5 = AQL_MD5;
exports.AQL_SHA1 = AQL_SHA1;
exports.AQL_RANDOM_TOKEN = AQL_RANDOM_TOKEN;
exports.AQL_FIND_FIRST = AQL_FIND_FIRST;
exports.AQL_FIND_LAST = AQL_FIND_LAST;
exports.AQL_TO_BOOL = AQL_TO_BOOL;
exports.AQL_TO_NUMBER = AQL_TO_NUMBER;
exports.AQL_TO_STRING = AQL_TO_STRING;
exports.AQL_TO_ARRAY = AQL_TO_ARRAY;
exports.AQL_ARRAYIZE = AQL_ARRAYIZE;
exports.AQL_TO_LIST = AQL_TO_ARRAY; // alias
exports.AQL_IS_NULL = AQL_IS_NULL;
exports.AQL_IS_BOOL = AQL_IS_BOOL;
exports.AQL_IS_NUMBER = AQL_IS_NUMBER;
exports.AQL_IS_STRING = AQL_IS_STRING;
exports.AQL_IS_ARRAY = AQL_IS_ARRAY;
exports.AQL_IS_LIST = AQL_IS_ARRAY; // alias
exports.AQL_IS_OBJECT = AQL_IS_OBJECT;
exports.AQL_IS_DOCUMENT = AQL_IS_OBJECT; // alias
exports.AQL_IS_DATESTRING = AQL_IS_DATESTRING;
exports.AQL_FLOOR = AQL_FLOOR;
exports.AQL_CEIL = AQL_CEIL;
exports.AQL_ROUND = AQL_ROUND;
exports.AQL_ABS = AQL_ABS;
exports.AQL_RAND = AQL_RAND;
exports.AQL_SQRT = AQL_SQRT;
exports.AQL_POW = AQL_POW;
exports.AQL_LENGTH = AQL_LENGTH;
exports.AQL_FIRST = AQL_FIRST;
exports.AQL_LAST = AQL_LAST;
exports.AQL_POSITION = AQL_POSITION;
exports.AQL_NTH = AQL_NTH;
exports.AQL_REVERSE = AQL_REVERSE;
exports.AQL_RANGE = AQL_RANGE;
exports.AQL_UNIQUE = AQL_UNIQUE;
exports.AQL_SORTED_UNIQUE = AQL_SORTED_UNIQUE;
exports.AQL_UNION = AQL_UNION;
exports.AQL_UNION_DISTINCT = AQL_UNION_DISTINCT;
exports.AQL_CALL = AQL_CALL;
exports.AQL_APPLY = AQL_APPLY;
exports.AQL_REMOVE_VALUE = AQL_REMOVE_VALUE;
exports.AQL_REMOVE_VALUES = AQL_REMOVE_VALUES;
exports.AQL_REMOVE_NTH = AQL_REMOVE_NTH;
exports.AQL_PUSH = AQL_PUSH;
exports.AQL_APPEND = AQL_APPEND;
exports.AQL_POP = AQL_POP;
exports.AQL_SHIFT = AQL_SHIFT;
exports.AQL_UNSHIFT = AQL_UNSHIFT;
exports.AQL_SLICE = AQL_SLICE;
exports.AQL_MINUS = AQL_MINUS;
exports.AQL_INTERSECTION = AQL_INTERSECTION;
exports.AQL_FLATTEN = AQL_FLATTEN;
exports.AQL_MAX = AQL_MAX;
exports.AQL_MIN = AQL_MIN;
exports.AQL_SUM = AQL_SUM;
exports.AQL_AVERAGE = AQL_AVERAGE;
exports.AQL_MEDIAN = AQL_MEDIAN;
exports.AQL_PERCENTILE = AQL_PERCENTILE;
exports.AQL_VARIANCE_SAMPLE = AQL_VARIANCE_SAMPLE;
exports.AQL_VARIANCE_POPULATION = AQL_VARIANCE_POPULATION;
exports.AQL_STDDEV_SAMPLE = AQL_STDDEV_SAMPLE;
exports.AQL_STDDEV_POPULATION = AQL_STDDEV_POPULATION;
exports.AQL_NEAR = AQL_NEAR;
exports.AQL_WITHIN = AQL_WITHIN;
exports.AQL_WITHIN_RECTANGLE = AQL_WITHIN_RECTANGLE;
exports.AQL_IS_IN_POLYGON = AQL_IS_IN_POLYGON;
exports.AQL_FULLTEXT = AQL_FULLTEXT;
exports.AQL_PATHS = AQL_PATHS;
exports.AQL_SHORTEST_PATH = AQL_SHORTEST_PATH;
exports.AQL_TRAVERSAL = AQL_TRAVERSAL;
exports.AQL_TRAVERSAL_TREE = AQL_TRAVERSAL_TREE;
exports.AQL_EDGES = AQL_EDGES;
exports.AQL_NEIGHBORS = AQL_NEIGHBORS;
exports.AQL_GRAPH_TRAVERSAL = AQL_GRAPH_TRAVERSAL;
exports.AQL_GRAPH_TRAVERSAL_TREE = AQL_GRAPH_TRAVERSAL_TREE;
exports.AQL_GRAPH_EDGES = AQL_GRAPH_EDGES;
exports.AQL_GRAPH_VERTICES = AQL_GRAPH_VERTICES;
exports.AQL_GRAPH_PATHS = AQL_GRAPH_PATHS;
exports.AQL_GRAPH_SHORTEST_PATH = AQL_GRAPH_SHORTEST_PATH;
exports.AQL_GRAPH_DISTANCE_TO = AQL_GRAPH_DISTANCE_TO;
exports.AQL_GRAPH_NEIGHBORS = AQL_GRAPH_NEIGHBORS;
exports.AQL_GRAPH_COMMON_NEIGHBORS = AQL_GRAPH_COMMON_NEIGHBORS;
exports.AQL_GRAPH_COMMON_PROPERTIES = AQL_GRAPH_COMMON_PROPERTIES;
exports.AQL_GRAPH_ECCENTRICITY = AQL_GRAPH_ECCENTRICITY;
exports.AQL_GRAPH_BETWEENNESS = AQL_GRAPH_BETWEENNESS;
exports.AQL_GRAPH_CLOSENESS = AQL_GRAPH_CLOSENESS;
exports.AQL_GRAPH_ABSOLUTE_ECCENTRICITY = AQL_GRAPH_ABSOLUTE_ECCENTRICITY;
exports.AQL_GRAPH_ABSOLUTE_BETWEENNESS = AQL_GRAPH_ABSOLUTE_BETWEENNESS;
exports.AQL_GRAPH_ABSOLUTE_CLOSENESS = AQL_GRAPH_ABSOLUTE_CLOSENESS;
exports.AQL_GRAPH_DIAMETER = AQL_GRAPH_DIAMETER;
exports.AQL_GRAPH_RADIUS = AQL_GRAPH_RADIUS;
exports.AQL_NOT_NULL = AQL_NOT_NULL;
exports.AQL_FIRST_LIST = AQL_FIRST_LIST;
exports.AQL_FIRST_DOCUMENT = AQL_FIRST_DOCUMENT;
exports.AQL_PARSE_IDENTIFIER = AQL_PARSE_IDENTIFIER;
exports.AQL_IS_SAME_COLLECTION = AQL_IS_SAME_COLLECTION;
exports.AQL_HAS = AQL_HAS;
exports.AQL_ATTRIBUTES = AQL_ATTRIBUTES;
exports.AQL_VALUES = AQL_VALUES;
exports.AQL_ZIP = AQL_ZIP;
exports.AQL_UNSET = AQL_UNSET;
exports.AQL_UNSET_RECURSIVE = AQL_UNSET_RECURSIVE;
exports.AQL_KEEP = AQL_KEEP;
exports.AQL_MERGE = AQL_MERGE;
exports.AQL_MERGE_RECURSIVE = AQL_MERGE_RECURSIVE;
exports.AQL_TRANSLATE = AQL_TRANSLATE;
exports.AQL_MATCHES = AQL_MATCHES;
exports.AQL_PASSTHRU = AQL_PASSTHRU;
exports.AQL_TEST_INTERNAL = AQL_TEST_INTERNAL;
exports.AQL_SLEEP = AQL_SLEEP;
exports.AQL_CURRENT_DATABASE = AQL_CURRENT_DATABASE;
exports.AQL_CURRENT_USER = AQL_CURRENT_USER;
exports.AQL_FAIL = AQL_FAIL;
exports.AQL_DATE_NOW = AQL_DATE_NOW;
exports.AQL_DATE_TIMESTAMP = AQL_DATE_TIMESTAMP;
exports.AQL_DATE_ISO8601 = AQL_DATE_ISO8601;
exports.AQL_DATE_DAYOFWEEK = AQL_DATE_DAYOFWEEK;
exports.AQL_DATE_YEAR = AQL_DATE_YEAR;
exports.AQL_DATE_MONTH = AQL_DATE_MONTH;
exports.AQL_DATE_DAY = AQL_DATE_DAY;
exports.AQL_DATE_HOUR = AQL_DATE_HOUR;
exports.AQL_DATE_MINUTE = AQL_DATE_MINUTE;
exports.AQL_DATE_SECOND = AQL_DATE_SECOND;
exports.AQL_DATE_MILLISECOND = AQL_DATE_MILLISECOND;
exports.AQL_DATE_DAYOFYEAR = AQL_DATE_DAYOFYEAR;
exports.AQL_DATE_ISOWEEK = AQL_DATE_ISOWEEK;
exports.AQL_DATE_LEAPYEAR = AQL_DATE_LEAPYEAR;
exports.AQL_DATE_QUARTER = AQL_DATE_QUARTER;
exports.AQL_DATE_DAYS_IN_MONTH = AQL_DATE_DAYS_IN_MONTH;
exports.AQL_DATE_ADD = AQL_DATE_ADD;
exports.AQL_DATE_SUBTRACT = AQL_DATE_SUBTRACT;
exports.AQL_DATE_DIFF = AQL_DATE_DIFF;
exports.AQL_DATE_COMPARE = AQL_DATE_COMPARE;
exports.AQL_DATE_FORMAT = AQL_DATE_FORMAT;
exports.reload = reloadUserFunctions;
exports.clearCaches = clearCaches;
// initialize the query engine
exports.clearCaches();
//reloadUserFunctions();