Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a function that executes an UPDATE request #1607

Merged
merged 3 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions src/engine/ExecuteUpdate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@

#include "engine/ExportQueryExecutionTrees.h"

// _____________________________________________________________________________
void ExecuteUpdate::executeUpdate(
const Index& index, const ParsedQuery& query, const QueryExecutionTree& qet,
DeltaTriples& deltaTriples, const CancellationHandle& cancellationHandle) {
auto [toInsert, toDelete] =
std::move(computeGraphUpdateQuads(index, query, qet, cancellationHandle));

// "The deletion of the triples happens before the insertion." (SPARQL 1.1
// Update 3.1.3)
deltaTriples.deleteTriples(cancellationHandle, std::move(toDelete.idTriples));
deltaTriples.insertTriples(cancellationHandle, std::move(toInsert.idTriples));
}

// _____________________________________________________________________________
std::pair<std::vector<ExecuteUpdate::TransformedTriple>, LocalVocab>
ExecuteUpdate::transformTriplesTemplate(
Expand Down Expand Up @@ -99,3 +112,61 @@ void ExecuteUpdate::computeAndAddQuadsForResultRow(
result.emplace_back(std::array{*subject, *predicate, *object, *graph});
}
}

// _____________________________________________________________________________
std::pair<ExecuteUpdate::IdTriplesAndLocalVocab,
ExecuteUpdate::IdTriplesAndLocalVocab>
ExecuteUpdate::computeGraphUpdateQuads(
const Index& index, const ParsedQuery& query, const QueryExecutionTree& qet,
const CancellationHandle& cancellationHandle) {
AD_CONTRACT_CHECK(query.hasUpdateClause());
auto updateClause = query.updateClause();
if (!std::holds_alternative<updateClause::GraphUpdate>(updateClause.op_)) {
throw std::runtime_error(
"Only INSERT/DELETE update operations are currently supported.");
}
auto graphUpdate = std::get<updateClause::GraphUpdate>(updateClause.op_);
// Fully materialize the result for now. This makes it easier to execute the
// update.
auto res = qet.getResult(false);

auto& vocab = index.getVocab();
Qup42 marked this conversation as resolved.
Show resolved Hide resolved

auto prepareTemplateAndResultContainer =
[&vocab, &qet,
&res](std::vector<SparqlTripleSimpleWithGraph>&& tripleTemplates) {
auto [transformedTripleTemplates, localVocab] =
transformTriplesTemplate(vocab, qet.getVariableColumns(),
std::move(tripleTemplates));
std::vector<IdTriple<>> updateTriples;
// The maximum result size is size(query result) x num template rows.
// The actual result can be smaller if there are template rows with
// variables for which a result row does not have a value.
updateTriples.reserve(res->idTable().size() *
transformedTripleTemplates.size());

return std::tuple{std::move(transformedTripleTemplates),
std::move(updateTriples), std::move(localVocab)};
};

auto [toInsertTemplates, toInsert, localVocabInsert] =
prepareTemplateAndResultContainer(std::move(graphUpdate.toInsert_));
auto [toDeleteTemplates, toDelete, localVocabDelete] =
prepareTemplateAndResultContainer(std::move(graphUpdate.toDelete_));

for (const auto& [pair, range] :
ExportQueryExecutionTrees::getRowIndices(query._limitOffset, *res)) {
auto& idTable = pair.idTable_;
for (const uint64_t i : range) {
computeAndAddQuadsForResultRow(toInsertTemplates, toInsert, idTable, i);
cancellationHandle->throwIfCancelled();

computeAndAddQuadsForResultRow(toDeleteTemplates, toDelete, idTable, i);
cancellationHandle->throwIfCancelled();
}
}

return {
IdTriplesAndLocalVocab{std::move(toInsert), std::move(localVocabInsert)},
IdTriplesAndLocalVocab{std::move(toDelete), std::move(localVocabDelete)}};
}
19 changes: 19 additions & 0 deletions src/engine/ExecuteUpdate.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ class ExecuteUpdate {
using IdOrVariableIndex = std::variant<Id, ColumnIndex>;
using TransformedTriple = std::array<IdOrVariableIndex, 4>;

// Execute an update. This function is comparable to
// `ExportQueryExecutionTrees::computeResult` for queries.
static void executeUpdate(const Index& index, const ParsedQuery& query,
const QueryExecutionTree& qet,
DeltaTriples& deltaTriples,
const CancellationHandle& cancellationHandle);

private:
// Resolve all `TripleComponent`s and `Graph`s in a vector of
// `SparqlTripleSimpleWithGraph` into `Variable`s or `Id`s.
Expand All @@ -41,4 +48,16 @@ class ExecuteUpdate {
const std::vector<TransformedTriple>& templates,
std::vector<IdTriple<>>& result, const IdTable& idTable, uint64_t rowIdx);
FRIEND_TEST(ExecuteUpdate, computeAndAddQuadsForResultRow);

struct IdTriplesAndLocalVocab {
std::vector<IdTriple<>> idTriples;
LocalVocab localVocab;
};
// Compute the set of quads to insert and delete for the given update. The
// update's operation must be a GraphUpdate.
Qup42 marked this conversation as resolved.
Show resolved Hide resolved
static std::pair<IdTriplesAndLocalVocab, IdTriplesAndLocalVocab>
computeGraphUpdateQuads(const Index& index, const ParsedQuery& query,
const QueryExecutionTree& qet,
const CancellationHandle& cancellationHandle);
FRIEND_TEST(ExecuteUpdate, computeGraphUpdateQuads);
};
159 changes: 159 additions & 0 deletions test/ExecuteUpdateTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,165 @@ MATCHER_P(AlwaysFalse, msg, "") {
return false;
}

// _____________________________________________________________________________
TEST(ExecuteUpdate, executeUpdate) {
auto executeUpdate = [](const std::string& update) {
// These tests run on the default dataset defined in
// `IndexTestHelpers::makeTestIndex`.
QueryExecutionContext* qec = ad_utility::testing::getQec(std::nullopt);
const Index& index = qec->getIndex();
DeltaTriples deltaTriples{index};
const auto sharedHandle =
std::make_shared<ad_utility::CancellationHandle<>>();
const std::vector<DatasetClause> datasets = {};
auto pq = SparqlParser::parseQuery(update);
QueryPlanner qp{qec, sharedHandle};
const auto qet = qp.createExecutionTree(pq);
ExecuteUpdate::executeUpdate(index, pq, qet, deltaTriples, sharedHandle);
return deltaTriples;
};
auto expectExecuteUpdate =
[&executeUpdate](
const std::string& update,
const testing::Matcher<const DeltaTriples&>& deltaTriplesMatcher) {
EXPECT_THAT(executeUpdate(update), deltaTriplesMatcher);
};
Comment on lines +52 to +57
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could just return a matcher (return ResultOf(executeUpdate, deltaTriplesMatcher);),
The you have the EXPECT_THAT with the single calls, and the failure messages have the correct line numbers.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(not super important though)

auto expectExecuteUpdateFails =
[&executeUpdate](
const std::string& update,
const testing::Matcher<const std::string&>& messageMatcher) {
AD_EXPECT_THROW_WITH_MESSAGE(executeUpdate(update), messageMatcher);
};
expectExecuteUpdate("INSERT DATA { <s> <p> <o> . }", NumTriples(1, 0, 1));
expectExecuteUpdate("DELETE DATA { <z> <label> \"zz\"@en }",
NumTriples(0, 1, 1));
expectExecuteUpdate(
"DELETE { ?s <is-a> ?o } INSERT { <a> <b> <c> } WHERE { ?s <is-a> ?o }",
NumTriples(1, 2, 3));
expectExecuteUpdate(
"DELETE { <a> <b> <c> } INSERT { <a> <b> <c> } WHERE { ?s <is-a> ?o }",
NumTriples(1, 0, 1));
expectExecuteUpdate(
"DELETE { ?s <is-a> ?o } INSERT { ?s <is-a> ?o } WHERE { ?s <is-a> ?o }",
NumTriples(2, 0, 2));
expectExecuteUpdate("DELETE WHERE { ?s ?p ?o }", NumTriples(0, 8, 8));
expectExecuteUpdateFails(
"SELECT * WHERE { ?s ?p ?o }",
testing::HasSubstr("Assertion `query.hasUpdateClause()` failed."));
expectExecuteUpdateFails(
"CLEAR DEFAULT",
testing::HasSubstr(
"Only INSERT/DELETE update operations are currently supported."));
}

// _____________________________________________________________________________
TEST(ExecuteUpdate, computeGraphUpdateQuads) {
// These tests run on the default dataset defined in
// `IndexTestHelpers::makeTestIndex`.
QueryExecutionContext* qec = ad_utility::testing::getQec(std::nullopt);
const Index& index = qec->getIndex();
const auto Id = ad_utility::testing::makeGetId(index);
auto defaultGraphId = Id(std::string{DEFAULT_GRAPH_IRI});

ad_utility::HashMap<std::string, std::unique_ptr<LocalVocabEntry>>
localVocabEntries;
auto LVI = [&localVocabEntries](const std::string& iri) {
if (!localVocabEntries.contains(iri)) {
localVocabEntries.try_emplace(
iri, std::make_unique<LocalVocabEntry>(
ad_utility::triple_component::Iri::fromIriref(iri)));
}
return Id::makeFromLocalVocabIndex(localVocabEntries.at(iri).get());
};
Qup42 marked this conversation as resolved.
Show resolved Hide resolved

auto IdTriple = [defaultGraphId](const ::Id s, const ::Id p, const ::Id o,
const std::optional<::Id> graph =
std::nullopt) -> ::IdTriple<> {
return ::IdTriple({s, p, o, graph.value_or(defaultGraphId)});
};

auto executeComputeGraphUpdateQuads = [&qec,
&index](const std::string& update) {
const auto sharedHandle =
std::make_shared<ad_utility::CancellationHandle<>>();
const std::vector<DatasetClause> datasets = {};
auto pq = SparqlParser::parseQuery(update);
QueryPlanner qp{qec, sharedHandle};
const auto qet = qp.createExecutionTree(pq);
return ExecuteUpdate::computeGraphUpdateQuads(index, pq, qet, sharedHandle);
};
auto expectComputeGraphUpdateQuads =
[&executeComputeGraphUpdateQuads](
const std::string& update,
const testing::Matcher<const std::vector<::IdTriple<>>&>&
toInsertMatcher,
const testing::Matcher<const std::vector<::IdTriple<>>&>&
toDeleteMatcher) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment above, you can also add the source_location to the matching functions and use generateLocationTrace to get better messages on failure.

EXPECT_THAT(
executeComputeGraphUpdateQuads(update),
testing::Pair(AD_FIELD(ExecuteUpdate::IdTriplesAndLocalVocab,
idTriples, toInsertMatcher),
AD_FIELD(ExecuteUpdate::IdTriplesAndLocalVocab,
idTriples, toDeleteMatcher)));
};
auto expectComputeGraphUpdateQuadsFails =
[&executeComputeGraphUpdateQuads](
const std::string& update,
const testing::Matcher<const std::string&>& messageMatcher) {
AD_EXPECT_THROW_WITH_MESSAGE(executeComputeGraphUpdateQuads(update),
messageMatcher);
};

expectComputeGraphUpdateQuads(
"INSERT DATA { <s> <p> <o> . }",
testing::ElementsAreArray({IdTriple(LVI("<s>"), LVI("<p>"), LVI("<o>"))}),
Qup42 marked this conversation as resolved.
Show resolved Hide resolved
testing::IsEmpty());
expectComputeGraphUpdateQuads(
"DELETE DATA { <z> <label> \"zz\"@en }", testing::IsEmpty(),
testing::ElementsAreArray(
{IdTriple(Id("<z>"), Id("<label>"), Id("\"zz\"@en"))}));
expectComputeGraphUpdateQuads(
"DELETE { ?s <is-a> ?o } INSERT { <s> <p> <o> } WHERE { ?s <is-a> ?o }",
testing::ElementsAreArray({IdTriple(LVI("<s>"), LVI("<p>"), LVI("<o>")),
// TODO: what to do with this duplicate entry?
IdTriple(LVI("<s>"), LVI("<p>"), LVI("<o>"))}),
Qup42 marked this conversation as resolved.
Show resolved Hide resolved
testing::ElementsAreArray(
{IdTriple(Id("<x>"), Id("<is-a>"), Id("<y>")),
IdTriple(Id("<y>"), Id("<is-a>"), Id("<x>"))}));
expectComputeGraphUpdateQuads(
"DELETE { <s> <p> <o> } INSERT { <s> <p> <o> } WHERE { ?s <is-a> ?o }",
testing::ElementsAreArray({IdTriple(LVI("<s>"), LVI("<p>"), LVI("<o>")),
IdTriple(LVI("<s>"), LVI("<p>"), LVI("<o>"))}),
testing::ElementsAreArray(
{IdTriple(LVI("<s>"), LVI("<p>"), LVI("<o>")),
IdTriple(LVI("<s>"), LVI("<p>"), LVI("<o>"))}));
expectComputeGraphUpdateQuads(
"DELETE { ?s <is-a> ?o } INSERT { ?s <is-a> ?o } WHERE { ?s <is-a> ?o }",
testing::ElementsAreArray({IdTriple(Id("<x>"), Id("<is-a>"), Id("<y>")),
IdTriple(Id("<y>"), Id("<is-a>"), Id("<x>"))}),
testing::ElementsAreArray(
{IdTriple(Id("<x>"), Id("<is-a>"), Id("<y>")),
IdTriple(Id("<y>"), Id("<is-a>"), Id("<x>"))}));
expectComputeGraphUpdateQuads(
"DELETE WHERE { ?s ?p ?o }", testing::IsEmpty(),
testing::UnorderedElementsAreArray(
{IdTriple(Id("<x>"), Id("<label>"), Id("\"alpha\"")),
IdTriple(Id("<x>"), Id("<label>"), Id("\"älpha\"")),
IdTriple(Id("<x>"), Id("<label>"), Id("\"A\"")),
IdTriple(Id("<x>"), Id("<label>"), Id("\"Beta\"")),
IdTriple(Id("<x>"), Id("<is-a>"), Id("<y>")),
IdTriple(Id("<y>"), Id("<is-a>"), Id("<x>")),
IdTriple(Id("<z>"), Id("<label>"), Id("\"zz\"@en")),
IdTriple(Id("<zz>"), Id("<label>"), Id("<zz>"))}));
expectComputeGraphUpdateQuadsFails(
"SELECT * WHERE { ?s ?p ?o }",
testing::HasSubstr("Assertion `query.hasUpdateClause()` failed."));
expectComputeGraphUpdateQuadsFails(
"CLEAR DEFAULT",
testing::HasSubstr(
"Only INSERT/DELETE update operations are currently supported."));
}

// _____________________________________________________________________________
TEST(ExecuteUpdate, transformTriplesTemplate) {
// Create an index for testing.
Expand Down
Loading