1111#include < string>
1212#include < vector>
1313
14+ #include " engine/ExecuteUpdate.h"
1415#include " engine/ExportQueryExecutionTrees.h"
1516#include " engine/QueryPlanner.h"
1617#include " global/RuntimeParameters.h"
@@ -43,7 +44,7 @@ Server::Server(unsigned short port, size_t numThreads,
4344 enablePatternTrick_ (usePatternTrick),
4445 // The number of server threads currently also is the number of queries
4546 // that can be processed simultaneously.
46- threadPool_ {numThreads} {
47+ queryThreadPool_ {numThreads} {
4748 // This also directly triggers the update functions and propagates the
4849 // values of the parameters to the cache.
4950 RuntimeParameters ().setOnUpdateAction <" cache-max-num-entries" >(
@@ -394,6 +395,25 @@ Awaitable<void> Server::process(
394395 logCommand (cmd, " clear cache completely (including unpinned elements)" );
395396 cache_.clearAll ();
396397 response = createJsonResponse (composeCacheStatsJson (), request);
398+ } else if (auto cmd = checkParameter (" cmd" , " clear-delta-triples" )) {
399+ requireValidAccessToken (" clear-delta-triples" );
400+ logCommand (cmd, " clear delta triples" );
401+ // The function requires a SharedCancellationHandle, but the operation is
402+ // not cancellable.
403+ auto handle = std::make_shared<ad_utility::CancellationHandle<>>();
404+ // We don't directly `co_await` because of lifetime issues (bugs) in the
405+ // Conan setup.
406+ auto coroutine = computeInNewThread (
407+ updateThreadPool_,
408+ [this ] {
409+ // Use `this` explicitly to silence false-positive errors on the
410+ // captured `this` being unused.
411+ this ->index_ .deltaTriplesManager ().clear ();
412+ },
413+ handle);
414+ co_await std::move (coroutine);
415+ response = createOkResponse (" Delta triples have been cleared" , request,
416+ MediaType::textPlain);
397417 } else if (auto cmd = checkParameter (" cmd" , " get-settings" )) {
398418 logCommand (cmd, " get server settings" );
399419 response = createJsonResponse (RuntimeParameters ().toMap (), request);
@@ -470,9 +490,10 @@ Awaitable<void> Server::process(
470490 };
471491 auto visitUpdate =
472492 [&checkParameter, &accessTokenOk, &request, &send, ¶meters,
473- &requestTimer,
474- this ]( const ad_utility::url_parser::sparqlOperation::Update& update)
493+ &requestTimer, this , &requireValidAccessToken](
494+ const ad_utility::url_parser::sparqlOperation::Update& update)
475495 -> Awaitable<void > {
496+ requireValidAccessToken (" SPARQL Update" );
476497 if (auto timeLimit = co_await verifyUserSubmittedQueryTimeout (
477498 checkParameter (" timeout" , std::nullopt ), accessTokenOk, request,
478499 send)) {
@@ -521,17 +542,17 @@ std::pair<bool, bool> Server::determineResultPinning(
521542 checkParameter (params, " pinresult" , " true" ).has_value ();
522543 return {pinSubtrees, pinResult};
523544}
545+
524546// ____________________________________________________________________________
525- Awaitable< Server::PlannedQuery> Server::setupPlannedQuery (
547+ Server::PlannedQuery Server::setupPlannedQuery (
526548 const ad_utility::url_parser::ParamValueMap& params,
527549 const std::string& operation, QueryExecutionContext& qec,
528550 SharedCancellationHandle handle, TimeLimit timeLimit,
529- const ad_utility::Timer& requestTimer) {
551+ const ad_utility::Timer& requestTimer) const {
530552 auto queryDatasets = ad_utility::url_parser::parseDatasetClauses (params);
531- std::optional<PlannedQuery> plannedQuery =
532- co_await parseAndPlan (operation, queryDatasets, qec, handle, timeLimit);
533- AD_CORRECTNESS_CHECK (plannedQuery.has_value ());
534- auto & qet = plannedQuery.value ().queryExecutionTree_ ;
553+ PlannedQuery plannedQuery =
554+ parseAndPlan (operation, queryDatasets, qec, handle, timeLimit);
555+ auto & qet = plannedQuery.queryExecutionTree_ ;
535556 qet.isRoot () = true ; // allow pinning of the final result
536557 auto timeForQueryPlanning = requestTimer.msecs ();
537558 auto & runtimeInfoWholeQuery =
@@ -541,7 +562,7 @@ Awaitable<Server::PlannedQuery> Server::setupPlannedQuery(
541562 << " ms" << std::endl;
542563 LOG (TRACE) << qet.getCacheKey () << std::endl;
543564
544- co_return std::move ( plannedQuery. value ()) ;
565+ return plannedQuery;
545566}
546567// _____________________________________________________________________________
547568json Server::composeErrorResponseJson (
@@ -751,6 +772,18 @@ MediaType Server::determineMediaType(
751772 return mediaType.value ();
752773}
753774
775+ // ____________________________________________________________________________
776+ ad_utility::websocket::MessageSender Server::createMessageSender (
777+ const std::weak_ptr<ad_utility::websocket::QueryHub>& queryHub,
778+ const ad_utility::httpUtils::HttpRequest auto & request,
779+ const string& operation) {
780+ auto queryHubLock = queryHub.lock ();
781+ AD_CORRECTNESS_CHECK (queryHubLock);
782+ ad_utility::websocket::MessageSender messageSender{
783+ getQueryId (request, operation), *queryHubLock};
784+ return messageSender;
785+ }
786+
754787// ____________________________________________________________________________
755788Awaitable<void > Server::processQuery (
756789 const ad_utility::url_parser::ParamValueMap& params, const string& query,
@@ -761,11 +794,8 @@ Awaitable<void> Server::processQuery(
761794 LOG (INFO) << " Requested media type of result is \" "
762795 << ad_utility::toString (mediaType) << " \" " << std::endl;
763796
764- auto queryHub = queryHub_.lock ();
765- AD_CORRECTNESS_CHECK (queryHub);
766- ad_utility::websocket::MessageSender messageSender{getQueryId (request, query),
767- *queryHub};
768-
797+ ad_utility::websocket::MessageSender messageSender =
798+ createMessageSender (queryHub_, request, query);
769799 auto [cancellationHandle, cancelTimeoutOnDestruction] =
770800 setupCancellationHandle (messageSender.getQueryId (), timeLimit);
771801
@@ -779,15 +809,35 @@ Awaitable<void> Server::processQuery(
779809 QueryExecutionContext qec (index_, &cache_, allocator_,
780810 sortPerformanceEstimator_, std::ref (messageSender),
781811 pinSubtrees, pinResult);
782- auto plannedQuery = co_await setupPlannedQuery (
783- params, query, qec, cancellationHandle, timeLimit, requestTimer);
812+
813+ // The usage of an `optional` here is required because of a limitation in
814+ // Boost::Asio which forces us to use default-constructible result types with
815+ // `computeInNewThread`. We also can't unwrap the optional directly in this
816+ // function, because then the conan build fails in a very strange way,
817+ // probably related to issues in GCC's coroutine implementation.
818+ // For the same reason (crashes in the conanbuild) we store the coroutine in
819+ // an explicit variable instead of directly `co_await`-ing it.
820+ auto coroutine = computeInNewThread (
821+ queryThreadPool_,
822+ [this , ¶ms, &query, &qec, cancellationHandle, &timeLimit,
823+ &requestTimer]() -> std::optional<PlannedQuery> {
824+ return setupPlannedQuery (params, query, qec, cancellationHandle,
825+ timeLimit, requestTimer);
826+ },
827+ cancellationHandle);
828+ auto plannedQueryOpt = co_await std::move (coroutine);
829+ AD_CORRECTNESS_CHECK (plannedQueryOpt.has_value ());
830+ auto plannedQuery = std::move (plannedQueryOpt).value ();
784831 auto qet = plannedQuery.queryExecutionTree_ ;
785832
786833 if (plannedQuery.parsedQuery_ .hasUpdateClause ()) {
787834 // This may be caused by a bug (the code is not yet tested well) or by an
788835 // attack which tries to circumvent (not yet existing) access controls for
789836 // Update.
790- throw std::runtime_error (" Expected normal query but received update query" );
837+ throw std::runtime_error (
838+ absl::StrCat (" SPARQL QUERY was request via the HTTP request, but the "
839+ " following update was sent instead of an update: " ,
840+ plannedQuery.parsedQuery_ ._originalString ));
791841 }
792842
793843 // Read the export limit from the send` parameter (historical name). This
@@ -833,6 +883,80 @@ Awaitable<void> Server::processQuery(
833883 co_return ;
834884}
835885
886+ // ____________________________________________________________________________
887+ void Server::processUpdateImpl (
888+ const ad_utility::url_parser::ParamValueMap& params, const string& update,
889+ ad_utility::Timer& requestTimer, TimeLimit timeLimit, auto & messageSender,
890+ ad_utility::SharedCancellationHandle cancellationHandle,
891+ DeltaTriples& deltaTriples) {
892+ auto [pinSubtrees, pinResult] = determineResultPinning (params);
893+ LOG (INFO) << " Processing the following SPARQL update:"
894+ << (pinResult ? " [pin result]" : " " )
895+ << (pinSubtrees ? " [pin subresults]" : " " ) << " \n "
896+ << update << std::endl;
897+ QueryExecutionContext qec (index_, &cache_, allocator_,
898+ sortPerformanceEstimator_, std::ref (messageSender),
899+ pinSubtrees, pinResult);
900+ auto plannedQuery = setupPlannedQuery (params, update, qec, cancellationHandle,
901+ timeLimit, requestTimer);
902+ auto qet = plannedQuery.queryExecutionTree_ ;
903+
904+ if (!plannedQuery.parsedQuery_ .hasUpdateClause ()) {
905+ throw std::runtime_error (
906+ absl::StrCat (" SPARQL UPDATE was request via the HTTP request, but the "
907+ " following query was sent instead of an update: " ,
908+ plannedQuery.parsedQuery_ ._originalString ));
909+ }
910+ ExecuteUpdate::executeUpdate (index_, plannedQuery.parsedQuery_ , qet,
911+ deltaTriples, cancellationHandle);
912+
913+ LOG (INFO) << " Done processing update"
914+ << " , total time was " << requestTimer.msecs ().count () << " ms"
915+ << std::endl;
916+ LOG (DEBUG) << " Runtime Info:\n "
917+ << qet.getRootOperation ()->runtimeInfo ().toString () << std::endl;
918+ }
919+
920+ // ____________________________________________________________________________
921+ Awaitable<void > Server::processUpdate (
922+ const ad_utility::url_parser::ParamValueMap& params, const string& update,
923+ ad_utility::Timer& requestTimer,
924+ const ad_utility::httpUtils::HttpRequest auto & request, auto && send,
925+ TimeLimit timeLimit) {
926+ auto messageSender = createMessageSender (queryHub_, request, update);
927+
928+ auto [cancellationHandle, cancelTimeoutOnDestruction] =
929+ setupCancellationHandle (messageSender.getQueryId (), timeLimit);
930+
931+ // Update the delta triples.
932+
933+ // Note: We don't directly `co_await` because of lifetime issues (probably
934+ // bugs in GCC or Boost) that occur in the Conan build.
935+ auto coroutine = computeInNewThread (
936+ updateThreadPool_,
937+ [this , ¶ms, &update, &requestTimer, &timeLimit, &messageSender,
938+ &cancellationHandle] {
939+ index_.deltaTriplesManager ().modify (
940+ [this , ¶ms, &update, &requestTimer, &timeLimit, &messageSender,
941+ &cancellationHandle](auto & deltaTriples) {
942+ // Use `this` explicitly to silence false-positive errors on
943+ // captured `this` being unused.
944+ this ->processUpdateImpl (params, update, requestTimer, timeLimit,
945+ messageSender, cancellationHandle,
946+ deltaTriples);
947+ });
948+ },
949+ cancellationHandle);
950+ co_await std::move (coroutine);
951+
952+ // TODO<qup42> send a proper response
953+ // SPARQL 1.1 Protocol 2.2.4 Successful Responses: "The response body of a
954+ // successful update request is implementation defined."
955+ co_await send (ad_utility::httpUtils::createOkResponse (
956+ " Update successful" , request, MediaType::textPlain));
957+ co_return ;
958+ }
959+
836960// ____________________________________________________________________________
837961template <Server::OperationType type>
838962Awaitable<void > Server::processQueryOrUpdate (
@@ -858,8 +982,8 @@ Awaitable<void> Server::processQueryOrUpdate(
858982 co_await processQuery (params, queryOrUpdate, requestTimer, request, send,
859983 timeLimit);
860984 } else {
861- throw std::runtime_error (
862- " SPARQL 1.1 Update is currently not supported by QLever. " );
985+ co_await processUpdate (params, queryOrUpdate, requestTimer, request, send,
986+ timeLimit );
863987 }
864988 } catch (const ParseException& e) {
865989 responseStatus = http::status::bad_request;
@@ -894,7 +1018,7 @@ Awaitable<void> Server::processQueryOrUpdate(
8941018 exceptionErrorMsg.value ().append (absl::StrCat (
8951019 " Highlighting an error for the command line log failed: " ,
8961020 e.what ()));
897- LOG (ERROR) << " Failed to highlight error in query . " << e.what ()
1021+ LOG (ERROR) << " Failed to highlight error in operation . " << e.what ()
8981022 << std::endl;
8991023 LOG (ERROR) << metadata.value ().query_ << std::endl;
9001024 }
@@ -915,7 +1039,8 @@ Awaitable<void> Server::processQueryOrUpdate(
9151039
9161040// _____________________________________________________________________________
9171041template <std::invocable Function, typename T>
918- Awaitable<T> Server::computeInNewThread (Function function,
1042+ Awaitable<T> Server::computeInNewThread (net::static_thread_pool& threadPool,
1043+ Function function,
9191044 SharedCancellationHandle handle) {
9201045 // `interruptible` will set the shared state of this promise
9211046 // with a function that can be used to cancel the timer.
@@ -935,48 +1060,35 @@ Awaitable<T> Server::computeInNewThread(Function function,
9351060 // this might still block. However it will make the code check the
9361061 // cancellation handle while waiting for a thread in the pool to become ready.
9371062 return ad_utility::interruptible (
938- ad_utility::runFunctionOnExecutor (threadPool_ .get_executor (),
1063+ ad_utility::runFunctionOnExecutor (threadPool .get_executor (),
9391064 std::move (inner), net::use_awaitable),
9401065 std::move (handle), std::move (cancelTimerPromise));
9411066}
9421067
9431068// _____________________________________________________________________________
944- net::awaitable<std::optional< Server::PlannedQuery>> Server::parseAndPlan (
1069+ Server::PlannedQuery Server::parseAndPlan (
9451070 const std::string& query, const vector<DatasetClause>& queryDatasets,
9461071 QueryExecutionContext& qec, SharedCancellationHandle handle,
947- TimeLimit timeLimit) {
948- auto handleCopy = handle;
949-
950- // The usage of an `optional` here is required because of a limitation in
951- // Boost::Asio which forces us to use default-constructible result types with
952- // `computeInNewThread`. We also can't unwrap the optional directly in this
953- // function, because then the conan build fails in a very strange way,
954- // probably related to issues in GCC's coroutine implementation.
955- return computeInNewThread (
956- [&query, &qec, enablePatternTrick = enablePatternTrick_,
957- handle = std::move (handle), timeLimit,
958- &queryDatasets]() mutable -> std::optional<PlannedQuery> {
959- auto pq = SparqlParser::parseQuery (query);
960- handle->throwIfCancelled ();
961- // SPARQL Protocol 2.1.4 specifies that the dataset from the query
962- // parameters overrides the dataset from the query itself.
963- if (!queryDatasets.empty ()) {
964- pq.datasetClauses_ =
965- parsedQuery::DatasetClauses::fromClauses (queryDatasets);
966- }
967- QueryPlanner qp (&qec, handle);
968- qp.setEnablePatternTrick (enablePatternTrick);
969- auto qet = qp.createExecutionTree (pq);
970- handle->throwIfCancelled ();
971- PlannedQuery plannedQuery{std::move (pq), std::move (qet)};
972-
973- plannedQuery.queryExecutionTree_ .getRootOperation ()
974- ->recursivelySetCancellationHandle (std::move (handle));
975- plannedQuery.queryExecutionTree_ .getRootOperation ()
976- ->recursivelySetTimeConstraint (timeLimit);
977- return plannedQuery;
978- },
979- std::move (handleCopy));
1072+ TimeLimit timeLimit) const {
1073+ auto pq = SparqlParser::parseQuery (query);
1074+ handle->throwIfCancelled ();
1075+ // SPARQL Protocol 2.1.4 specifies that the dataset from the query
1076+ // parameters overrides the dataset from the query itself.
1077+ if (!queryDatasets.empty ()) {
1078+ pq.datasetClauses_ =
1079+ parsedQuery::DatasetClauses::fromClauses (queryDatasets);
1080+ }
1081+ QueryPlanner qp (&qec, handle);
1082+ qp.setEnablePatternTrick (enablePatternTrick_);
1083+ auto qet = qp.createExecutionTree (pq);
1084+ handle->throwIfCancelled ();
1085+ PlannedQuery plannedQuery{std::move (pq), std::move (qet)};
1086+
1087+ plannedQuery.queryExecutionTree_ .getRootOperation ()
1088+ ->recursivelySetCancellationHandle (std::move (handle));
1089+ plannedQuery.queryExecutionTree_ .getRootOperation ()
1090+ ->recursivelySetTimeConstraint (timeLimit);
1091+ return plannedQuery;
9801092}
9811093
9821094// _____________________________________________________________________________
0 commit comments