//////////////////////////////////////////////////////////////////////////////// /// DISCLAIMER /// /// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany /// Copyright 2004-2014 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 ArangoDB GmbH, Cologne, Germany /// /// @author Jan Steemann //////////////////////////////////////////////////////////////////////////////// #include "RestCursorHandler.h" #include "Aql/Query.h" #include "Aql/QueryRegistry.h" #include "Basics/Exceptions.h" #include "Basics/json.h" #include "Basics/MutexLocker.h" #include "Basics/ScopeGuard.h" #include "Basics/VelocyPackHelper.h" #include "Utils/Cursor.h" #include "Utils/CursorRepository.h" #include "V8Server/ApplicationV8.h" #include #include #include using namespace triagens::arango; using namespace triagens::rest; RestCursorHandler::RestCursorHandler( HttpRequest* request, std::pair* pair) : RestVocbaseBaseHandler(request), _applicationV8(pair->first), _queryRegistry(pair->second), _queryLock(), _query(nullptr), _queryKilled(false) {} //////////////////////////////////////////////////////////////////////////////// /// {@inheritDoc} //////////////////////////////////////////////////////////////////////////////// HttpHandler::status_t RestCursorHandler::execute() { // extract the sub-request type HttpRequest::HttpRequestType type = _request->requestType(); if (type == HttpRequest::HTTP_REQUEST_POST) { createCursor(); return status_t(HANDLER_DONE); } if (type == HttpRequest::HTTP_REQUEST_PUT) { modifyCursor(); return status_t(HANDLER_DONE); } if (type == HttpRequest::HTTP_REQUEST_DELETE) { deleteCursor(); return status_t(HANDLER_DONE); } generateError(HttpResponse::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); return status_t(HANDLER_DONE); } //////////////////////////////////////////////////////////////////////////////// /// {@inheritDoc} //////////////////////////////////////////////////////////////////////////////// bool RestCursorHandler::cancel() { return cancelQuery(); } //////////////////////////////////////////////////////////////////////////////// /// @brief processes the query and returns the results/cursor /// this method is also used by derived classes //////////////////////////////////////////////////////////////////////////////// void RestCursorHandler::processQuery(VPackSlice const& slice) { if (!slice.isObject()) { generateError(HttpResponse::BAD, TRI_ERROR_QUERY_EMPTY); return; } VPackSlice const querySlice = slice.get("query"); if (!querySlice.isString()) { generateError(HttpResponse::BAD, TRI_ERROR_QUERY_EMPTY); return; } VPackSlice const bindVars = slice.get("bindVars"); if (!bindVars.isNone()) { if (!bindVars.isObject() && !bindVars.isNull()) { generateError(HttpResponse::BAD, TRI_ERROR_TYPE_ERROR, "expecting object for "); return; } } VPackBuilder optionsBuilder = buildOptions(slice); VPackSlice options = optionsBuilder.slice(); VPackValueLength l; char const* queryString = querySlice.getString(l); triagens::aql::Query query( _applicationV8, false, _vocbase, queryString, static_cast(l), (!bindVars.isNone() ? triagens::basics::VelocyPackHelper::velocyPackToJson(bindVars) : nullptr), triagens::basics::VelocyPackHelper::velocyPackToJson(options), triagens::aql::PART_MAIN); registerQuery(&query); auto queryResult = query.execute(_queryRegistry); unregisterQuery(); if (queryResult.code != TRI_ERROR_NO_ERROR) { if (queryResult.code == TRI_ERROR_REQUEST_CANCELED || (queryResult.code == TRI_ERROR_QUERY_KILLED && wasCanceled())) { THROW_ARANGO_EXCEPTION(TRI_ERROR_REQUEST_CANCELED); } THROW_ARANGO_EXCEPTION_MESSAGE(queryResult.code, queryResult.details); } TRI_ASSERT(TRI_IsArrayJson(queryResult.json)); { createResponse(HttpResponse::CREATED); _response->setContentType("application/json; charset=utf-8"); triagens::basics::Json extra = buildExtra(queryResult); size_t batchSize = triagens::basics::VelocyPackHelper::getNumericValue( options, "batchSize", 1000); size_t const n = TRI_LengthArrayJson(queryResult.json); if (n <= batchSize) { // result is smaller than batchSize and will be returned directly. no need // to create a cursor triagens::basics::Json result(triagens::basics::Json::Object, 7); result.set("result", triagens::basics::Json(TRI_UNKNOWN_MEM_ZONE, queryResult.json, triagens::basics::Json::AUTOFREE)); queryResult.json = nullptr; result.set("hasMore", triagens::basics::Json(false)); if (triagens::basics::VelocyPackHelper::getBooleanValue(options, "count", false)) { result.set("count", triagens::basics::Json(static_cast(n))); } result.set("cached", triagens::basics::Json(queryResult.cached)); if (queryResult.cached) { result.set("extra", triagens::basics::Json(triagens::basics::Json::Object)); } else { result.set("extra", extra); } result.set("error", triagens::basics::Json(false)); result.set("code", triagens::basics::Json( static_cast(_response->responseCode()))); result.dump(_response->body()); return; } // result is bigger than batchSize, and a cursor will be created auto cursors = static_cast( _vocbase->_cursorRepository); TRI_ASSERT(cursors != nullptr); double ttl = triagens::basics::VelocyPackHelper::getNumericValue( options, "ttl", 30); bool count = triagens::basics::VelocyPackHelper::getBooleanValue( options, "count", false); // steal the query JSON, cursor will take over the ownership auto j = queryResult.json; triagens::arango::JsonCursor* cursor = cursors->createFromJson( j, batchSize, extra.steal(), ttl, count, queryResult.cached); queryResult.json = nullptr; try { _response->body().appendChar('{'); cursor->dump(_response->body()); _response->body().appendText(",\"error\":false,\"code\":"); _response->body().appendInteger( static_cast(_response->responseCode())); _response->body().appendChar('}'); cursors->release(cursor); } catch (...) { cursors->release(cursor); throw; } } } //////////////////////////////////////////////////////////////////////////////// /// @brief register the currently running query //////////////////////////////////////////////////////////////////////////////// void RestCursorHandler::registerQuery(triagens::aql::Query* query) { MUTEX_LOCKER(_queryLock); TRI_ASSERT(_query == nullptr); _query = query; } //////////////////////////////////////////////////////////////////////////////// /// @brief unregister the currently running query //////////////////////////////////////////////////////////////////////////////// void RestCursorHandler::unregisterQuery() { MUTEX_LOCKER(_queryLock); _query = nullptr; } //////////////////////////////////////////////////////////////////////////////// /// @brief cancel the currently running query //////////////////////////////////////////////////////////////////////////////// bool RestCursorHandler::cancelQuery() { MUTEX_LOCKER(_queryLock); if (_query != nullptr) { _query->killed(true); _queryKilled = true; return true; } return false; } //////////////////////////////////////////////////////////////////////////////// /// @brief whether or not the query was canceled //////////////////////////////////////////////////////////////////////////////// bool RestCursorHandler::wasCanceled() { MUTEX_LOCKER(_queryLock); return _queryKilled; } //////////////////////////////////////////////////////////////////////////////// /// @brief build options for the query as JSON //////////////////////////////////////////////////////////////////////////////// VPackBuilder RestCursorHandler::buildOptions(VPackSlice const& slice) const { VPackBuilder options; options.openObject(); VPackSlice count = slice.get("count"); if (count.isBool()) { options.add("count", count); } else { options.add("count", VPackValue(false)); } VPackSlice batchSize = slice.get("batchSize"); if (batchSize.isNumber()) { if ((batchSize.isDouble() && batchSize.getDouble() == 0.0) || (batchSize.isInteger() && batchSize.getUInt() == 0)) { THROW_ARANGO_EXCEPTION_MESSAGE( TRI_ERROR_TYPE_ERROR, "expecting non-zero value for "); } options.add("batchSize", batchSize); } else { options.add("batchSize", VPackValue(1000)); } bool hasCache = false; VPackSlice cache = slice.get("cache"); if (cache.isBool()) { hasCache = true; options.add("cache", cache); } VPackSlice opts = slice.get("options"); if (opts.isObject()) { for (auto const& it : VPackObjectIterator(opts)) { if (!it.key.isString() || it.value.isNone()) { continue; } std::string keyName = it.key.copyString(); if (keyName != "count" && keyName != "batchSize") { if (keyName == "cache" && hasCache) { continue; } options.add(keyName, it.value); } } } VPackSlice ttl = slice.get("ttl"); if (ttl.isNumber()) { options.add("ttl", ttl); } else { options.add("ttl", VPackValue(30)); } options.close(); return options; } //////////////////////////////////////////////////////////////////////////////// /// @brief builds the "extra" attribute values from the result. /// note that the "extra" object will take ownership from the result for /// several values //////////////////////////////////////////////////////////////////////////////// triagens::basics::Json RestCursorHandler::buildExtra( triagens::aql::QueryResult& queryResult) const { // build "extra" attribute triagens::basics::Json extra(triagens::basics::Json::Object); VPackSlice stats = queryResult.stats.slice(); if (!stats.isNone()) { extra.set("stats", triagens::basics::Json( TRI_UNKNOWN_MEM_ZONE, triagens::basics::VelocyPackHelper::velocyPackToJson(stats), triagens::basics::Json::AUTOFREE)); } if (queryResult.profile != nullptr) { extra.set("profile", triagens::basics::Json(TRI_UNKNOWN_MEM_ZONE, queryResult.profile, triagens::basics::Json::AUTOFREE)); queryResult.profile = nullptr; } if (queryResult.warnings == nullptr) { extra.set("warnings", triagens::basics::Json(triagens::basics::Json::Array)); } else { extra.set("warnings", triagens::basics::Json(TRI_UNKNOWN_MEM_ZONE, queryResult.warnings, triagens::basics::Json::AUTOFREE)); queryResult.warnings = nullptr; } return extra; } //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock JSF_post_api_cursor //////////////////////////////////////////////////////////////////////////////// void RestCursorHandler::createCursor() { std::vector const& suffix = _request->suffix(); if (suffix.size() != 0) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "expecting POST /_api/cursor"); return; } try { bool parseSuccess = true; VPackOptions options; std::shared_ptr parsedBody = parseVelocyPackBody(&options, parseSuccess); if (!parseSuccess) { return; } VPackSlice body = parsedBody.get()->slice(); processQuery(body); } catch (triagens::basics::Exception const& ex) { unregisterQuery(); generateError(HttpResponse::responseCode(ex.code()), ex.code(), ex.what()); } catch (std::bad_alloc const&) { unregisterQuery(); generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_OUT_OF_MEMORY); } catch (std::exception const& ex) { unregisterQuery(); generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_INTERNAL, ex.what()); } catch (...) { unregisterQuery(); generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_INTERNAL); } } //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock JSF_post_api_cursor_identifier //////////////////////////////////////////////////////////////////////////////// void RestCursorHandler::modifyCursor() { std::vector const& suffix = _request->suffix(); if (suffix.size() != 1) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "expecting PUT /_api/cursor/"); return; } std::string const& id = suffix[0]; auto cursors = static_cast( _vocbase->_cursorRepository); TRI_ASSERT(cursors != nullptr); auto cursorId = static_cast( triagens::basics::StringUtils::uint64(id)); bool busy; auto cursor = cursors->find(cursorId, busy); if (cursor == nullptr) { if (busy) { generateError(HttpResponse::responseCode(TRI_ERROR_CURSOR_BUSY), TRI_ERROR_CURSOR_BUSY); } else { generateError(HttpResponse::responseCode(TRI_ERROR_CURSOR_NOT_FOUND), TRI_ERROR_CURSOR_NOT_FOUND); } return; } try { createResponse(HttpResponse::OK); _response->setContentType("application/json; charset=utf-8"); _response->body().appendChar('{'); cursor->dump(_response->body()); _response->body().appendText(",\"error\":false,\"code\":"); _response->body().appendInteger( static_cast(_response->responseCode())); _response->body().appendChar('}'); cursors->release(cursor); } catch (triagens::basics::Exception const& ex) { cursors->release(cursor); generateError(HttpResponse::responseCode(ex.code()), ex.code(), ex.what()); } catch (...) { cursors->release(cursor); generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_INTERNAL); } } //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock JSF_post_api_cursor_delete //////////////////////////////////////////////////////////////////////////////// void RestCursorHandler::deleteCursor() { std::vector const& suffix = _request->suffix(); if (suffix.size() != 1) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "expecting DELETE /_api/cursor/"); return; } std::string const& id = suffix[0]; auto cursors = static_cast( _vocbase->_cursorRepository); TRI_ASSERT(cursors != nullptr); auto cursorId = static_cast( triagens::basics::StringUtils::uint64(id)); bool found = cursors->remove(cursorId); if (!found) { generateError(HttpResponse::NOT_FOUND, TRI_ERROR_CURSOR_NOT_FOUND); return; } createResponse(HttpResponse::ACCEPTED); _response->setContentType("application/json; charset=utf-8"); triagens::basics::Json json(triagens::basics::Json::Object); json.set("id", triagens::basics::Json(id)); // id as a string! json.set("error", triagens::basics::Json(false)); json.set("code", triagens::basics::Json( static_cast(_response->responseCode()))); json.dump(_response->body()); }