/*jshint strict: false */ //////////////////////////////////////////////////////////////////////////////// /// @brief querying and managing collections /// /// @file /// /// DISCLAIMER /// /// Copyright 2014 ArangoDB 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 ArangoDB GmbH, Cologne, Germany /// /// @author Achim Brandt /// @author Copyright 2014, ArangoDB GmbH, Cologne, Germany /// @author Copyright 2012, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// var arangodb = require("@arangodb"); var actions = require("@arangodb/actions"); var cluster = require("@arangodb/cluster"); //////////////////////////////////////////////////////////////////////////////// /// @brief return a prefixed URL //////////////////////////////////////////////////////////////////////////////// function databasePrefix (req, url) { // location response (e.g. /_db/dbname/_api/collection/xyz) return "/_db/" + encodeURIComponent(arangodb.db._name()) + url; } //////////////////////////////////////////////////////////////////////////////// /// @brief collection representation //////////////////////////////////////////////////////////////////////////////// function collectionRepresentation (collection, showProperties, showCount, showFigures) { var result = {}; result.id = collection._id; result.name = collection.name(); result.isSystem = (result.name.charAt(0) === '_'); if (showProperties) { var properties = collection.properties(); result.doCompact = properties.doCompact; result.isVolatile = properties.isVolatile; result.journalSize = properties.journalSize; result.keyOptions = properties.keyOptions; result.waitForSync = properties.waitForSync; result.indexBuckets = properties.indexBuckets; if (cluster.isCoordinator()) { result.shardKeys = properties.shardKeys; result.numberOfShards = properties.numberOfShards; result.replicationFactor = properties.replicationFactor; } } if (showCount) { result.count = collection.count(); } if (showFigures) { var figures = collection.figures(); if (figures) { result.figures = figures; } } result.status = collection.status(); result.type = collection.type(); return result; } //////////////////////////////////////////////////////////////////////////////// /// @brief helper to parse arguments for creating collections //////////////////////////////////////////////////////////////////////////////// function parseBodyForCreateCollection (req, res) { var body = actions.getJsonBody(req, res); if (body === undefined) { return { bodyIsEmpty: true }; } var r = {}; if (! body.hasOwnProperty("name")) { r.name = ""; } else { r.name = body.name; } r.parameters = { waitForSync : require("internal").options()["database.wait-for-sync"] }; r.type = arangodb.ArangoCollection.TYPE_DOCUMENT; if (body.hasOwnProperty("doCompact")) { r.parameters.doCompact = body.doCompact; } if (body.hasOwnProperty("isSystem")) { r.parameters.isSystem = (body.isSystem && r.name[0] === '_'); } if (body.hasOwnProperty("id")) { r.parameters.id = body.id; } if (body.hasOwnProperty("isVolatile")) { r.parameters.isVolatile = body.isVolatile; } if (body.hasOwnProperty("journalSize")) { r.parameters.journalSize = body.journalSize; } if (body.hasOwnProperty("indexBuckets")) { r.parameters.indexBuckets = body.indexBuckets; } if (body.hasOwnProperty("keyOptions")) { r.parameters.keyOptions = body.keyOptions; } if (body.hasOwnProperty("type")) { r.type = body.type; } if (body.hasOwnProperty("waitForSync")) { r.parameters.waitForSync = body.waitForSync; } if (cluster.isCoordinator()) { if (body.hasOwnProperty("shardKeys")) { r.parameters.shardKeys = body.shardKeys || { }; } if (body.hasOwnProperty("numberOfShards")) { r.parameters.numberOfShards = body.numberOfShards || 0; } if (body.hasOwnProperty("distributeShardsLike")) { r.parameters.distributeShardsLike = body.distributeShardsLike || ""; } if (body.hasOwnProperty("replicationFactor")) { r.parameters.replicationFactor = body.replicationFactor || ""; } if (body.hasOwnProperty("servers")) { r.parameters.servers = body.servers || ""; } } return r; } //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock JSF_post_api_collection //////////////////////////////////////////////////////////////////////////////// function post_api_collection (req, res) { var r = parseBodyForCreateCollection(req, res); if (r.bodyIsEmpty) { return; // error in JSON, is already reported } if (r.name === "") { actions.resultBad(req, res, arangodb.ERROR_ARANGO_ILLEGAL_NAME, "name must be non-empty"); return; } try { var collection; if (typeof(r.type) === "string") { if (r.type.toLowerCase() === "edge" || r.type === "3") { r.type = arangodb.ArangoCollection.TYPE_EDGE; } } if (r.type === arangodb.ArangoCollection.TYPE_EDGE) { collection = arangodb.db._createEdgeCollection(r.name, r.parameters); } else { collection = arangodb.db._createDocumentCollection(r.name, r.parameters); } var result = {}; result.id = collection._id; result.name = collection.name(); result.waitForSync = r.parameters.waitForSync || false; result.isVolatile = r.parameters.isVolatile || false; result.isSystem = r.parameters.isSystem || false; result.status = collection.status(); result.type = collection.type(); result.keyOptions = collection.keyOptions; if (cluster.isCoordinator()) { result.shardKeys = collection.shardKeys; result.numberOfShards = collection.numberOfShards; result.distributeShardsLike = collection.distributeShardsLike || ""; } var headers = { location: databasePrefix(req, "/_api/collection/" + result.name) }; actions.resultOk(req, res, actions.HTTP_OK, result, headers); } catch (err) { actions.resultException(req, res, err, undefined, false); } } //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock JSF_get_api_collections //////////////////////////////////////////////////////////////////////////////// function get_api_collections (req, res) { var excludeSystem; var collections = arangodb.db._collections(); excludeSystem = false; if (req.parameters.hasOwnProperty('excludeSystem')) { var value = req.parameters.excludeSystem.toLowerCase(); if (value === 'true' || value === 'yes' || value === 'on' || value === 'y' || value === '1') { excludeSystem = true; } } var list = []; for (var i = 0; i < collections.length; ++i) { var collection = collections[i]; var rep = collectionRepresentation(collection); // include system collections or exclude them? if (! excludeSystem || rep.name.substr(0, 1) !== '_') { list.push(rep); } } actions.resultOk(req, res, actions.HTTP_OK, { result : list }); } //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock JSA_get_api_collection_name //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock JSA_get_api_collection_properties //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock JSA_get_api_collection_count //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock JSA_get_api_collection_figures //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock JSA_get_api_collection_revision //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock JSA_get_api_collection_checksum //////////////////////////////////////////////////////////////////////////////// function get_api_collection (req, res) { var name; var result; var sub; // ............................................................................. // /_api/collection // ............................................................................. if (req.suffix.length === 0 && req.parameters.id === undefined) { get_api_collections(req, res); return; } // ............................................................................. // /_api/collection/ // ............................................................................. name = decodeURIComponent(req.suffix[0]); var collection = arangodb.db._collection(name); if (collection === null) { actions.collectionNotFound(req, res, name); return; } var headers; // ............................................................................. // /_api/collection/ // ............................................................................. if (req.suffix.length === 1) { result = collectionRepresentation(collection, false, false, false); headers = { location : databasePrefix(req, "/_api/collection/" + collection.name()) }; actions.resultOk(req, res, actions.HTTP_OK, result, headers); return; } if (req.suffix.length === 2) { sub = decodeURIComponent(req.suffix[1]); // ............................................................................. // /_api/collection//checksum // ............................................................................. if (sub === "checksum") { var withRevisions = false; var withData = false; var value; if (req.parameters.hasOwnProperty('withRevisions')) { value = req.parameters.withRevisions.toLowerCase(); if (value === 'true' || value === 'yes' || value === 'on' || value === 'y' || value === '1') { withRevisions = true; } } if (req.parameters.hasOwnProperty('withData')) { value = req.parameters.withData.toLowerCase(); if (value === 'true' || value === 'yes' || value === 'on' || value === 'y' || value === '1') { withData = true; } } result = collectionRepresentation(collection, false, false, false); var checksum = collection.checksum(withRevisions, withData); result.checksum = checksum.checksum; result.revision = checksum.revision; actions.resultOk(req, res, actions.HTTP_OK, result); } // ............................................................................. // /_api/collection//figures // ............................................................................. else if (sub === "figures") { result = collectionRepresentation(collection, true, true, true); headers = { location : databasePrefix(req, "/_api/collection/" + collection.name() + "/figures") }; actions.resultOk(req, res, actions.HTTP_OK, result, headers); } // ............................................................................. // /_api/collection//count // ............................................................................. else if (sub === "count") { result = collectionRepresentation(collection, true, true, false); headers = { location : databasePrefix(req, "/_api/collection/" + collection.name() + "/count") }; actions.resultOk(req, res, actions.HTTP_OK, result, headers); } // ............................................................................. // /_api/collection//properties // ............................................................................. else if (sub === "properties") { result = collectionRepresentation(collection, true, false, false); headers = { location : databasePrefix(req, "/_api/collection/" + collection.name() + "/properties") }; actions.resultOk(req, res, actions.HTTP_OK, result, headers); } // ............................................................................. // /_api/collection//revision // ............................................................................. else if (sub === "revision") { result = collectionRepresentation(collection, false, false, false); result.revision = collection.revision(); actions.resultOk(req, res, actions.HTTP_OK, result); } else { actions.resultNotFound(req, res, arangodb.ERROR_HTTP_NOT_FOUND, "expecting one of the resources 'count'," +" 'figures', 'properties', 'parameter'"); } } else { actions.resultBad(req, res, arangodb.ERROR_HTTP_BAD_PARAMETER, "expect GET /_api/collection//"); } } //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock JSF_put_api_collection_load //////////////////////////////////////////////////////////////////////////////// function put_api_collection_load (req, res, collection) { try { collection.load(); var showCount = true; var body = actions.getJsonBody(req, res); if (body && body.hasOwnProperty("count")) { showCount = body.count; } var result = collectionRepresentation(collection, false, showCount, false); actions.resultOk(req, res, actions.HTTP_OK, result); } catch (err) { actions.resultException(req, res, err, undefined, false); } } //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock JSF_put_api_collection_unload //////////////////////////////////////////////////////////////////////////////// function put_api_collection_unload (req, res, collection) { try { if (req.parameters.hasOwnProperty('flush')) { var value = req.parameters.flush.toLowerCase(); if (value === 'true' || value === 'yes' || value === 'on' || value === 'y' || value === '1') { if (collection.status() === 3 /* loaded */ && collection.figures().uncollectedLogfileEntries > 0) { // flush WAL so uncollected logfile entries can get collected require("internal").wal.flush(); } } } // then unload collection.unload(); var result = collectionRepresentation(collection); actions.resultOk(req, res, actions.HTTP_OK, result); } catch (err) { actions.resultException(req, res, err, undefined, false); } } //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock JSF_put_api_collection_truncate //////////////////////////////////////////////////////////////////////////////// function put_api_collection_truncate (req, res, collection) { try { collection.truncate(); var result = collectionRepresentation(collection); actions.resultOk(req, res, actions.HTTP_OK, result); } catch (err) { actions.resultException(req, res, err, undefined, false); } } //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock JSF_put_api_collection_properties //////////////////////////////////////////////////////////////////////////////// function put_api_collection_properties (req, res, collection) { var body = actions.getJsonBody(req, res); if (body === undefined) { return; } try { collection.properties(body); var result = collectionRepresentation(collection, true); actions.resultOk(req, res, actions.HTTP_OK, result); } catch (err) { actions.resultException(req, res, err, undefined, false); } } //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock JSF_put_api_collection_rename //////////////////////////////////////////////////////////////////////////////// function put_api_collection_rename (req, res, collection) { var body = actions.getJsonBody(req, res); if (body === undefined) { return; } if (! body.hasOwnProperty("name")) { actions.resultBad(req, res, arangodb.ERROR_ARANGO_ILLEGAL_NAME, "name must be non-empty"); return; } var name = body.name; try { collection.rename(name); var result = collectionRepresentation(collection); actions.resultOk(req, res, actions.HTTP_OK, result); } catch (err) { actions.resultException(req, res, err, undefined, false); } } //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock JSF_put_api_collection_rotate //////////////////////////////////////////////////////////////////////////////// function put_api_collection_rotate (req, res, collection) { try { collection.rotate(); actions.resultOk(req, res, actions.HTTP_OK, { result: true }); } catch (err) { actions.resultException(req, res, err, undefined, false); } } //////////////////////////////////////////////////////////////////////////////// /// @brief changes a collection //////////////////////////////////////////////////////////////////////////////// function put_api_collection (req, res) { if (req.suffix.length !== 2) { actions.resultBad(req, res, arangodb.ERROR_HTTP_BAD_PARAMETER, "expected PUT /_api/collection//"); return; } var name = decodeURIComponent(req.suffix[0]); var collection = arangodb.db._collection(name); if (collection === null) { actions.collectionNotFound(req, res, name); return; } var sub = decodeURIComponent(req.suffix[1]); if (sub === "load") { put_api_collection_load(req, res, collection); } else if (sub === "unload") { put_api_collection_unload(req, res, collection); collection = null; // run garbage collection once in all threads require("internal").executeGlobalContextFunction("collectGarbage"); } else if (sub === "truncate") { put_api_collection_truncate(req, res, collection); } else if (sub === "properties") { put_api_collection_properties(req, res, collection); } else if (sub === "rename") { put_api_collection_rename(req, res, collection); } else if (sub === "rotate") { put_api_collection_rotate(req, res, collection); } else { actions.resultNotFound(req, res, arangodb.ERROR_HTTP_NOT_FOUND, "expecting one of the actions 'load', 'unload'," + " 'truncate', 'properties', 'rename'"); } } //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock JSF_delete_api_collection //////////////////////////////////////////////////////////////////////////////// function delete_api_collection (req, res) { if (req.suffix.length !== 1) { actions.resultBad(req, res, arangodb.ERROR_HTTP_BAD_PARAMETER, "expected DELETE /_api/collection/"); } else { var name = decodeURIComponent(req.suffix[0]); var collection = arangodb.db._collection(name); if (collection === null) { actions.collectionNotFound(req, res, name); } else { try { var result = { id : collection._id }; collection.drop(); actions.resultOk(req, res, actions.HTTP_OK, result); } catch (err) { actions.resultException(req, res, err, undefined, false); } } } } //////////////////////////////////////////////////////////////////////////////// /// @brief handles a collection request //////////////////////////////////////////////////////////////////////////////// actions.defineHttp({ url : "_api/collection", callback : function (req, res) { try { if (req.requestType === actions.GET) { get_api_collection(req, res); } else if (req.requestType === actions.DELETE) { delete_api_collection(req, res); } else if (req.requestType === actions.POST) { post_api_collection(req, res); } else if (req.requestType === actions.PUT) { put_api_collection(req, res); } else { actions.resultUnsupported(req, res); } } catch (err) { actions.resultException(req, res, err, undefined, false); } } });