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

Prepare for supporting chained updates #1841

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 5 additions & 1 deletion src/parser/SparqlParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ ParsedQuery SparqlParser::parseQuery(std::string query) {
// input. If this is not the case a ParseException should have been thrown at
// an earlier point.
AD_CONTRACT_CHECK(resultOfParseAndRemainingText.remainingText_.empty());
return std::move(resultOfParseAndRemainingText.resultOfParse_);
if (resultOfParseAndRemainingText.resultOfParse_.size() != 1) {
throw std::runtime_error(
"Multiple Updates in one request are not supported.");
}
return std::move(resultOfParseAndRemainingText.resultOfParse_[0]);
}

// _____________________________________________________________________________
Expand Down
40 changes: 27 additions & 13 deletions src/parser/sparqlParser/SparqlQleverVisitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ ParsedQuery Visitor::visit(Parser::QueryContext* ctx) {
}

// ____________________________________________________________________________________
ParsedQuery Visitor::visit(Parser::QueryOrUpdateContext* ctx) {
std::vector<ParsedQuery> Visitor::visit(Parser::QueryOrUpdateContext* ctx) {
if (ctx->update() && !ctx->update()->update1()) {
// An empty query currently matches the `update()` rule. We handle this
// case manually to get a better error message. If an update query doesn't
Expand All @@ -242,7 +242,8 @@ ParsedQuery Visitor::visit(Parser::QueryOrUpdateContext* ctx) {
"Empty query (this includes queries that only consist "
"of comments or prefix declarations).");
}
return visitAlternative<ParsedQuery>(ctx->query(), ctx->update());
return visitAlternative<std::vector<ParsedQuery>>(ctx->query(),
ctx->update());
}

// ____________________________________________________________________________________
Expand Down Expand Up @@ -506,23 +507,36 @@ std::optional<Values> Visitor::visit(Parser::ValuesClauseContext* ctx) {
}

// ____________________________________________________________________________
ParsedQuery Visitor::visit(Parser::UpdateContext* ctx) {
std::vector<ParsedQuery> Visitor::visit(Parser::UpdateContext* ctx) {
// The prologue (BASE and PREFIX declarations) only affects the internal
// state of the visitor.
visit(ctx->prologue());
if (!ctx->update1()) {
return {};
}

auto update = visit(ctx->update1());
auto thisUpdate = visit(ctx->update1());
Comment on lines +516 to +518
Copy link
Member

Choose a reason for hiding this comment

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

How easy is it to update this rule also to something non-recursive, s.t. the following code becomes a simple loop?
If feasible, please do so (for readability rather than efficiency, but still)

Copy link
Member Author

Choose a reason for hiding this comment

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

The rule only gets a bit longer, but then we'd need something like a reset() function for our state.

// The string representation of the Update is from the beginning of that
// updates prologue to the end of the update. The `;` between queries is
// ignored in the string representation.
const size_t updateStartPos = ctx->prologue()->getStart()->getStartIndex();
const size_t updateEndPos = ctx->update1()->getStop()->getStopIndex();
thisUpdate._originalString = std::string{ad_utility::getUTF8Substring(
ctx->getStart()->getInputStream()->toString(), updateStartPos,
updateEndPos - updateStartPos + 1)};
std::vector updates{std::move(thisUpdate)};

// More than one operation in a single update request is not yet supported,
// but a semicolon after a single update is allowed.
if (ctx->update() && !ctx->update()->getText().empty()) {
parsedQuery_ = ParsedQuery{};
reportNotSupported(ctx->update(), "Multiple updates in one query are");
if (ctx->update()) {
// This is a new operation, reset the state of the Visitor.
activeDatasetClauses_ = {};
prefixMap_ = initialPrefixMap_;
baseIri_ = {};
prologueString_ = {};
parsedQuery_ = {};
ad_utility::appendVector(updates, visit(ctx->update()));
}

update._originalString = ctx->getStart()->getInputStream()->toString();

return update;
return updates;
}

// ____________________________________________________________________________________
Expand All @@ -535,7 +549,7 @@ ParsedQuery Visitor::visit(Parser::Update1Context* ctx) {
ctx->move(), ctx->copy(), ctx->insertData(), ctx->deleteData());
}

return parsedQuery_;
return std::move(parsedQuery_);
}

// ____________________________________________________________________________________
Expand Down
11 changes: 8 additions & 3 deletions src/parser/sparqlParser/SparqlQleverVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ class SparqlQleverVisitor {
ParsedQuery::DatasetClauses activeDatasetClauses_;

// The map from prefixes to their full IRIs.
// The initial prefixes the visitor was initialised with. Required for chained
// updates.
PrefixMap initialPrefixMap_{};
// Changed during the parsing.
PrefixMap prefixMap_{};

// The `BASE` IRI of the query if any.
Expand Down Expand Up @@ -113,7 +117,8 @@ class SparqlQleverVisitor {
PrefixMap prefixMap,
DisableSomeChecksOnlyForTesting disableSomeChecksOnlyForTesting =
DisableSomeChecksOnlyForTesting::False)
: prefixMap_{std::move(prefixMap)},
: initialPrefixMap_{prefixMap},
prefixMap_{std::move(prefixMap)},
disableSomeChecksOnlyForTesting_{disableSomeChecksOnlyForTesting} {}

const PrefixMap& prefixMap() const { return prefixMap_; }
Expand All @@ -129,7 +134,7 @@ class SparqlQleverVisitor {
}

// ___________________________________________________________________________
ParsedQuery visit(Parser::QueryOrUpdateContext* ctx);
std::vector<ParsedQuery> visit(Parser::QueryOrUpdateContext* ctx);

// ___________________________________________________________________________
ParsedQuery visit(Parser::QueryContext* ctx);
Expand Down Expand Up @@ -201,7 +206,7 @@ class SparqlQleverVisitor {

std::optional<parsedQuery::Values> visit(Parser::ValuesClauseContext* ctx);

ParsedQuery visit(Parser::UpdateContext* ctx);
std::vector<ParsedQuery> visit(Parser::UpdateContext* ctx);

ParsedQuery visit(Parser::Update1Context* ctx);

Expand Down
85 changes: 58 additions & 27 deletions test/SparqlAntlrParserTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2187,7 +2187,9 @@ TEST(SparqlParser, Update) {
const std::string& query, auto&& expected,
ad_utility::source_location l =
ad_utility::source_location::current()) {
expectUpdate_(query, testing::AllOf(expected, m::pq::OriginalString(query)),
expectUpdate_(query,
testing::ElementsAre(
testing::AllOf(expected, m::pq::OriginalString(query))),
l);
};
auto expectUpdateFails = ExpectParseFails<&Parser::update>{};
Expand Down Expand Up @@ -2291,10 +2293,6 @@ TEST(SparqlParser, Update) {
m::GraphUpdate({{Var("?a"), Iri("<b>"), Iri("<c>"), noGraph}}, {},
Iri("<foo>")),
m::GraphPattern(m::Triples({{Iri("<d>"), "<e>", Var{"?a"}}}))));
// Chaining multiple updates into one query is not supported.
expectUpdateFails(
"INSERT DATA { <a> <b> <c> } ; INSERT { ?a <b> <c> } WHERE { <d> <e> ?a "
"}");
expectUpdate("LOAD <foo>",
m::UpdateClause(m::Load(false, Iri("<foo>"), std::nullopt),
m::GraphPattern()));
Expand Down Expand Up @@ -2324,17 +2322,47 @@ TEST(SparqlParser, Update) {
m::GraphUpdate({}, {{Iri("<a>"), Iri("<b>"), Iri("<c>"), noGraph}},
std::nullopt),
m::GraphPattern());
const auto deleteWhereAllMatcher = m::UpdateClause(
m::GraphUpdate({{Var("?s"), Var("?p"), Var("?o"), noGraph}}, {},
std::nullopt),
m::GraphPattern(m::Triples({{Var("?s"), "?p", Var("?o")}})));
expectUpdate("INSERT DATA { <a> <b> <c> }", simpleInsertMatcher);
expectUpdate("INSERT DATA { <a> <b> <c> };", simpleInsertMatcher);
const auto multipleUpdatesError = testing::HasSubstr(
"Not supported: Multiple updates in one query are "
"currently not supported by QLever");
expectUpdateFails("INSERT DATA { <a> <b> <c> }; PREFIX foo: <foo>",
multipleUpdatesError);
expectUpdateFails("INSERT DATA { <a> <b> <c> }; BASE <bar>",
multipleUpdatesError);
expectUpdateFails("INSERT DATA { <a> <b> <c> }; INSERT DATA { <d> <e> <f> }",
multipleUpdatesError);
// Multiple Updates
expectUpdate_(
"INSERT DATA { <a> <b> <c> };",
ElementsAre(AllOf(simpleInsertMatcher,
m::pq::OriginalString("INSERT DATA { <a> <b> <c> }"))));
expectUpdate_(
"INSERT DATA { <a> <b> <c> }; BASE <https://example.org> PREFIX foo: "
"<foo>",
ElementsAre(AllOf(simpleInsertMatcher,
m::pq::OriginalString("INSERT DATA { <a> <b> <c> }"))));
expectUpdate_(
"INSERT DATA { <a> <b> <c> }; DELETE WHERE { ?s ?p ?o }",
ElementsAre(AllOf(simpleInsertMatcher,
m::pq::OriginalString("INSERT DATA { <a> <b> <c> }")),
AllOf(deleteWhereAllMatcher,
m::pq::OriginalString("DELETE WHERE { ?s ?p ?o }"))));
expectUpdateFails(
"PREFIX foo: <foo> INSERT DATA { <a> <b> <c> }; INSERT DATA { foo:a "
"foo:b foo:c }",
testing::HasSubstr("Invalid SPARQL query: Prefix foo was not registered "
"using a PREFIX declaration in \"foo:a\""));
expectUpdate_(
"PREFIX foo: <foo> INSERT DATA { <a> <b> <c> }; PREFIX foo: <bar/> "
"INSERT "
"DATA { foo:a foo:b foo:c }",
ElementsAre(
AllOf(simpleInsertMatcher,
m::pq::OriginalString(
"PREFIX foo: <foo> INSERT DATA { <a> <b> <c> }")),
AllOf(m::UpdateClause(m::GraphUpdate({},
{{Iri("<bar/a>"), Iri("<bar/b>"),
Iri("<bar/c>"), noGraph}},
std::nullopt),
m::GraphPattern()),
m::pq::OriginalString(
"PREFIX foo: <bar/> INSERT DATA { foo:a foo:b foo:c }"))));
}

TEST(SparqlParser, QueryOrUpdate) {
Expand All @@ -2352,20 +2380,23 @@ TEST(SparqlParser, QueryOrUpdate) {
expectQueryFails("PREFIX ex: <http://example.org>", emptyMatcher);
expectQueryFails("### Some comment \n \n #someMoreComments", emptyMatcher);
// Hit all paths for coverage.
expectQuery("SELECT ?a WHERE { ?a <is-a> <b> }",
AllOf(m::SelectQuery(m::Select({Var{"?a"}}),
m::GraphPattern(m::Triples(
{{Var{"?a"}, "<is-a>", Iri("<b>")}}))),
m::pq::OriginalString("SELECT ?a WHERE { ?a <is-a> <b> }"),
m::VisibleVariables({Var{"?a"}})));
expectQuery(
"SELECT ?a WHERE { ?a <is-a> <b> }",
ElementsAre(AllOf(
m::SelectQuery(
m::Select({Var{"?a"}}),
m::GraphPattern(m::Triples({{Var{"?a"}, "<is-a>", Iri("<b>")}}))),
m::pq::OriginalString("SELECT ?a WHERE { ?a <is-a> <b> }"),
m::VisibleVariables({Var{"?a"}}))));
expectQuery(
"INSERT DATA { <a> <b> <c> }",
AllOf(m::UpdateClause(m::GraphUpdate({},
{{Iri("<a>"), Iri("<b>"), Iri("<c>"),
std::monostate{}}},
std::nullopt),
m::GraphPattern()),
m::pq::OriginalString("INSERT DATA { <a> <b> <c> }")));
ElementsAre(AllOf(
m::UpdateClause(
m::GraphUpdate(
{}, {{Iri("<a>"), Iri("<b>"), Iri("<c>"), std::monostate{}}},
std::nullopt),
m::GraphPattern()),
m::pq::OriginalString("INSERT DATA { <a> <b> <c> }"))));
}

TEST(SparqlParser, GraphOrDefault) {
Expand Down
7 changes: 7 additions & 0 deletions test/SparqlParserTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1407,3 +1407,10 @@ TEST(ParserTest, parseWithDatasets) {
m::SelectQuery(m::AsteriskSelect(), queryGraphPatternMatcher,
{{Iri("<bar>"), Iri("<baz>")}}, {{Iri("<foo>")}}));
}

TEST(ParserTest, multipleUpdatesAreForbidden) {
AD_EXPECT_THROW_WITH_MESSAGE(
SparqlParser::parseQuery(
"INSERT DATA { <a> <b> <c> }; DELETE DATA { <d> <e> <f> }"),
testing::HasSubstr("Multiple Updates in one request are not supported."));
}
Loading