Skip to content

Commit

Permalink
Implement cast expressions correctly (#1751)
Browse files Browse the repository at this point in the history
Add the cast expressions `xsd:boolean(?something)` and `xsd:string(?something)` . 
Also add `xsd:float` which just does the same as `xsd:double` as QLever currently doesn't distinguish between these operations.
Also support exponential notation like "1E04" for the `xsd:double` cast.
  • Loading branch information
RobinTF authored Feb 4, 2025
1 parent db6825c commit be240ab
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 10 deletions.
49 changes: 47 additions & 2 deletions src/engine/sparqlExpressions/ConvertToNumericExpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ namespace detail::to_numeric {

// class that converts an input `int64_t`, `double` or `std::string`
// to a numeric value `int64_t` or `double`
template <typename T>
template <typename T, bool AllowExponentialNotation = true>
requires std::same_as<int64_t, T> || std::same_as<double, T>
class ToNumericImpl {
private:
Id getFromString(const std::string& input) const {
auto str = absl::StripAsciiWhitespace(input);
// Abseil and the standard library don't match leading + signs, so we skip
// them.
if (str.starts_with('+')) {
str.remove_prefix(1);
}
auto strEnd = str.data() + str.size();
auto strStart = str.data();
T resT{};
Expand All @@ -24,7 +29,10 @@ class ToNumericImpl {
return Id::makeFromInt(resT);
}
} else {
auto conv = absl::from_chars(strStart, strEnd, resT);
auto conv = absl::from_chars(strStart, strEnd, resT,
AllowExponentialNotation
? absl::chars_format::general
: absl::chars_format::fixed);
if (conv.ec == std::error_code{} && conv.ptr == strEnd) {
return Id::makeFromDouble(resT);
}
Expand Down Expand Up @@ -61,9 +69,38 @@ class ToNumericImpl {

using ToInteger = NARY<1, FV<ToNumericImpl<int64_t>, ToNumericValueGetter>>;
using ToDouble = NARY<1, FV<ToNumericImpl<double>, ToNumericValueGetter>>;
using ToDecimal =
NARY<1, FV<ToNumericImpl<double, false>, ToNumericValueGetter>>;
} // namespace detail::to_numeric

namespace detail::to_boolean {
class ToBooleanImpl {
public:
Id operator()(IntDoubleStr value) const {
if (std::holds_alternative<std::string>(value)) {
const std::string& str = std::get<std::string>(value);
if (str == "true" || str == "1") {
return Id::makeFromBool(true);
}
if (str == "false" || str == "0") {
return Id::makeFromBool(false);
}
return Id::makeUndefined();
} else if (std::holds_alternative<int64_t>(value)) {
return Id::makeFromBool(std::get<int64_t>(value) != 0);
} else if (std::holds_alternative<double>(value)) {
return Id::makeFromBool(std::get<double>(value) != 0);
} else {
AD_CORRECTNESS_CHECK(std::holds_alternative<std::monostate>(value));
return Id::makeUndefined();
}
}
};
using ToBoolean = NARY<1, FV<ToBooleanImpl, ToNumericValueGetter>>;
} // namespace detail::to_boolean

using namespace detail::to_numeric;
using namespace detail::to_boolean;
using Expr = SparqlExpression::Ptr;

Expr makeConvertToIntExpression(Expr child) {
Expand All @@ -73,4 +110,12 @@ Expr makeConvertToIntExpression(Expr child) {
Expr makeConvertToDoubleExpression(Expr child) {
return std::make_unique<ToDouble>(std::move(child));
}

Expr makeConvertToDecimalExpression(Expr child) {
return std::make_unique<ToDecimal>(std::move(child));
}

Expr makeConvertToBooleanExpression(Expr child) {
return std::make_unique<ToBoolean>(std::move(child));
}
} // namespace sparqlExpression
7 changes: 7 additions & 0 deletions src/engine/sparqlExpressions/NaryExpression.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ SparqlExpression::Ptr makeSHA256Expression(SparqlExpression::Ptr child);
SparqlExpression::Ptr makeSHA384Expression(SparqlExpression::Ptr child);
SparqlExpression::Ptr makeSHA512Expression(SparqlExpression::Ptr child);

SparqlExpression::Ptr makeConvertToStringExpression(
SparqlExpression::Ptr child);

SparqlExpression::Ptr makeIfExpression(SparqlExpression::Ptr child1,
SparqlExpression::Ptr child2,
SparqlExpression::Ptr child3);
Expand All @@ -104,6 +107,10 @@ SparqlExpression::Ptr makeIfExpression(SparqlExpression::Ptr child1,
SparqlExpression::Ptr makeConvertToIntExpression(SparqlExpression::Ptr child);
SparqlExpression::Ptr makeConvertToDoubleExpression(
SparqlExpression::Ptr child);
SparqlExpression::Ptr makeConvertToDecimalExpression(
SparqlExpression::Ptr child);
SparqlExpression::Ptr makeConvertToBooleanExpression(
SparqlExpression::Ptr child);

// Implemented in RdfTermExpressions.cpp
SparqlExpression::Ptr makeDatatypeExpression(SparqlExpression::Ptr child);
Expand Down
4 changes: 4 additions & 0 deletions src/engine/sparqlExpressions/StringExpressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -619,4 +619,8 @@ Expr makeSHA384Expression(Expr child) { return make<SHA384Expression>(child); }

Expr makeSHA512Expression(Expr child) { return make<SHA512Expression>(child); }

Expr makeConvertToStringExpression(Expr child) {
return std::make_unique<StrExpressionImpl>(std::move(child));
}

} // namespace sparqlExpression
19 changes: 18 additions & 1 deletion src/parser/sparqlParser/SparqlQleverVisitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,28 @@ ExpressionPtr Visitor::processIriFunctionCall(
checkNumArgs(1);
return sparqlExpression::makeConvertToIntExpression(
std::move(argList[0]));
} else if (functionName == "double" || functionName == "decimal") {
}
if (functionName == "decimal") {
checkNumArgs(1);
return sparqlExpression::makeConvertToDecimalExpression(
std::move(argList[0]));
}
// We currently don't have a float type, so we just convert to double.
if (functionName == "double" || functionName == "float") {
checkNumArgs(1);
return sparqlExpression::makeConvertToDoubleExpression(
std::move(argList[0]));
}
if (functionName == "boolean") {
checkNumArgs(1);
return sparqlExpression::makeConvertToBooleanExpression(
std::move(argList[0]));
}
if (functionName == "string") {
checkNumArgs(1);
return sparqlExpression::makeConvertToStringExpression(
std::move(argList[0]));
}
}

// QLever-internal functions.
Expand Down
9 changes: 8 additions & 1 deletion test/SparqlAntlrParserTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1798,8 +1798,15 @@ TEST(SparqlParser, FunctionCall) {
matchUnary(&makeConvertToIntExpression));
expectFunctionCall(absl::StrCat(xsd, "double>(?x)"),
matchUnary(&makeConvertToDoubleExpression));
expectFunctionCall(absl::StrCat(xsd, "decimal>(?x)"),
expectFunctionCall(absl::StrCat(xsd, "float>(?x)"),
matchUnary(&makeConvertToDoubleExpression));
expectFunctionCall(absl::StrCat(xsd, "decimal>(?x)"),
matchUnary(&makeConvertToDecimalExpression));
expectFunctionCall(absl::StrCat(xsd, "boolean>(?x)"),
matchUnary(&makeConvertToBooleanExpression));

expectFunctionCall(absl::StrCat(xsd, "string>(?x)"),
matchUnary(&makeConvertToStringExpression));

// Wrong number of arguments.
expectFunctionCallFails(absl::StrCat(geof, "distance>(?a)"));
Expand Down
58 changes: 52 additions & 6 deletions test/SparqlExpressionTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1090,31 +1090,77 @@ TEST(SparqlExpression, testToNumericExpression) {
Id G = Id::makeFromGeoPoint(GeoPoint(50.0, 50.0));
auto checkGetInt = testUnaryExpression<&makeConvertToIntExpression>;
auto checkGetDouble = testUnaryExpression<&makeConvertToDoubleExpression>;
auto chekGetDecimal = testUnaryExpression<&makeConvertToDecimalExpression>;

checkGetInt(idOrLitOrStringVec({U, " -1275", "5.97", "-78.97", "-5BoB6",
"FreBurg1", "", " .", " 42\n", " 0.01 ", "",
"@", "@?+1", "1", G}),
Ids{U, I(-1275), U, U, U, U, U, U, I(42), U, U, U, U, I(1), U});
checkGetInt(
idOrLitOrStringVec({U, " -1275", "5.97", "-78.97", "-5BoB6", "FreBurg1",
"", " .", " 42\n", " 0.01 ", "", "@", "@?+1", "1", G,
"+42"}),
Ids{U, I(-1275), U, U, U, U, U, U, I(42), U, U, U, U, I(1), U, I(42)});
checkGetDouble(
idOrLitOrStringVec({U, "-122.2", "19,96", " 128789334.345 ", "-0.f",
" 0.007 ", " -14.75 ", "Q", "@!+?", "1", G}),
" 0.007 ", " -14.75 ", "Q", "@!+?", "1", G, "+42.0",
" +1E-2", "1e3 ", "1.3E1"}),
Ids{U, D(-122.2), U, D(128789334.345), U, D(0.007), D(-14.75), U, U,
D(1.00), U});
D(1.00), U, D(42), D(0.01), D(1000), D(13)});
chekGetDecimal(
idOrLitOrStringVec({U, "-122.2", "19,96", " 128789334.345 ", "-0.f",
" 0.007 ", " -14.75 ", "Q", "@!+?", "1", G, "+42.0",
" +1E-2", "1e3 ", "1.3E1"}),
Ids{U, D(-122.2), U, D(128789334.345), U, D(0.007), D(-14.75), U, U,
D(1.00), U, D(42), U, U, U});
checkGetInt(idOrLitOrStringVec(
{U, I(-12475), I(42), I(0), D(-14.57), D(33.0), D(0.00001)}),
Ids{U, I(-12475), I(42), I(0), I(-14), I(33), I(0)});
checkGetDouble(
idOrLitOrStringVec(
{U, I(-12475), I(42), I(0), D(-14.57), D(33.0), D(0.00001)}),
Ids{U, D(-12475.00), D(42.00), D(0.00), D(-14.57), D(33.00), D(0.00001)});
chekGetDecimal(
idOrLitOrStringVec(
{U, I(-12475), I(42), I(0), D(-14.57), D(33.0), D(0.00001)}),
Ids{U, D(-12475.00), D(42.00), D(0.00), D(-14.57), D(33.00), D(0.00001)});
checkGetDouble(IdOrLiteralOrIriVec{lit("."), lit("-12.745"), T, F,
lit("0.003"), lit("1")},
Ids{U, D(-12.745), D(1.00), D(0.00), D(0.003), D(1.00)});
chekGetDecimal(IdOrLiteralOrIriVec{lit("."), lit("-12.745"), T, F,
lit("0.003"), lit("1")},
Ids{U, D(-12.745), D(1.00), D(0.00), D(0.003), D(1.00)});
checkGetInt(IdOrLiteralOrIriVec{lit("."), lit("-12.745"), T, F, lit(".03"),
lit("1"), lit("-33")},
Ids{U, U, I(1), I(0), U, I(1), I(-33)});
}

// ____________________________________________________________________________
TEST(SparqlExpression, testToBooleanExpression) {
Id T = Id::makeFromBool(true);
Id F = Id::makeFromBool(false);
auto checkGetBoolean = testUnaryExpression<&makeConvertToBooleanExpression>;

checkGetBoolean(
IdOrLiteralOrIriVec(
{sparqlExpression::detail::LiteralOrIri{
iri("<http://example.org/z>")},
lit("string"), lit("-10.2E3"), lit("+33.3300"), lit("0.0"), lit("0"),
lit("0E1"), lit("1.5"), lit("1"), lit("1E0"), lit("13"),
lit("2002-10-10T17:00:00Z"), lit("false"), lit("true"), T, F,
lit("0", absl::StrCat("^^<", XSD_PREFIX.second, "boolean>")), I(0),
I(1), I(-1), D(0.0), D(1.0), D(-1.0),
// The SPARQL compliance tests for the boolean conversion functions
// mandate that xds:boolean("0E1") is undefined, but
// xsd:boolean("0E1"^^xsd:float) is false. The code currently returns
// undefined in both cases, which is fine, because the SPARQL parser
// converts the latter into an actual float literal.
lit("0E1", absl::StrCat("^^<", XSD_PREFIX.second, "float>")),
lit("1E0", absl::StrCat("^^<", XSD_PREFIX.second, "float>")),
lit("1.25", absl::StrCat("^^<", XSD_PREFIX.second, "float>")),
lit("-7.875", absl::StrCat("^^<", XSD_PREFIX.second, "float>"))}),
Ids{U, U, U, U, U, F, U, U, T, U, U, U, F, T,
T, F, F, F, T, T, F, T, T, U, U, U, U});

checkGetBoolean(IdOrLiteralOrIriVec({Id::makeUndefined()}), Ids{U});
}

// ____________________________________________________________________________
TEST(SparqlExpression, geoSparqlExpressions) {
auto checkLat = testUnaryExpression<&makeLatitudeExpression>;
Expand Down

0 comments on commit be240ab

Please sign in to comment.