QueryEvaluationUtil.java
/*******************************************************************************
* Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
package org.eclipse.rdf4j.query.algebra.evaluation.util;
import javax.xml.datatype.DatatypeConstants;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.base.CoreDatatype;
import org.eclipse.rdf4j.model.datatypes.XMLDatatypeUtil;
import org.eclipse.rdf4j.model.impl.BooleanLiteral;
import org.eclipse.rdf4j.model.util.Literals;
import org.eclipse.rdf4j.query.algebra.Compare.CompareOp;
import org.eclipse.rdf4j.query.algebra.evaluation.ValueExprEvaluationException;
/**
* Utility functions used during logical query evaluation.
*
* <p>
* <b>Performance note</b>: every comparison operator now has its own specialised method. All hot paths are branch���free
* w.r.t. {@code CompareOp}, allowing the JVM to inline and optimise aggressively.
* </p>
*/
public class QueryEvaluationUtil {
/*
* ======================================================================= Shared (unchanged) exception instances
* =====================================================================
*/
public static final ValueExprEvaluationException INDETERMINATE_DATE_TIME_EXCEPTION = new ValueExprEvaluationException(
"Indeterminate result for date/time comparison");
public static final ValueExprEvaluationException STRING_WITH_OTHER_SUPPORTED_TYPE_EXCEPTION = new ValueExprEvaluationException(
"Unable to compare strings with other supported types");
public static final ValueExprEvaluationException NUMERIC_WITH_OTHER_SUPPORTED_TYPE_EXCEPTION = new ValueExprEvaluationException(
"Unable to compare numeric types with other supported types");
public static final ValueExprEvaluationException DATE_WITH_OTHER_SUPPORTED_TYPE_EXCEPTION = new ValueExprEvaluationException(
"Unable to compare date types with other supported types");
public static final ValueExprEvaluationException UNSUPPOERTED_TYPES_EXCEPTION = new ValueExprEvaluationException(
"Unable to compare literals with unsupported types");
public static final ValueExprEvaluationException NOT_COMPATIBLE_AND_ORDERED_EXCEPTION = new ValueExprEvaluationException(
"Only literals with compatible, ordered datatypes can be compared using <, <=, > and >= operators");
/*
* ======================================================================= EBV helper (unchanged)
* =====================================================================
*/
public static boolean getEffectiveBooleanValue(Value value) throws ValueExprEvaluationException {
if (value == BooleanLiteral.TRUE) {
return true;
}
if (value == BooleanLiteral.FALSE) {
return false;
}
if (value.isLiteral()) {
Literal lit = (Literal) value;
String label = lit.getLabel();
CoreDatatype.XSD dt = lit.getCoreDatatype().asXSDDatatypeOrNull();
if (dt == CoreDatatype.XSD.STRING) {
return !label.isEmpty();
}
if (dt == CoreDatatype.XSD.BOOLEAN) {
return "true".equals(label) || "1".equals(label);
}
try {
if (dt == CoreDatatype.XSD.DECIMAL) {
return !"0.0".equals(XMLDatatypeUtil.normalizeDecimal(label));
}
if (dt != null && dt.isIntegerDatatype()) {
return !"0".equals(XMLDatatypeUtil.normalize(label, dt));
}
if (dt != null && dt.isFloatingPointDatatype()) {
String n = XMLDatatypeUtil.normalize(label, dt);
return !("0.0E0".equals(n) || "NaN".equals(n));
}
} catch (IllegalArgumentException ignore) {
return false;
}
}
throw new ValueExprEvaluationException();
}
/*
* ======================================================================= Tiny int���comparators
* =====================================================================
*/
private static boolean _lt(int c) {
return c < 0;
}
private static boolean _le(int c) {
return c <= 0;
}
private static boolean _eq(int c) {
return c == 0;
}
private static boolean _ne(int c) {
return c != 0;
}
private static boolean _gt(int c) {
return c > 0;
}
private static boolean _ge(int c) {
return c >= 0;
}
/*
* ======================================================================= PUBLIC VALUE���LEVEL SPECIALISED
* COMPARATORS =====================================================================
*/
/* -------- EQ -------- */
public static boolean compareEQ(Value l, Value r) throws ValueExprEvaluationException {
return compareEQ(l, r, true);
}
public static boolean compareEQ(Value l, Value r, boolean strict)
throws ValueExprEvaluationException {
if (l == null || r == null) {
return l == r; // null is equal to null, but not to anything else
}
if (l == r) {
return true;
}
if (l.isLiteral() && r.isLiteral()) {
return doCompareLiteralsEQ((Literal) l, (Literal) r, strict);
}
return l.equals(r);
}
/* -------- NE -------- */
public static boolean compareNE(Value l, Value r) throws ValueExprEvaluationException {
return compareNE(l, r, true);
}
public static boolean compareNE(Value l, Value r, boolean strict)
throws ValueExprEvaluationException {
if (l == null || r == null) {
return l != r; // null is equal to null, but not to anything else
}
if (l == r) {
return false;
}
if (l.isLiteral() && r.isLiteral()) {
return doCompareLiteralsNE((Literal) l, (Literal) r, strict);
}
return !l.equals(r);
}
/* -------- LT -------- */
public static boolean compareLT(Value l, Value r) throws ValueExprEvaluationException {
return compareLT(l, r, true);
}
public static boolean compareLT(Value l, Value r, boolean strict)
throws ValueExprEvaluationException {
if (l == r) {
if (l == null || !l.isLiteral()) {
throw NOT_COMPATIBLE_AND_ORDERED_EXCEPTION;
}
return false;
}
if (l != null && l.isLiteral() && r != null && r.isLiteral()) {
return doCompareLiteralsLT((Literal) l, (Literal) r, strict);
}
throw NOT_COMPATIBLE_AND_ORDERED_EXCEPTION;
}
/* -------- LE -------- */
public static boolean compareLE(Value l, Value r) throws ValueExprEvaluationException {
return compareLE(l, r, true);
}
public static boolean compareLE(Value l, Value r, boolean strict)
throws ValueExprEvaluationException {
if (l == r) {
if (l == null || !l.isLiteral()) {
throw NOT_COMPATIBLE_AND_ORDERED_EXCEPTION;
}
return true;
}
if (l != null && l.isLiteral() && r != null && r.isLiteral()) {
return doCompareLiteralsLE((Literal) l, (Literal) r, strict);
}
throw NOT_COMPATIBLE_AND_ORDERED_EXCEPTION;
}
/* -------- GT -------- */
public static boolean compareGT(Value l, Value r) throws ValueExprEvaluationException {
return compareGT(l, r, true);
}
public static boolean compareGT(Value l, Value r, boolean strict)
throws ValueExprEvaluationException {
if (l == r) {
if (l == null || !l.isLiteral()) {
throw NOT_COMPATIBLE_AND_ORDERED_EXCEPTION;
}
return false;
}
if (l != null && l.isLiteral() && r != null && r.isLiteral()) {
return doCompareLiteralsGT((Literal) l, (Literal) r, strict);
}
throw NOT_COMPATIBLE_AND_ORDERED_EXCEPTION;
}
/* -------- GE -------- */
public static boolean compareGE(Value l, Value r) throws ValueExprEvaluationException {
return compareGE(l, r, true);
}
public static boolean compareGE(Value l, Value r, boolean strict)
throws ValueExprEvaluationException {
if (l == r) {
if (l == null || !l.isLiteral()) {
throw NOT_COMPATIBLE_AND_ORDERED_EXCEPTION;
}
return true;
}
if (l != null && l.isLiteral() && r != null && r.isLiteral()) {
return doCompareLiteralsGE((Literal) l, (Literal) r, strict);
}
throw NOT_COMPATIBLE_AND_ORDERED_EXCEPTION;
}
/*
* ======================================================================= PUBLIC LITERAL���LEVEL SPECIALISED
* COMPARATORS =====================================================================
*/
/* -- EQ -- */
public static boolean compareLiteralsEQ(Literal l, Literal r) throws ValueExprEvaluationException {
return compareLiteralsEQ(l, r, true);
}
public static boolean compareLiteralsEQ(Literal l, Literal r, boolean strict)
throws ValueExprEvaluationException {
return doCompareLiteralsEQ(l, r, strict);
}
/* -- NE -- */
public static boolean compareLiteralsNE(Literal l, Literal r) throws ValueExprEvaluationException {
return compareLiteralsNE(l, r, true);
}
public static boolean compareLiteralsNE(Literal l, Literal r, boolean strict)
throws ValueExprEvaluationException {
return doCompareLiteralsNE(l, r, strict);
}
/* -- LT -- */
public static boolean compareLiteralsLT(Literal l, Literal r) throws ValueExprEvaluationException {
return compareLiteralsLT(l, r, true);
}
public static boolean compareLiteralsLT(Literal l, Literal r, boolean strict)
throws ValueExprEvaluationException {
return doCompareLiteralsLT(l, r, strict);
}
/* -- LE -- */
public static boolean compareLiteralsLE(Literal l, Literal r) throws ValueExprEvaluationException {
return compareLiteralsLE(l, r, true);
}
public static boolean compareLiteralsLE(Literal l, Literal r, boolean strict)
throws ValueExprEvaluationException {
return doCompareLiteralsLE(l, r, strict);
}
/* -- GT -- */
public static boolean compareLiteralsGT(Literal l, Literal r) throws ValueExprEvaluationException {
return compareLiteralsGT(l, r, true);
}
public static boolean compareLiteralsGT(Literal l, Literal r, boolean strict)
throws ValueExprEvaluationException {
return doCompareLiteralsGT(l, r, strict);
}
/* -- GE -- */
public static boolean compareLiteralsGE(Literal l, Literal r) throws ValueExprEvaluationException {
return compareLiteralsGE(l, r, true);
}
public static boolean compareLiteralsGE(Literal l, Literal r, boolean strict)
throws ValueExprEvaluationException {
return doCompareLiteralsGE(l, r, strict);
}
/*
* ======================================================================= LEGACY PUBLIC APIs ��� retained for
* compatibility =====================================================================
*/
/** @deprecated use the specialised compareXX methods instead. */
@Deprecated
public static boolean compare(Value l, Value r, CompareOp op)
throws ValueExprEvaluationException {
return compare(l, r, op, true);
}
/** @deprecated use the specialised compareXX methods instead. */
@Deprecated
public static boolean compare(Value l, Value r, CompareOp op, boolean strict)
throws ValueExprEvaluationException {
switch (op) {
case EQ:
return compareEQ(l, r, strict);
case NE:
return compareNE(l, r, strict);
case LT:
return compareLT(l, r, strict);
case LE:
return compareLE(l, r, strict);
case GT:
return compareGT(l, r, strict);
case GE:
return compareGE(l, r, strict);
default:
throw new IllegalArgumentException("Unknown operator: " + op);
}
}
/** @deprecated use the specialised compareLiteralsXX methods instead. */
@Deprecated
public static boolean compareLiterals(Literal l, Literal r, CompareOp op)
throws ValueExprEvaluationException {
return compareLiterals(l, r, op, true);
}
/** @deprecated use the specialised compareLiteralsXX methods instead. */
@Deprecated
public static boolean compareLiterals(Literal l, Literal r, CompareOp op, boolean strict)
throws ValueExprEvaluationException {
switch (op) {
case EQ:
return compareLiteralsEQ(l, r, strict);
case NE:
return compareLiteralsNE(l, r, strict);
case LT:
return compareLiteralsLT(l, r, strict);
case LE:
return compareLiteralsLE(l, r, strict);
case GT:
return compareLiteralsGT(l, r, strict);
case GE:
return compareLiteralsGE(l, r, strict);
default:
throw new IllegalArgumentException("Unknown operator: " + op);
}
}
/* Still referenced by some external code */
public static boolean compareWithOperator(CompareOp op, int c) {
switch (op) {
case LT:
return _lt(c);
case LE:
return _le(c);
case EQ:
return _eq(c);
case NE:
return _ne(c);
case GE:
return _ge(c);
case GT:
return _gt(c);
default:
throw new IllegalArgumentException("Unknown operator: " + op);
}
}
/*
* ======================================================================= PRIVATE HEAVY LITERAL COMPARATORS
* (prefixed with do��� to avoid signature clashes with public wrappers)
* =====================================================================
*/
private static boolean doCompareLiteralsEQ(Literal l, Literal r, boolean strict)
throws ValueExprEvaluationException {
if (l == r) {
return true;
}
CoreDatatype ld = l.getCoreDatatype();
CoreDatatype rd = r.getCoreDatatype();
if (ld == rd) {
if (ld == CoreDatatype.XSD.STRING) {
return l.getLabel().equals(r.getLabel());
}
if (ld == CoreDatatype.RDF.LANGSTRING) {
return l.getLanguage().equals(r.getLanguage()) && l.getLabel().equals(r.getLabel());
}
}
boolean lLang = Literals.isLanguageLiteral(l);
boolean rLang = Literals.isLanguageLiteral(r);
if (!(lLang || rLang)) {
CoreDatatype.XSD common = getCommonDatatype(strict, ld.asXSDDatatypeOrNull(), rd.asXSDDatatypeOrNull());
if (common != null) {
try {
if (common == CoreDatatype.XSD.STRING) {
return l.getLabel().equals(r.getLabel());
}
if (common == CoreDatatype.XSD.DOUBLE) {
return l.doubleValue() == r.doubleValue();
}
if (common == CoreDatatype.XSD.FLOAT) {
return l.floatValue() == r.floatValue();
}
if (common == CoreDatatype.XSD.BOOLEAN) {
return l.booleanValue() == r.booleanValue();
}
if (l.getLabel().equals(r.getLabel())) {
return true;
}
if (common == CoreDatatype.XSD.DECIMAL) {
return l.decimalValue().compareTo(r.decimalValue()) == 0;
}
if (common.isIntegerDatatype()) {
return l.integerValue().compareTo(r.integerValue()) == 0;
}
if (common.isCalendarDatatype()) {
if (ld == rd) {
if (l.getLabel().equals(r.getLabel())) {
return true; // same label, same calendar value
}
}
int c = l.calendarValue().compare(r.calendarValue());
if (c == DatatypeConstants.INDETERMINATE &&
ld == CoreDatatype.XSD.DATETIME &&
rd == CoreDatatype.XSD.DATETIME) {
throw INDETERMINATE_DATE_TIME_EXCEPTION;
}
return _eq(c);
}
if (!strict && common.isDurationDatatype()) {
if (ld == rd) {
if (l.getLabel().equals(r.getLabel())) {
return true; // same label, same calendar value
}
}
int c = XMLDatatypeUtil.parseDuration(l.getLabel())
.compare(XMLDatatypeUtil.parseDuration(r.getLabel()));
if (c != DatatypeConstants.INDETERMINATE) {
return _eq(c);
}
}
} catch (IllegalArgumentException iae) {
// lexical���to���value failed; fall through
}
}
}
return otherCasesEQ(l, r, ld.asXSDDatatypeOrNull(), rd.asXSDDatatypeOrNull(), lLang, rLang, strict);
}
private static boolean doCompareLiteralsNE(Literal l, Literal r, boolean strict)
throws ValueExprEvaluationException {
if (l.equals(r)) {
return false;
}
return !doCompareLiteralsEQ(l, r, strict);
}
private static boolean doCompareLiteralsLT(Literal l, Literal r, boolean strict)
throws ValueExprEvaluationException {
CoreDatatype.XSD ld = l.getCoreDatatype().asXSDDatatypeOrNull();
CoreDatatype.XSD rd = r.getCoreDatatype().asXSDDatatypeOrNull();
boolean lLang = Literals.isLanguageLiteral(l);
boolean rLang = Literals.isLanguageLiteral(r);
if (isSimpleLiteral(lLang, ld) && isSimpleLiteral(rLang, rd)) {
return _lt(l.getLabel().compareTo(r.getLabel()));
}
if (!(lLang || rLang)) {
CoreDatatype.XSD common = getCommonDatatype(strict, ld, rd);
if (common != null) {
try {
if (common == CoreDatatype.XSD.DOUBLE) {
return _lt(Double.compare(l.doubleValue(), r.doubleValue()));
}
if (common == CoreDatatype.XSD.FLOAT) {
return _lt(Float.compare(l.floatValue(), r.floatValue()));
}
if (common == CoreDatatype.XSD.DECIMAL) {
return _lt(l.decimalValue().compareTo(r.decimalValue()));
}
if (common.isIntegerDatatype()) {
return _lt(l.integerValue().compareTo(r.integerValue()));
}
if (common == CoreDatatype.XSD.BOOLEAN) {
return _lt(Boolean.compare(l.booleanValue(), r.booleanValue()));
}
if (common.isCalendarDatatype()) {
int c = l.calendarValue().compare(r.calendarValue());
if (c == DatatypeConstants.INDETERMINATE &&
ld == CoreDatatype.XSD.DATETIME &&
rd == CoreDatatype.XSD.DATETIME) {
throw INDETERMINATE_DATE_TIME_EXCEPTION;
}
return _lt(c);
}
if (!strict && common.isDurationDatatype()) {
int c = XMLDatatypeUtil.parseDuration(l.getLabel())
.compare(XMLDatatypeUtil.parseDuration(r.getLabel()));
if (c != DatatypeConstants.INDETERMINATE) {
return _lt(c);
}
}
if (common == CoreDatatype.XSD.STRING) {
return _lt(l.getLabel().compareTo(r.getLabel()));
}
} catch (IllegalArgumentException iae) {
throw new ValueExprEvaluationException(iae);
}
}
}
if (!isSupportedDatatype(ld) || !isSupportedDatatype(rd)) {
throw UNSUPPOERTED_TYPES_EXCEPTION;
}
validateDatatypeCompatibility(strict, ld, rd);
throw NOT_COMPATIBLE_AND_ORDERED_EXCEPTION;
}
private static boolean doCompareLiteralsLE(Literal l, Literal r, boolean strict)
throws ValueExprEvaluationException {
return doCompareLiteralsLT(l, r, strict) || doCompareLiteralsEQ(l, r, strict);
}
private static boolean doCompareLiteralsGT(Literal l, Literal r, boolean strict)
throws ValueExprEvaluationException {
return !doCompareLiteralsLE(l, r, strict);
}
private static boolean doCompareLiteralsGE(Literal l, Literal r, boolean strict)
throws ValueExprEvaluationException {
return !doCompareLiteralsLT(l, r, strict);
}
/*
* ======================================================================= Fallback for EQ otherCases (unchanged
* from previous draft) =====================================================================
*/
private static boolean otherCasesEQ(Literal left, Literal right,
CoreDatatype.XSD ldt, CoreDatatype.XSD rdt,
boolean lLang, boolean rLang, boolean strict)
throws ValueExprEvaluationException {
boolean equal = left.equals(right);
if (!equal) {
if (!lLang && !rLang && isSupportedDatatype(ldt) && isSupportedDatatype(rdt)) {
if (!XMLDatatypeUtil.isValidValue(left.getLabel(), ldt)) {
throw new ValueExprEvaluationException("not a valid datatype value: " + left);
}
if (!XMLDatatypeUtil.isValidValue(right.getLabel(), rdt)) {
throw new ValueExprEvaluationException("not a valid datatype value: " + right);
}
validateDatatypeCompatibility(strict, ldt, rdt);
} else if (!lLang && !rLang) {
throw UNSUPPOERTED_TYPES_EXCEPTION;
}
}
return equal;
}
/*
* ======================================================================= Datatype helpers & misc (unchanged)
* =====================================================================
*/
private static void validateDatatypeCompatibility(boolean strict,
CoreDatatype.XSD ld, CoreDatatype.XSD rd)
throws ValueExprEvaluationException {
if (!strict) {
return;
}
boolean leftString = ld == CoreDatatype.XSD.STRING;
boolean rightString = rd == CoreDatatype.XSD.STRING;
if (leftString != rightString) {
throw STRING_WITH_OTHER_SUPPORTED_TYPE_EXCEPTION;
}
boolean leftNum = ld.isNumericDatatype();
boolean rightNum = rd.isNumericDatatype();
if (leftNum != rightNum) {
throw NUMERIC_WITH_OTHER_SUPPORTED_TYPE_EXCEPTION;
}
boolean leftDate = ld.isCalendarDatatype();
boolean rightDate = rd.isCalendarDatatype();
if (leftDate != rightDate) {
throw DATE_WITH_OTHER_SUPPORTED_TYPE_EXCEPTION;
}
}
private static CoreDatatype.XSD getCommonDatatype(boolean strict,
CoreDatatype.XSD ld, CoreDatatype.XSD rd) {
if (ld != null && rd != null) {
if (ld == rd) {
return ld;
}
if (ld.isNumericDatatype() && rd.isNumericDatatype()) {
if (ld == CoreDatatype.XSD.DOUBLE || rd == CoreDatatype.XSD.DOUBLE) {
return CoreDatatype.XSD.DOUBLE;
}
if (ld == CoreDatatype.XSD.FLOAT || rd == CoreDatatype.XSD.FLOAT) {
return CoreDatatype.XSD.FLOAT;
}
if (ld == CoreDatatype.XSD.DECIMAL || rd == CoreDatatype.XSD.DECIMAL) {
return CoreDatatype.XSD.DECIMAL;
}
return CoreDatatype.XSD.INTEGER;
}
if (!strict && ld.isCalendarDatatype() && rd.isCalendarDatatype()) {
return CoreDatatype.XSD.DATETIME;
}
if (!strict && ld.isDurationDatatype() && rd.isDurationDatatype()) {
return CoreDatatype.XSD.DURATION;
}
}
return null;
}
public static boolean isPlainLiteral(Value v) {
return v.isLiteral() && isPlainLiteral((Literal) v);
}
public static boolean isPlainLiteral(Literal l) {
assert l.getLanguage().isEmpty() || l.getCoreDatatype() == CoreDatatype.RDF.LANGSTRING;
return l.getCoreDatatype() == CoreDatatype.XSD.STRING ||
l.getCoreDatatype() == CoreDatatype.RDF.LANGSTRING;
}
public static boolean isSimpleLiteral(Value v) {
return v.isLiteral() && isSimpleLiteral((Literal) v);
}
public static boolean isSimpleLiteral(Literal l) {
return l.getCoreDatatype() == CoreDatatype.XSD.STRING && !Literals.isLanguageLiteral(l);
}
public static boolean isSimpleLiteral(boolean lang, CoreDatatype dt) {
return !lang && dt == CoreDatatype.XSD.STRING;
}
public static boolean isStringLiteral(Value v) {
return v.isLiteral() && isStringLiteral((Literal) v);
}
public static boolean isStringLiteral(Literal l) {
return l.getCoreDatatype() == CoreDatatype.XSD.STRING || Literals.isLanguageLiteral(l);
}
private static boolean isSupportedDatatype(CoreDatatype.XSD dt) {
return dt != null && (dt == CoreDatatype.XSD.STRING || dt.isNumericDatatype() || dt.isCalendarDatatype());
}
/**
* Checks whether the supplied two literal arguments are 'argument compatible' according to the SPARQL definition.
*
* @param arg1 the first argument
* @param arg2 the second argument
* @return true iff the two supplied arguments are argument compatible, false otherwise
* @see <a href="http://www.w3.org/TR/sparql11-query/#func-arg-compatibility">SPARQL Argument Compatibility
* Rules</a>
*/
public static boolean compatibleArguments(Literal arg1, Literal arg2) {
// 1. The arguments are literals typed as CoreDatatype.XSD:string
// 2. The arguments are language literals with identical language tags
// 3. The first argument is a language literal and the second
// argument is a literal typed as CoreDatatype.XSD:string
return isSimpleLiteral(arg1) && isSimpleLiteral(arg2)
|| Literals.isLanguageLiteral(arg1) && Literals.isLanguageLiteral(arg2)
&& arg1.getLanguage().equals(arg2.getLanguage())
|| Literals.isLanguageLiteral(arg1) && isSimpleLiteral(arg2);
}
}