/*jshint strict: false */ //////////////////////////////////////////////////////////////////////////////// /// @brief AQL user functions management /// /// @file /// /// DISCLAIMER /// /// Copyright 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 arangodb = require("@arangodb"); var db = arangodb.db; var ArangoError = arangodb.ArangoError; //////////////////////////////////////////////////////////////////////////////// /// @brief return the _aqlfunctions collection //////////////////////////////////////////////////////////////////////////////// var getStorage = function () { 'use strict'; var functions = db._collection("_aqlfunctions"); if (functions === null) { var err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_ARANGO_COLLECTION_NOT_FOUND.code; err.errorMessage = "collection '_aqlfunctions' not found"; throw err; } return functions; }; //////////////////////////////////////////////////////////////////////////////// /// @brief apply a prefix filter on the functions //////////////////////////////////////////////////////////////////////////////// var getFiltered = function (group) { 'use strict'; var result = [ ]; if (group !== null && group !== undefined && group.length > 0) { var prefix = group.toUpperCase(); if (group.length > 1 && group.substr(group.length - 2, 2) !== '::') { prefix += '::'; } getStorage().toArray().forEach(function (f) { if (f.name.toUpperCase().substr(0, prefix.length) === prefix) { result.push(f); } }); } else { result = getStorage().toArray(); } return result; }; //////////////////////////////////////////////////////////////////////////////// /// @brief validate a function name //////////////////////////////////////////////////////////////////////////////// var validateName = function (name) { 'use strict'; if (typeof name !== 'string' || ! name.match(/^[a-zA-Z0-9_]+(::[a-zA-Z0-9_]+)+$/) || name.substr(0, 1) === "_") { var err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_NAME.code; err.errorMessage = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_NAME.message; throw err; } }; //////////////////////////////////////////////////////////////////////////////// /// @brief validate user function code //////////////////////////////////////////////////////////////////////////////// var stringifyFunction = function (code, name) { 'use strict'; if (typeof code === 'function') { code = String(code) + "\n"; } if (typeof code === 'string') { code = "(" + code + "\n)"; if (! internal.parse) { // no parsing possible. assume always valid return code; } try { if (internal.parse(code, name)) { // parsing successful return code; } } catch (e) { } } // fall-through intentional var err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_CODE.code; err.errorMessage = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_CODE.message; throw err; }; //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock aqlFunctionsUnregister //////////////////////////////////////////////////////////////////////////////// var unregisterFunction = function (name) { 'use strict'; var func = null; validateName(name); try { func = getStorage().document(name.toUpperCase()); } catch (err1) { } if (func === null) { var err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code; err.errorMessage = internal.sprintf(arangodb.errors.ERROR_QUERY_FUNCTION_NOT_FOUND.message, name); throw err; } getStorage().remove(func._id); internal.reloadAqlFunctions(); return true; }; //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock aqlFunctionsUnregisterGroup //////////////////////////////////////////////////////////////////////////////// var unregisterFunctionsGroup = function (group) { 'use strict'; if (group.length === 0) { var err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code; err.errorMessage = arangodb.errors.ERROR_BAD_PARAMETER.message; throw err; } var deleted = 0; getFiltered(group).forEach(function (f) { getStorage().remove(f._id); deleted++; }); if (deleted > 0) { internal.reloadAqlFunctions(); } return deleted; }; //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock aqlFunctionsRegister //////////////////////////////////////////////////////////////////////////////// var registerFunction = function (name, code, isDeterministic) { // validate input validateName(name); code = stringifyFunction(code, name); var testCode = "(function() { var callback = " + code + "; return callback; })()"; var err; try { if (internal && internal.hasOwnProperty("executeScript")) { var evalResult = internal.executeScript(testCode, undefined, "(user function " + name + ")"); if (typeof evalResult !== "function") { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_CODE.code; err.errorMessage = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_CODE.message + ": code must be contained in function"; throw err; } } } catch (err1) { err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_CODE.code; err.errorMessage = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_CODE.message; throw err; } var result = db._executeTransaction({ collections: { write: getStorage().name() }, action: function (params) { var exists = false; var collection = require("internal").db._collection(params.collection); var name = params.name; try { collection.remove(name.toUpperCase()); exists = true; } catch (err2) { } var data = { _key: name.toUpperCase(), name: name, code: params.code, isDeterministic: params.isDeterministic || false }; collection.save(data); return exists; }, params: { name: name, code: code, isDeterministic: isDeterministic, collection: getStorage().name() } }); internal.reloadAqlFunctions(); return result; }; //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock aqlFunctionsToArray //////////////////////////////////////////////////////////////////////////////// var toArrayFunctions = function (group) { 'use strict'; var result = [ ]; getFiltered(group).forEach(function (f) { result.push({ name: f.name, code: f.code.substr(1, f.code.length - 2).trim() }); }); return result; }; exports.unregister = unregisterFunction; exports.unregisterGroup = unregisterFunctionsGroup; exports.register = registerFunction; exports.toArray = toArrayFunctions;