Skip to content
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
16 changes: 14 additions & 2 deletions src/engine/sparqlExpressions/GroupConcatExpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,20 @@ sparqlExpression::GroupConcatExpression::evaluate(
result.reserve(20000);
bool firstIteration = true;
for (auto& inp : generator) {
auto literal = detail::LiteralValueGetterWithoutStrFunction{}(
std::move(inp), context);
// For GROUP_CONCAT, we need to:
// 1. Convert IRIs to string literals (implicit STR() behavior)
// 2. Preserve language tags on literals (for mergeLanguageTags)
//
// LiteralValueGetterWithoutStrFunction preserves language tags but
// returns nullopt for IRIs. LiteralValueGetterWithStrFunction handles
// IRIs but strips language tags. We try WithoutStr first, and only
// fall back to WithStr for IRIs (when WithoutStr returns nullopt).
auto literal =
detail::LiteralValueGetterWithoutStrFunction{}(inp, context);
if (!literal.has_value()) {
// This is an IRI (or other non-literal). Use WithStr to convert it.
literal = detail::LiteralValueGetterWithStrFunction{}(inp, context);
}
if (firstIteration) {
firstIteration = false;
detail::pushLanguageTag(langTag, literal);
Expand Down
44 changes: 44 additions & 0 deletions test/engine/GroupConcatExpressionTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,50 @@ TEST(GroupConcatExpression, concatenationWithLanguageTags) {
lit("\"a;a\""));
}

// _____________________________________________________________________________
// Test that IRIs are converted to strings (like implicit STR() application).
// This tests vocabulary IRIs, local vocab IRIs, and literals mixed together.
TEST(GroupConcatExpression, concatenationWithIris) {
auto* qec = ad_utility::testing::getQec();
auto getId = ad_utility::testing::makeGetId(qec->getIndex());

auto iri = [](std::string_view s) {
return tc::LiteralOrIri{tc::Iri::fromIriref(s)};
};

LocalVocab localVocab;
IdTable input{1, ad_utility::makeUnlimitedAllocator<Id>()};

// 1. Add a vocabulary IRI.
Id xId = getId("<x>");
input.push_back({xId});
expectIdsAreConcatenatedTo(false, input,
IdOrLiteralOrIri{LocalVocabEntry{lit("\"x\"")}});

// 2. Add a local vocab IRI.
auto localIriIdx = localVocab.getIndexAndAddIfNotContained(
LocalVocabEntry{iri("<http://example.org/foo>")});
input.push_back({Id::makeFromLocalVocabIndex(localIriIdx)});
expectIdsAreConcatenatedTo(
false, input,
IdOrLiteralOrIri{LocalVocabEntry{lit("\"x;http://example.org/foo\"")}});

// 3. Add a local vocab literal.
auto literalIdx =
localVocab.getIndexAndAddIfNotContained(LocalVocabEntry{lit("\"bar\"")});
input.push_back({Id::makeFromLocalVocabIndex(literalIdx)});
expectIdsAreConcatenatedTo(false, input,
IdOrLiteralOrIri{LocalVocabEntry{
lit("\"x;http://example.org/foo;bar\"")}});

// 4. Add another vocabulary IRI.
Id labelId = getId("<label>");
input.push_back({labelId});
expectIdsAreConcatenatedTo(false, input,
IdOrLiteralOrIri{LocalVocabEntry{lit(
"\"x;http://example.org/foo;bar;label\"")}});
}

// _____________________________________________________________________________
TEST(GroupConcatExpression, getCacheKey) {
Variable var{"?x"};
Expand Down
Loading