mirror of https://gitee.com/bigwinds/arangodb
607 lines
16 KiB
JavaScript
607 lines
16 KiB
JavaScript
'use strict';
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief JavaScript base module
|
|
// /
|
|
// / @file
|
|
// /
|
|
// / DISCLAIMER
|
|
// /
|
|
// / Copyright 2004-2013 triAGENS GmbH, Cologne, Germany
|
|
// /
|
|
// / Licensed under the Apache License, Version 2.0 (the "License")
|
|
// / you may not use this file except in compliance with the License.
|
|
// / You may obtain a copy of the License at
|
|
// /
|
|
// / http://www.apache.org/licenses/LICENSE-2.0
|
|
// /
|
|
// / Unless required by applicable law or agreed to in writing, software
|
|
// / distributed under the License is distributed on an "AS IS" BASIS,
|
|
// / WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// / See the License for the specific language governing permissions and
|
|
// / limitations under the License.
|
|
// /
|
|
// / Copyright holder is triAGENS GmbH, Cologne, Germany
|
|
// /
|
|
// / @author Dr. Frank Celler
|
|
// / @author Copyright 2012-2013, triAGENS GmbH, Cologne, Germany
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
var internal = require('internal');
|
|
var fs = require('fs');
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief errors
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
Object.keys(internal.errors).forEach(function(key) {
|
|
exports[key] = internal.errors[key].code;
|
|
});
|
|
|
|
function isAqlQuery(query) {
|
|
return Boolean(query && typeof query.query === "string" && query.bindVars);
|
|
}
|
|
|
|
function isGeneratedAqlQuery(query) {
|
|
return isAqlQuery(query) && typeof query._source === "function";
|
|
}
|
|
|
|
function isAqlLiteral(literal) {
|
|
return Boolean(literal && typeof literal.toAQL === "function");
|
|
}
|
|
|
|
exports.aql = function aql(templateStrings, ...args) {
|
|
const strings = [...templateStrings];
|
|
const bindVars = {};
|
|
const bindVals = [];
|
|
let query = strings[0];
|
|
for (let i = 0; i < args.length; i++) {
|
|
const rawValue = args[i];
|
|
let value = rawValue;
|
|
if (isGeneratedAqlQuery(rawValue)) {
|
|
const src = rawValue._source();
|
|
if (src.args.length) {
|
|
query += src.strings[0];
|
|
args.splice(i, 1, ...src.args);
|
|
strings.splice(
|
|
i,
|
|
2,
|
|
strings[i] + src.strings[0],
|
|
...src.strings.slice(1, src.args.length),
|
|
src.strings[src.args.length] + strings[i + 1]
|
|
);
|
|
} else {
|
|
query += rawValue.query + strings[i + 1];
|
|
args.splice(i, 1);
|
|
strings.splice(i, 2, strings[i] + rawValue.query + strings[i + 1]);
|
|
}
|
|
i -= 1;
|
|
continue;
|
|
}
|
|
if (rawValue === undefined) {
|
|
query += strings[i + 1];
|
|
continue;
|
|
}
|
|
if (isAqlLiteral(rawValue)) {
|
|
query += `${rawValue.toAQL()}${strings[i + 1]}`;
|
|
continue;
|
|
}
|
|
const index = bindVals.indexOf(rawValue);
|
|
const isKnown = index !== -1;
|
|
let name = `value${isKnown ? index : bindVals.length}`;
|
|
if (rawValue && rawValue.isArangoCollection) {
|
|
name = `@${name}`;
|
|
value = rawValue.name();
|
|
}
|
|
if (!isKnown) {
|
|
bindVals.push(rawValue);
|
|
bindVars[name] = value;
|
|
}
|
|
query += `@${name}${strings[i + 1]}`;
|
|
}
|
|
return {
|
|
query,
|
|
bindVars,
|
|
_source: () => ({ strings, args })
|
|
};
|
|
};
|
|
|
|
exports.aql.literal = function (value) {
|
|
if (isAqlLiteral(value)) {
|
|
return value;
|
|
}
|
|
return {
|
|
toAQL() {
|
|
if (value === undefined) {
|
|
return "";
|
|
}
|
|
return String(value);
|
|
}
|
|
};
|
|
};
|
|
|
|
exports.aql.join = function (values, sep = " ") {
|
|
if (!values.length) {
|
|
return exports.aql``;
|
|
}
|
|
if (values.length === 1) {
|
|
return exports.aql`${values[0]}`;
|
|
}
|
|
return exports.aql(
|
|
["", ...Array(values.length - 1).fill(sep), ""],
|
|
...values
|
|
);
|
|
};
|
|
|
|
exports.isValidDocumentKey = function (documentKey) {
|
|
if (!documentKey) return false;
|
|
// see VocBase/KeyGenerator.cpp keyCharLookupTable
|
|
return /^[-_!$%'()*+,.:;=@0-9a-z]+$/i.test(documentKey);
|
|
};
|
|
|
|
exports.errors = internal.errors;
|
|
|
|
exports.time = internal.time;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief ArangoError
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.ArangoError = internal.ArangoError;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief defines a module
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.defineModule = function(path, file) {
|
|
let content = fs.read(file);
|
|
let mc = internal.db._collection('_modules');
|
|
|
|
if (mc === null) {
|
|
// lazily create _modules collection if it does not yet exist
|
|
const DEFAULT_REPLICATION_FACTOR_SYSTEM = internal.DEFAULT_REPLICATION_FACTOR_SYSTEM;
|
|
mc = internal.db._create('_modules', {
|
|
isSystem: true,
|
|
journalSize: 1024 * 1024,
|
|
replicationFactor: DEFAULT_REPLICATION_FACTOR_SYSTEM,
|
|
distributeShardsLike: '_graphs'
|
|
});
|
|
}
|
|
|
|
path = module.normalize(path);
|
|
let m = mc.firstExample({path: path});
|
|
|
|
if (m === null) {
|
|
mc.save({path: path, content: content});
|
|
} else {
|
|
mc.replace(m, {path: path, content: content});
|
|
}
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief normalizeURL
|
|
// /
|
|
// / If @FA{path} starts with "." or "..", then it is a relative path.
|
|
// / Otherwise it is an absolute path. Normalizing will remove `//`,
|
|
// / `/./`, `/../` from the url - expect in the beginning, where it keeps
|
|
// / `../` and or at most one `./`.
|
|
// /
|
|
// / If @FA{path} is empty, the url `./` will be returned.
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.normalizeURL = function(path) {
|
|
var i;
|
|
var n;
|
|
var p;
|
|
var q;
|
|
var r;
|
|
var x;
|
|
|
|
if (path === '') {
|
|
return './';
|
|
}
|
|
|
|
p = path.split('/');
|
|
|
|
// relative path
|
|
if (p[0] === '.' || p[0] === '..') {
|
|
r = p[0] + '/';
|
|
p.shift();
|
|
q = p;
|
|
} else if (p[0] === '') {
|
|
// absolute path
|
|
r = '/';
|
|
p.shift();
|
|
q = p;
|
|
} else {
|
|
// assume that the path is relative
|
|
r = './';
|
|
q = p;
|
|
}
|
|
|
|
// normalize path
|
|
n = [];
|
|
|
|
for (i = 0; i < q.length; ++i) {
|
|
x = q[i];
|
|
|
|
if (x === '..') {
|
|
if (n.length === 0) {
|
|
if (r === '../') {
|
|
n.push(x);
|
|
} else if (r === './') {
|
|
r = '../';
|
|
} else {
|
|
throw "cannot use '..' to escape top-level-directory";
|
|
}
|
|
} else if (n[n.length - 1] === '..') {
|
|
n.push(x);
|
|
} else {
|
|
n.pop();
|
|
}
|
|
} else if (x !== '' && x !== '.') {
|
|
n.push(x);
|
|
}
|
|
}
|
|
|
|
return r + n.join('/');
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief inspect
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.inspect = internal.inspect;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief output
|
|
// /
|
|
// / In order to allow "capture" output to work, we cannot assigne the
|
|
// / function here.
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.output = function() {
|
|
internal.output.apply(internal.output, arguments);
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief print
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.print = internal.print;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief printf
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.printf = internal.printf;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief sprintf
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.sprintf = internal.sprintf;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief printObject
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.printObject = internal.printObject;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief 2D ASCII table printing
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.printTable = function(list, columns, options) {
|
|
options = options || {};
|
|
if (options.totalString === undefined) {
|
|
options.totalString = '%s document(s)\n';
|
|
}
|
|
|
|
var pad = '...';
|
|
var descriptions, matrix, col, what, j;
|
|
|
|
if (columns === undefined) {
|
|
what = list[0];
|
|
} else if (Array.isArray(columns)) {
|
|
what = {};
|
|
|
|
columns.forEach(function(col) {
|
|
what[col] = null;
|
|
});
|
|
} else {
|
|
what = columns;
|
|
}
|
|
|
|
j = 0;
|
|
descriptions = [];
|
|
matrix = [[]];
|
|
|
|
for (col in what) {
|
|
if (what.hasOwnProperty(col)) {
|
|
var fixedLength = null;
|
|
|
|
if (columns && columns.hasOwnProperty(col) && columns[col] > 0) {
|
|
fixedLength = columns[col] >= pad.length ? columns[col] : pad.length;
|
|
}
|
|
|
|
// header
|
|
var name = col;
|
|
|
|
// rename header?
|
|
if (options.hasOwnProperty('rename')) {
|
|
if (options.rename.hasOwnProperty(col)) {
|
|
name = options.rename[col];
|
|
}
|
|
}
|
|
|
|
descriptions.push({
|
|
id: col,
|
|
fixedLength: fixedLength,
|
|
length: fixedLength || name.length,
|
|
});
|
|
|
|
matrix[0][j++] = name;
|
|
}
|
|
}
|
|
|
|
// determine values & max widths
|
|
list.forEach(function(row, i) {
|
|
matrix[i + 1] = [];
|
|
descriptions.forEach(function(col) {
|
|
if (row.hasOwnProperty(col.id)) {
|
|
var value;
|
|
if (options.prettyStrings && typeof row[col.id] === 'string') {
|
|
value = row[col.id];
|
|
} else {
|
|
value = JSON.stringify(row[col.id]) || '';
|
|
}
|
|
|
|
matrix[i + 1].push(value);
|
|
|
|
if (value.length > col.length && !col.fixedLength) {
|
|
col.length = Math.min(value.length, 100);
|
|
}
|
|
} else {
|
|
// undefined
|
|
matrix[i + 1].push('');
|
|
}
|
|
});
|
|
});
|
|
|
|
var divider = function() {
|
|
var parts = [];
|
|
descriptions.forEach(function(desc) {
|
|
parts.push(exports.stringPadding('', desc.length, '-', 'r'));
|
|
});
|
|
|
|
if (options.framed) {
|
|
return '+-' + parts.join('-+-') + '-+\n';
|
|
}
|
|
|
|
return parts.join(' ') + '\n';
|
|
};
|
|
|
|
var compose = function() {
|
|
var result = '';
|
|
|
|
if (options.framed) {
|
|
result += divider();
|
|
}
|
|
matrix.forEach(function(row, i) {
|
|
var parts = [];
|
|
|
|
row.forEach(function(col, j) {
|
|
var len = descriptions[j].length,
|
|
value = row[j];
|
|
if (value.length > len) {
|
|
value = value.substr(0, len - pad.length) + pad;
|
|
}
|
|
parts.push(exports.stringPadding(value, len, ' ', 'r'));
|
|
});
|
|
|
|
if (options.framed) {
|
|
result += '| ' + parts.join(' | ') + ' |\n';
|
|
} else {
|
|
result += parts.join(' ') + '\n';
|
|
}
|
|
|
|
if (i === 0) {
|
|
result += divider();
|
|
}
|
|
});
|
|
|
|
result += divider();
|
|
|
|
if (!options.hideTotal) {
|
|
result += internal.sprintf(options.totalString, String(list.length));
|
|
}
|
|
return result;
|
|
};
|
|
|
|
if (!Array.isArray(list)) {
|
|
// not an array
|
|
return;
|
|
}
|
|
|
|
if (list.length === 0) {
|
|
exports.print(options.emptyString || 'no document(s)');
|
|
} else {
|
|
exports.print(compose());
|
|
}
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief stringPadding
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.stringPadding = function(str, len, pad, dir) {
|
|
// yes, this is more code than new Array(length).join(chr), but it makes jslint happy
|
|
function fill(length, chr) {
|
|
var result = '',
|
|
i;
|
|
for (i = 0; i < length; ++i) {
|
|
result += chr;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
if (typeof len === 'undefined') {
|
|
len = 0;
|
|
}
|
|
if (typeof pad === 'undefined') {
|
|
pad = ' ';
|
|
}
|
|
|
|
if (len + 1 >= str.length) {
|
|
switch (dir || 'r') {
|
|
// LEFT
|
|
case 'l':
|
|
str = fill(len + 1 - str.length, pad) + str;
|
|
break;
|
|
|
|
// BOTH
|
|
case 'b':
|
|
var padlen = len - str.length;
|
|
var right = Math.ceil(padlen / 2);
|
|
var left = padlen - right;
|
|
str = fill(left + 1, pad) + str + fill(right + 1, pad);
|
|
break;
|
|
|
|
default:
|
|
str = str + fill(len + 1 - str.length, pad);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return str;
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief throws an error in case of missing file
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.throwFileNotFound = function(msg) {
|
|
throw new exports.ArangoError({
|
|
errorNum: exports.errors.ERROR_FILE_NOT_FOUND.code,
|
|
errorMessage:
|
|
exports.errors.ERROR_FILE_NOT_FOUND.message + ': ' + String(msg),
|
|
});
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief throws an error in case of a bad parameter
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.throwBadParameter = function(msg) {
|
|
throw new exports.ArangoError({
|
|
errorNum: exports.errors.ERROR_BAD_PARAMETER.code,
|
|
errorMessage:
|
|
exports.errors.ERROR_BAD_PARAMETER.message + ': ' + String(msg),
|
|
});
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief checks parameter, throws an error if missing
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.checkParameter = function(usage, descs, vars) {
|
|
var i;
|
|
|
|
for (i = 0; i < descs.length; ++i) {
|
|
var desc = descs[i];
|
|
|
|
if (typeof vars[i] === 'undefined') {
|
|
exports.throwBadParameter(desc[0] + ' missing, usage: ' + usage);
|
|
}
|
|
|
|
if (typeof vars[i] !== desc[1]) {
|
|
exports.throwBadParameter(
|
|
desc[0] +
|
|
" should be a '" +
|
|
desc[1] +
|
|
"', " +
|
|
"not '" +
|
|
typeof vars[i] +
|
|
"'"
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief generate info message for newer version(s) available
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.checkAvailableVersions = function(version) {
|
|
var console = require('console');
|
|
var log;
|
|
|
|
if (require('@arangodb').isServer) {
|
|
log = console.info;
|
|
} else {
|
|
log = internal.print;
|
|
}
|
|
|
|
if (version === undefined) {
|
|
version = internal.version;
|
|
}
|
|
|
|
if (version.match(/beta|alpha|preview|milestone|devel/) !== null) {
|
|
log(
|
|
"You are using a milestone/alpha/beta/preview version ('" +
|
|
version +
|
|
"') of ArangoDB"
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (require('@arangodb').isServer || internal.isEnterprise()) {
|
|
// don't check for version updates in the server
|
|
// nor in the enterprise version
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var u =
|
|
'https://www.arangodb.com/repositories/versions.php?version=' +
|
|
version +
|
|
'&os=' +
|
|
internal.platform;
|
|
var d = internal.download(u, '', {timeout: 5});
|
|
var v = JSON.parse(d.body);
|
|
|
|
if (v.hasOwnProperty('bugfix')) {
|
|
log(
|
|
"Please note that a new bugfix version '" +
|
|
v.bugfix.version +
|
|
"' is available"
|
|
);
|
|
}
|
|
|
|
if (v.hasOwnProperty('minor')) {
|
|
log(
|
|
"Please note that a new minor version '" +
|
|
v.minor.version +
|
|
"' is available"
|
|
);
|
|
}
|
|
|
|
if (v.hasOwnProperty('major')) {
|
|
log(
|
|
"Please note that a new major version '" +
|
|
v.major.version +
|
|
"' is available"
|
|
);
|
|
}
|
|
} catch (err) {
|
|
if (console && console.debug) {
|
|
console.debug('cannot check for newer version: ', err.stack);
|
|
}
|
|
}
|
|
};
|
|
|
|
exports.query = function query (strings, ...args) {
|
|
return internal.db._query(exports.aql(strings, ...args));
|
|
};
|