This is an automated email from the ASF dual-hosted git repository. andy pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/jena.git
commit da41c836247353f502c6f6b168b196df7b94e513 Author: Andy Seaborne <[email protected]> AuthorDate: Wed Apr 23 09:24:03 2025 +0100 GH-3144: SPARQL function call by URI --- .../src/main/java/org/apache/jena/query/ARQ.java | 2 + .../java/org/apache/jena/sparql/ARQConstants.java | 20 +- .../sparql/expr/urifunctions/SPARQLDispatch.java | 253 ++++++++++++++++ .../expr/urifunctions/SPARQLEvalException.java | 25 ++ .../sparql/expr/urifunctions/SPARQLFuncOp.java | 321 +++++++++++++++++++++ .../java/org/apache/jena/sparql/expr/TS_Expr.java | 1 + .../apache/jena/sparql/expr/TestFunctions2.java | 19 +- .../jena/sparql/expr/TestFunctionsByURI.java | 69 +++++ 8 files changed, 689 insertions(+), 21 deletions(-) diff --git a/jena-arq/src/main/java/org/apache/jena/query/ARQ.java b/jena-arq/src/main/java/org/apache/jena/query/ARQ.java index 2d6649babf..2448cc3c73 100644 --- a/jena-arq/src/main/java/org/apache/jena/query/ARQ.java +++ b/jena-arq/src/main/java/org/apache/jena/query/ARQ.java @@ -36,6 +36,7 @@ import org.apache.jena.sparql.algebra.optimize.TransformOrderByDistinctApplicati import org.apache.jena.sparql.core.assembler.AssemblerUtils; import org.apache.jena.sparql.exec.http.QuerySendMode; import org.apache.jena.sparql.expr.aggregate.AggregateRegistry; +import org.apache.jena.sparql.expr.urifunctions.SPARQLFuncOp; import org.apache.jena.sparql.function.FunctionRegistry; import org.apache.jena.sparql.function.scripting.ScriptLangSymbols; import org.apache.jena.sparql.mgt.Explain; @@ -685,6 +686,7 @@ public class ARQ ServiceExecutorRegistry.init(); AggregateRegistry.init(); PropertyFunctionRegistry.init(); + SPARQLFuncOp.init(); // Register the datatypes for the CDT literals TypeMapper.getInstance().registerDatatype(CompositeDatatypeList.type) ; diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/ARQConstants.java b/jena-arq/src/main/java/org/apache/jena/sparql/ARQConstants.java index 97f99c1860..464c51223e 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/ARQConstants.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/ARQConstants.java @@ -126,17 +126,17 @@ public class ARQConstants protected static final PrefixMapping globalPrefixMap = new PrefixMappingImpl() ; static { //globalPrefixMap.setNsPrefixes(PrefixMapping.Standard) ; - globalPrefixMap.setNsPrefix("rdf", rdfPrefix) ; - globalPrefixMap.setNsPrefix("rdfs", rdfsPrefix) ; - globalPrefixMap.setNsPrefix("xsd", xsdPrefix) ; - globalPrefixMap.setNsPrefix("owl" , owlPrefix) ; - globalPrefixMap.setNsPrefix("fn" , fnPrefix) ; - globalPrefixMap.setNsPrefix("sparql", sparqlPrefix) ; + globalPrefixMap.setNsPrefix("rdf", rdfPrefix) ; + globalPrefixMap.setNsPrefix("rdfs", rdfsPrefix) ; + globalPrefixMap.setNsPrefix("xsd", xsdPrefix) ; + globalPrefixMap.setNsPrefix("owl" , owlPrefix) ; + globalPrefixMap.setNsPrefix("fn" , fnPrefix) ; + globalPrefixMap.setNsPrefix("sparql", sparqlPrefix) ; // Treat op: as fn: (op: has no namespace in XSD F&O). - globalPrefixMap.setNsPrefix("op" , fnPrefix) ; - globalPrefixMap.setNsPrefix("math" , mathPrefix) ; - globalPrefixMap.setNsPrefix("afn", ARQFunctionLibraryURI) ; - globalPrefixMap.setNsPrefix("apf", ARQPropertyFunctionLibraryURI) ; + globalPrefixMap.setNsPrefix("op" , fnPrefix) ; + globalPrefixMap.setNsPrefix("math" , mathPrefix) ; + globalPrefixMap.setNsPrefix("afn", ARQFunctionLibraryURI) ; + globalPrefixMap.setNsPrefix("apf", ARQPropertyFunctionLibraryURI) ; } public static PrefixMapping getGlobalPrefixMap() { return globalPrefixMap ; } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/expr/urifunctions/SPARQLDispatch.java b/jena-arq/src/main/java/org/apache/jena/sparql/expr/urifunctions/SPARQLDispatch.java new file mode 100644 index 0000000000..c1cc858c04 --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/sparql/expr/urifunctions/SPARQLDispatch.java @@ -0,0 +1,253 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.sparql.expr.urifunctions; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.apache.jena.atlas.lib.InternalErrorException; +import org.apache.jena.sparql.expr.NodeValue; + +/** + * Dispatch to SPARQL function by URI. + */ +public class SPARQLDispatch { + + static final String NS0 = SPARQLFuncOp.NS.substring(0, SPARQLFuncOp.NS.length()-1); + + public static NodeValue exec(String uri, List<NodeValue>args) { + Objects.requireNonNull(uri); + Objects.requireNonNull(args); + + NodeValue[] a = args.toArray(NodeValue[]::new); + return exec(uri,a); + } + + public static NodeValue exec(String uri, NodeValue...args) { + if ( uri.startsWith("#") ) { + // Short name. + uri = SPARQLDispatch.NS0+uri; + } + + Call call = getDispatchMap().get(uri); + if ( call == null ) + throw new SPARQLEvalException("No such function: "+uri); + return call.exec(args); + } + + private static RuntimeException exception(String format, Object...args) { + String msg = String.format(format, args); + return new SPARQLEvalException(msg); + } + + static void register(Map<String, Call> map, String uri, Function0 function) { + Call call = args->{ + if ( args.length != 0 ) throw exception("%s: Expected zero arguments. Got %d", uri, args.length); + return function.exec(); + }; + registerCall(map, uri, call); + } + + static void register(Map<String, Call> map, String uri, Function1 function) { + Call call = args->{ + if ( args.length != 1 ) throw exception("%s: Expected one argument. Got %d", uri, args.length); + return function.exec(args[0]); + }; + registerCall(map, uri, call); + } + + static void register(Map<String, Call> map, String uri, Function2 function) { + Call call = args->{ + if ( args.length != 2 ) throw exception("%s: Expected two arguments. Got %d", uri, args.length); + return function.exec(args[0], args[1]); + }; + registerCall(map, uri, call); + } + + static void register(Map<String, Call> map, String uri, Function3 function) { + Call call = args->{ + if ( args.length != 3 ) throw exception("%s: Expected three arguments. Got %d", uri, args.length); + return function.exec(args[0], args[1], args[2]); + }; + registerCall(map, uri, call); + } + + static void register(Map<String, Call> map, String uri, Function4 function) { + Call call = args->{ + if ( args.length != 3 ) throw exception("%s: Expected arguments. Got %d", uri, args.length); + return function.exec(args[0], args[1], args[2], args[3]); + }; + registerCall(map, uri, call); + } + + // Switch on arity + static void register(Map<String, Call> map, String uri, Function1 function1, Function2 function2) { + Call call = args->{ + if ( args.length == 1 ) + return function1.exec(args[0]); + if ( args.length == 2 ) + return function2.exec(args[0], args[1]); + throw exception("%s: Expected one or two arguments. Got %d", uri, args.length); + }; + registerCall(map, uri, call); + } + + // Switch on arity + static void register(Map<String, Call> map, String uri, Function2 function2, Function3 function3) { + Call call = args->{ + if ( args.length == 2 ) + return function2.exec(args[0], args[1]); + if ( args.length == 3 ) + return function3.exec(args[0], args[1], args[2]); + throw exception("%s: Expected two or three arguments. Got %d", uri, args.length); + }; + registerCall(map, uri, call); + } + + // Switch on arity + static void register(Map<String, Call> map, String uri, FunctionN function) { + Call call = args->{ + return function.exec(args); + }; + registerCall(map, uri, call); + } + + static void registerCall(Map<String, Call> map, String localName, Call call) { + String uri = SPARQLFuncOp.NS+localName; + Call oldCall = map.put(uri, call); + if ( oldCall != null ) + throw new InternalErrorException("Multiple registration of "+uri); + } + + private static class LazyDispatchMap { + private static final Map<String, Call> INITIALIZED_MAP = buildDispatchMap(); + private static Map<String, Call> dispatchMap() { + return INITIALIZED_MAP; + } + } + + static Map<String, Call> getDispatchMap() { + return LazyDispatchMap.dispatchMap(); + } + + static Map<String, Call> buildDispatchMap() { + Map<String, Call> map = new HashMap<>(); + register(map, "plus", SPARQLFuncOp::sparql_plus ); + register(map, "add", SPARQLFuncOp::sparql_plus ); // Alt name. + register(map, "subtract", SPARQLFuncOp::sparql_subtract ); + register(map, "minus", SPARQLFuncOp::sparql_subtract ); // Alt name. + register(map, "multiply", SPARQLFuncOp::sparql_multiply ); + register(map, "divide", SPARQLFuncOp::sparql_divide ); + + register(map, "equals", SPARQLFuncOp::sparql_equals ); + register(map, "not-equals", SPARQLFuncOp::sparql_not_equals ); + register(map, "greaterThan", SPARQLFuncOp::sparql_greaterThan ); + register(map, "greaterThanOrEquals", SPARQLFuncOp::sparql_greaterThanOrEquals ); + register(map, "lessThat", SPARQLFuncOp::sparql_lessThat ); + register(map, "lessThanOrEquals", SPARQLFuncOp::sparql_lessThanOrEquals ); + + register(map, "and", SPARQLFuncOp::sparql_function_and ); + register(map, "or", SPARQLFuncOp::sparql_function_or ); + register(map, "not", SPARQLFuncOp::sparql_function_not ); + + register(map, "unary-minus", SPARQLFuncOp::sparql_unary_minus ); + register(map, "unary-plus", SPARQLFuncOp::sparql_unary_plus ); + + register(map, "abs", SPARQLFuncOp::sparql_abs); + register(map, "bnode", SPARQLFuncOp::sparql_bnode); + register(map, "ceil", SPARQLFuncOp::sparql_ceil); + + // List arity + register(map, "concat", (NodeValue[] args)->SPARQLFuncOp.sparql_concat(args)); + register(map, "contains", SPARQLFuncOp::sparql_contains); + register(map, "datatype", SPARQLFuncOp::sparql_datatype); + + register(map, "encode", SPARQLFuncOp::sparql_encode); + register(map, "floor", SPARQLFuncOp::sparql_floor); + register(map, "haslang", SPARQLFuncOp::sparql_haslang); + register(map, "haslangdir", SPARQLFuncOp::sparql_haslangdir); + // Arity 1/2 + register(map, "iri", x->SPARQLFuncOp.sparql_iri(x), (x,b)->SPARQLFuncOp.sparql_iri(x,b)); + register(map, "uri", SPARQLFuncOp::sparql_uri); + register(map, "isBlank", SPARQLFuncOp::sparql_isBlank); + register(map, "isLiteral", SPARQLFuncOp::sparql_isLiteral); + register(map, "isNumeric", SPARQLFuncOp::sparql_isNumeric); + register(map, "isIRI", SPARQLFuncOp::sparql_isIRI); + register(map, "isURI", SPARQLFuncOp::sparql_isURI); + register(map, "lang", SPARQLFuncOp::sparql_lang); + register(map, "langMatches", SPARQLFuncOp::sparql_langMatches); + register(map, "langdir", SPARQLFuncOp::sparql_langdir); + register(map, "lcase", SPARQLFuncOp::sparql_lcase); + register(map, "ucase", SPARQLFuncOp::sparql_ucase); + register(map, "now", SPARQLFuncOp::sparql_now); + register(map, "rand", SPARQLFuncOp::sparql_rand); + register(map, "regex", SPARQLFuncOp::sparql_regex); + register(map, "replace", SPARQLFuncOp::sparql_replace); + register(map, "round", SPARQLFuncOp::sparql_round); + register(map, "sameTerm", SPARQLFuncOp::sparql_sameTerm); + register(map, "sameValue", SPARQLFuncOp::sparql_sameValue); + register(map, "uuid", SPARQLFuncOp::sparql_uuid); + + register(map, "year", SPARQLFuncOp::sparql_year); + register(map, "month", SPARQLFuncOp::sparql_month); + register(map, "day", SPARQLFuncOp::sparql_day); + register(map, "hours", SPARQLFuncOp::sparql_hours); + register(map, "minutes", SPARQLFuncOp::sparql_minutes); + register(map, "seconds", SPARQLFuncOp::sparql_seconds); + register(map, "tz", SPARQLFuncOp::sparql_tz); + register(map, "timezone", SPARQLFuncOp::sparql_timezone); + + register(map, "subject", SPARQLFuncOp::sparql_subject); + register(map, "object", SPARQLFuncOp::sparql_object); + register(map, "predicate", SPARQLFuncOp::sparql_predicate); + register(map, "isTriple", SPARQLFuncOp::sparql_isTriple); + register(map, "triple", SPARQLFuncOp::sparql_triple); + + register(map, "md5", SPARQLFuncOp::sparql_md5); + register(map, "sha1", SPARQLFuncOp::sparql_sha1); + register(map, "sha256", SPARQLFuncOp::sparql_sha256); + register(map, "sha384", SPARQLFuncOp::sparql_sha384); + register(map, "sha512", SPARQLFuncOp::sparql_sha512); + + register(map, "str", SPARQLFuncOp::sparql_str); + register(map, "strafter", SPARQLFuncOp::sparql_strafter); + register(map, "strbefore", SPARQLFuncOp::sparql_strbefore); + register(map, "strdt", SPARQLFuncOp::sparql_strdt); + register(map, "strends", SPARQLFuncOp::sparql_strends); + register(map, "strlang", SPARQLFuncOp::sparql_strlang); + register(map, "strlangdir", SPARQLFuncOp::sparql_strlangdir); + register(map, "strlen", SPARQLFuncOp::sparql_strlen); + register(map, "strstarts", SPARQLFuncOp::sparql_strstarts); + // Arity 2/3 + register(map, "substr", (s,x)->SPARQLFuncOp.sparql_substr(s,x), (s,x,y)->SPARQLFuncOp.sparql_substr(s,x,y)); + register(map, "struuid", SPARQLFuncOp::sparql_struuid); + + return Map.copyOf(map); + } + + interface Call { NodeValue exec(NodeValue... nv); } + interface Function0 { NodeValue exec(); } + interface Function1 { NodeValue exec(NodeValue nv); } + interface Function2 { NodeValue exec(NodeValue nv1, NodeValue nv2); } + interface Function3 { NodeValue exec(NodeValue nv1, NodeValue nv2, NodeValue nv3); } + interface Function4 { NodeValue exec(NodeValue nv1, NodeValue nv2, NodeValue nv3, NodeValue nv4); } + interface FunctionN { NodeValue exec(NodeValue... nv); } +} diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/expr/urifunctions/SPARQLEvalException.java b/jena-arq/src/main/java/org/apache/jena/sparql/expr/urifunctions/SPARQLEvalException.java new file mode 100644 index 0000000000..345c237954 --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/sparql/expr/urifunctions/SPARQLEvalException.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.sparql.expr.urifunctions; + +import org.apache.jena.shared.JenaException; + +class SPARQLEvalException extends JenaException { + SPARQLEvalException(String msg) { super(msg); } +} \ No newline at end of file diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/expr/urifunctions/SPARQLFuncOp.java b/jena-arq/src/main/java/org/apache/jena/sparql/expr/urifunctions/SPARQLFuncOp.java new file mode 100644 index 0000000000..24493cc72a --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/sparql/expr/urifunctions/SPARQLFuncOp.java @@ -0,0 +1,321 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.sparql.expr.urifunctions; + +import java.util.List; +import java.util.Map; + +import org.apache.jena.atlas.lib.*; +import org.apache.jena.query.ARQ; +import org.apache.jena.sparql.expr.*; +import org.apache.jena.sparql.expr.nodevalue.NodeFunctions; +import org.apache.jena.sparql.expr.nodevalue.NodeValueDigest; +import org.apache.jena.sparql.expr.nodevalue.NodeValueOps; +import org.apache.jena.sparql.expr.nodevalue.XSDFuncOp; +import org.apache.jena.sparql.expr.urifunctions.SPARQLDispatch.Call; +import org.apache.jena.sparql.function.FunctionBase; +import org.apache.jena.sparql.function.FunctionFactory; +import org.apache.jena.sparql.function.FunctionRegistry; +import org.apache.jena.sparql.function.library.triple.TripleTermOps; + +/** + * SPARQL Functions and Operators, including extensions. + * <p> + * This class does not cover functional forms e.g. IF, COALESCE and BOUND. + * <p> + * Some function-like use of keywords in SPARQL do not make sense as standalone functions. + * Some functions depend on the execution context and do not make sense outside of such a context + * e.g. {@code BNODE(string)}, {@code NOW()}. + */ +public class SPARQLFuncOp { + + public static final String NS = "https://www.w3.org/ns/sparql#"; + + // ---- Integration into ARQ function execution + + static boolean initialized = false; + public static void init() { + if ( ! initialized ) { + initialized = true; + loadFunctionRegistry(FunctionRegistry.get()); + } + } + + /** Load the SPARQL functions into a {@link FunctionRegistry}. */ + public static void loadFunctionRegistry(FunctionRegistry reg) { + Map<String, Call> map = SPARQLDispatch.getDispatchMap(); + // Add to the system FunctionRegistry once. + addToFunctionRegistry(FunctionRegistry.get(), map); + } + + private static void addToFunctionRegistry(FunctionRegistry reg, Map<String, Call> map) { + FunctionFactory ff = createFunctionFactory(); + map.forEach((uri,call) -> FunctionRegistry.get().put(uri, ff)); + } + + private static FunctionFactory createFunctionFactory() { + return uri -> new FunctionBase() { + @Override + public NodeValue exec(List<NodeValue> args) { + return SPARQLFuncOp.exec(uri, args); + } + @Override public void checkBuild(String uri, ExprList args) {} + }; + } + + // ---- Execution + + public static NodeValue exec(String uri, List<NodeValue>args) { + return SPARQLDispatch.exec(uri, args); + } + + public static NodeValue exec(String uri, NodeValue...args) { + return SPARQLDispatch.exec(uri, args); + } + + private static boolean strict() { + return ARQ.isStrictMode(); + } + + // ---- All the implementations + + public static NodeValue sparql_plus(NodeValue nv1, NodeValue nv2) { + if ( strict() ) + return XSDFuncOp.numAdd(nv1, nv2); + return NodeValueOps.additionNV(nv1, nv2); + } + + public static NodeValue sparql_subtract(NodeValue nv1, NodeValue nv2) { + if ( strict() ) + return XSDFuncOp.numSubtract(nv1, nv2); + return NodeValueOps.subtractionNV(nv1, nv2); + } + + public static NodeValue sparql_multiply(NodeValue nv1, NodeValue nv2) { + if ( strict() ) + return XSDFuncOp.numMultiply(nv1, nv2); + + return NodeValueOps.multiplicationNV(nv1, nv2); + } + + public static NodeValue sparql_divide(NodeValue nv1, NodeValue nv2) { + if ( strict() ) + return XSDFuncOp.numDivide(nv1, nv2); + return NodeValueOps.divisionNV(nv1, nv2); + } + + public static NodeValue sparql_unary_minus(NodeValue nv) { + return XSDFuncOp.unaryMinus(nv); } + + public static NodeValue sparql_unary_plus(NodeValue nv) { + return XSDFuncOp.unaryPlus(nv); + } + + public static NodeValue sparql_equals(NodeValue nv1, NodeValue nv2) { + boolean b = NodeValue.sameValueAs(nv1, nv2); + return NodeValue.booleanReturn(b); + } + + public static NodeValue sparql_not_equals(NodeValue nv1, NodeValue nv2) { + boolean b = NodeValue.notSameValueAs(nv1, nv2); + return NodeValue.booleanReturn(b); + } + + public static NodeValue sparql_greaterThan(NodeValue nv1, NodeValue nv2) { + int r = NodeValue.compare(nv1, nv2); + return NodeValue.booleanReturn(r == Expr.CMP_GREATER); + } + + public static NodeValue sparql_lessThat(NodeValue nv1, NodeValue nv2) { + int r = NodeValue.compare(nv1, nv2); + return NodeValue.booleanReturn(r == Expr.CMP_LESS); + } + + public static NodeValue sparql_greaterThanOrEquals(NodeValue nv1, NodeValue nv2) { + int r = NodeValue.compare(nv1, nv2); + return NodeValue.booleanReturn(r == Expr.CMP_GREATER || r == Expr.CMP_EQUAL); + } + + public static NodeValue sparql_lessThanOrEquals(NodeValue nv1, NodeValue nv2) { + int r = NodeValue.compare(nv1, nv2); + return NodeValue.booleanReturn(r == Expr.CMP_LESS || r == Expr.CMP_EQUAL); + } + + // and,or,not as functions (arguments have been evaluated) + + public static NodeValue sparql_function_and(NodeValue nv1, NodeValue nv2) { + boolean arg1 = XSDFuncOp.booleanEffectiveValue(nv1); + boolean arg2 = XSDFuncOp.booleanEffectiveValue(nv2); + return NodeValue.booleanReturn(arg1 && arg2); + } + + public static NodeValue sparql_function_or(NodeValue nv1, NodeValue nv2) { + boolean arg1 = XSDFuncOp.booleanEffectiveValue(nv1); + boolean arg2 = XSDFuncOp.booleanEffectiveValue(nv2); + return NodeValue.booleanReturn(arg1 || arg2); + } + + public static NodeValue sparql_function_not(NodeValue nv) { + boolean arg = XSDFuncOp.booleanEffectiveValue(nv); + return NodeValue.booleanReturn(!arg); + } + + // These are not functions. +// public static NodeValue sparql_operator_and() { return null; } +// public static NodeValue sparql_operator_or() { return null; } +// public static NodeValue sparql_operator_not() { return null; } + +// public static NodeValue sparql_bound() { return null; } +// public static NodeValue sparql_if() { return null; } +// public static NodeValue sparql_coalesce() { return null; } + +// public static NodeValue sparql_filter_exists() { return null; } +// public static NodeValue sparql_filter_not_exists() { return null; } + +// public static NodeValue sparql_logical_or() { return null; } +// public static NodeValue sparql_logical_and() { return null; } + +// public static NodeValue sparql_in(NodeValue... nv) { return null; } +// public static NodeValue sparql_not_in(NodeValue... nv) { return null; } + + public static NodeValue sparql_sameTerm(NodeValue nv1, NodeValue nv2) { return NodeFunctions.sameTerm(nv1, nv2); } + + public static NodeValue sparql_sameValue(NodeValue nv1, NodeValue nv2) { + // Need to deal with NaN. + if ( ( nv1.isDouble()||nv1.isFloat() ) && ( nv2.isDouble()||nv2.isFloat() ) ) { + double d1 = nv1.getDouble(); + double d2 = nv2.getDouble(); + if ( Double.isNaN(d1) &&Double.isNaN(d2) ) + return NodeValue.TRUE; + } + boolean b = NodeValue.sameValueAs(nv1, nv2); + return NodeValue.booleanReturn(b); + } + +// Old name:: public static NodeValue sparql_RDFterm_equal(NodeValue nv1, NodeValue nv2) { return null; } + public static NodeValue sparql_isIRI(NodeValue nv) { return NodeFunctions.isIRI(nv); } + public static NodeValue sparql_isURI(NodeValue nv) { return NodeFunctions.isURI(nv); } + public static NodeValue sparql_isBlank(NodeValue nv) { return NodeFunctions.isBlank(nv); } + public static NodeValue sparql_isLiteral(NodeValue nv) { return NodeFunctions.isLiteral(nv); } + public static NodeValue sparql_isNumeric(NodeValue nv) { return NodeFunctions.isNumeric(nv); } + public static NodeValue sparql_str(NodeValue nv) { return NodeFunctions.str(nv); } + + public static NodeValue sparql_lang(NodeValue nv) { return NodeFunctions.str(nv); } + public static NodeValue sparql_langdir(NodeValue nv) { return NodeFunctions.langdir(nv); } + public static NodeValue sparql_haslang(NodeValue nv) { return NodeFunctions.hasLang(nv); } + public static NodeValue sparql_haslangdir(NodeValue nv) { return NodeFunctions.hasLangDir(nv); } + public static NodeValue sparql_datatype(NodeValue nv) { return NodeFunctions.datatype(nv); } + + // Term functions : NodeFunctions + + public static NodeValue sparql_iri(NodeValue nv) { return NodeFunctions.iri(nv, null); } + // Extension + public static NodeValue sparql_iri(NodeValue nv, NodeValue nvBase) { return NodeFunctions.iri(nv, nvBase.getString()); } + + public static NodeValue sparql_uri(NodeValue nv) { return sparql_iri(nv); } + + // Only BNODE(), not BNODE(str) + public static NodeValue sparql_bnode() { return null; } + + // Not a function - depends on "current row". + //public static NodeValue sparql_bnode(NodeValue nv) { return null; } + + public static NodeValue sparql_strdt(NodeValue nv1, NodeValue nv2) { return NodeFunctions.strDatatype(nv1, nv2); } + public static NodeValue sparql_strlang(NodeValue nv1, NodeValue nv2) { return NodeFunctions.strLang(nv1, nv2); } + public static NodeValue sparql_strlangdir(NodeValue nv1, NodeValue nv2, NodeValue nv3) { return NodeFunctions.strLangDir(nv1, nv2, nv3); } + + public static NodeValue sparql_uuid() { return NodeFunctions.uuid(); } + public static NodeValue sparql_struuid() { return NodeFunctions.struuid(); } + + public static NodeValue sparql_strlen(NodeValue nv) { return XSDFuncOp.strlen(nv); } + public static NodeValue sparql_substr(NodeValue nv1, NodeValue nv2) { return XSDFuncOp.substring(nv1, nv2) ; } + public static NodeValue sparql_substr(NodeValue nv1, NodeValue nv2, NodeValue nv3) { return XSDFuncOp.substring(nv1, nv2, nv3) ; } + + public static NodeValue sparql_ucase(NodeValue nv) { return XSDFuncOp.strUpperCase(nv); } + public static NodeValue sparql_lcase(NodeValue nv) { return XSDFuncOp.strLowerCase(nv); } + public static NodeValue sparql_strstarts(NodeValue nv1, NodeValue nv2) { return XSDFuncOp.strStartsWith(nv1, nv2); } + public static NodeValue sparql_strends(NodeValue nv1, NodeValue nv2) { return XSDFuncOp.strEndsWith(nv1, nv2); } + + public static NodeValue sparql_contains(NodeValue nv1, NodeValue nv2) { return XSDFuncOp.strContains(nv1, nv2); } + public static NodeValue sparql_strbefore(NodeValue nv1, NodeValue nv2) { return XSDFuncOp.strBefore(nv1, nv2); } + public static NodeValue sparql_strafter(NodeValue nv1, NodeValue nv2) { return XSDFuncOp.strAfter(nv1, nv2); } + + public static NodeValue sparql_concat(NodeValue...args) { + return XSDFuncOp.strConcat(List.of(args)); + } + + public static NodeValue sparql_langMatches(NodeValue nv1, NodeValue nv2) { return NodeFunctions.langMatches(nv1, nv2); } + + private static record CacheKey(String pattern, String flags) {} + private static Cache<CacheKey, RegexEngine> regexEngineCache = CacheFactory.createCache(20); + private static RegexEngine getRegexEngine(String patternStr, String flagsStr) { + CacheKey cacheKey = new CacheKey(patternStr, flagsStr); + return regexEngineCache.get(cacheKey, (key)->E_Regex.makeRegexEngine(key.pattern, key.flags)); + } + + public static NodeValue sparql_regex(NodeValue string, NodeValue pattern, NodeValue flags) { + // Cache - this means regexes are compiled once. + String patternStr = pattern.getString(); + String flagsStr = flags.getString(); + RegexEngine regexEngine = getRegexEngine(patternStr, flagsStr); + String str = string.getString(); + boolean b = regexEngine.match(str); + return NodeValue.booleanReturn(b); + } + + public static NodeValue sparql_replace(NodeValue nvStr, NodeValue nvPattern, NodeValue nvReplacement) + { return XSDFuncOp.strReplace(nvStr, nvPattern, nvReplacement); } + + public static NodeValue sparql_encode(NodeValue nv) { return XSDFuncOp.strEncodeForURI(nv); } + public static NodeValue sparql_abs(NodeValue nv) { return XSDFuncOp.abs(nv); } + public static NodeValue sparql_round(NodeValue nv) { return XSDFuncOp.round(nv); } + public static NodeValue sparql_ceil(NodeValue nv) { return XSDFuncOp.ceiling(nv); } + public static NodeValue sparql_floor(NodeValue nv) { return XSDFuncOp.floor(nv); } + + public static NodeValue sparql_rand() { + double d = RandomLib.random.nextDouble(); + return NodeValue.makeDouble(d); + } + + // Warning : not scoped to evaluation. + public static NodeValue sparql_now() { return NodeValue.makeDateTime(DateTimeUtils.nowAsXSDDateTimeString()); } + + public static NodeValue sparql_year(NodeValue nv) { return XSDFuncOp.getYear(nv); } + public static NodeValue sparql_month(NodeValue nv) { return XSDFuncOp.getMonth(nv); } + public static NodeValue sparql_day(NodeValue nv) { return XSDFuncOp.getDay(nv); } + public static NodeValue sparql_hours(NodeValue nv) { return XSDFuncOp.getHours(nv); } + public static NodeValue sparql_minutes(NodeValue nv) { return XSDFuncOp.getMinutes(nv); } + public static NodeValue sparql_seconds(NodeValue nv) { return XSDFuncOp.getSeconds(nv); } + // Returns duration + public static NodeValue sparql_timezone(NodeValue nv) { return XSDFuncOp.dtGetTimezone(nv); } + // Returns string + public static NodeValue sparql_tz(NodeValue nv) { return XSDFuncOp.dtGetTZ(nv); } + + public static NodeValue sparql_triple(NodeValue s, NodeValue p, NodeValue o) { return TripleTermOps.fnTriple(s, p, o); } + public static NodeValue sparql_subject(NodeValue tripleTerm) { return TripleTermOps.tripleSubject(tripleTerm); } + public static NodeValue sparql_predicate(NodeValue tripleTerm) { return TripleTermOps.triplePredicate(tripleTerm); } + public static NodeValue sparql_object(NodeValue tripleTerm) { return TripleTermOps.tripleObject(tripleTerm); } + public static NodeValue sparql_isTriple(NodeValue nv) { return TripleTermOps.isTriple(nv); } + + public static NodeValue sparql_md5(NodeValue nv) { return NodeValueDigest.calculateDigest(nv, "MD-5"); } + public static NodeValue sparql_sha1(NodeValue nv) { return NodeValueDigest.calculateDigest(nv, "SHA-1"); } + public static NodeValue sparql_sha256(NodeValue nv) { return NodeValueDigest.calculateDigest(nv, "SHA-256"); } + public static NodeValue sparql_sha384(NodeValue nv) { return NodeValueDigest.calculateDigest(nv, "SHA-384"); } + public static NodeValue sparql_sha512(NodeValue nv) { return NodeValueDigest.calculateDigest(nv, "SHA-512"); } +} diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TS_Expr.java b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TS_Expr.java index 0fa08aa625..7a6e0ef9e0 100644 --- a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TS_Expr.java +++ b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TS_Expr.java @@ -37,6 +37,7 @@ import org.junit.runners.Suite.SuiteClasses; , TestExpressionsMath.class , TestFunctions.class , TestFunctions2.class + , TestFunctionsByURI.class , TestTripleTermFunctions.class , TestLeviathanFunctions.class , TestNodeValueOps.class diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions2.java b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions2.java index 1812d2b5e5..835f9e37c8 100644 --- a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions2.java +++ b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions2.java @@ -581,19 +581,16 @@ public class TestFunctions2 // -------- - private static PrefixMapping pmap = ARQConstants.getGlobalPrefixMap() ; + private static PrefixMapping pmap = ARQConstants.getGlobalPrefixMap(); - private static void test(String string, String result) - { - Expr expr = ExprUtils.parse(string, pmap) ; - NodeValue nv = expr.eval(null, new FunctionEnvBase()) ; - Node r = NodeFactoryExtra.parseNode(result) ; - NodeValue nvr = NodeValue.makeNode(r) ; + private static void test(String string, String result) { + Expr expr = ExprUtils.parse(string, pmap); + NodeValue nv = expr.eval(null, new FunctionEnvBase()); + Node r = NodeFactoryExtra.parseNode(result); + NodeValue nvr = NodeValue.makeNode(r); - assertTrue("Not same value: Expected: "+nvr+" : Actual = "+nv, NodeValue.sameValueAs(nvr, nv)) ; + assertTrue("Not same value: Expected: " + nvr + " : Actual = " + nv, NodeValue.sameValueAs(nvr, nv)); // test result must be lexical form exact. - assertEquals(r, nv.asNode()) ; + assertEquals(r, nv.asNode()); } - - // ROUND to TestXSDFuncOps. } diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctionsByURI.java b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctionsByURI.java new file mode 100644 index 0000000000..f9290ad091 --- /dev/null +++ b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctionsByURI.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.sparql.expr; + +import static org.junit.Assert.assertTrue; + +import org.junit.AfterClass ; +import org.junit.BeforeClass ; +import org.junit.Test ; + +import org.apache.jena.shared.PrefixMapping ; +import org.apache.jena.sparql.ARQConstants ; +import org.apache.jena.sparql.function.FunctionEnvBase ; +import org.apache.jena.sparql.sse.SSE; +import org.apache.jena.sparql.util.ExprUtils ; +import org.apache.jena.sys.JenaSystem; + +/** + * Test that SPARQl functions can be called by their URI as given in the SPARQL 1.2 + * namespace <a href="https://www.w3.org/ns/sparql#">https://www.w3.org/ns/sparql#</a>. + */ +public class TestFunctionsByURI +{ + private static boolean warnOnBadLexicalForms; + + static { JenaSystem.init(); } + + @BeforeClass + public static void beforeClass() { + warnOnBadLexicalForms = NodeValue.VerboseWarnings; + NodeValue.VerboseWarnings = false; + } + + @AfterClass + public static void afterClass() { + NodeValue.VerboseWarnings = warnOnBadLexicalForms; + } + + @Test public void sparql_function_uri_1() { test("sparql:unary-minus(123)", "-123") ; } + @Test public void sparql_function_uri_2() { test("sparql:subtract(12 ,sparql:multiply(5,2))", "2") ; } + @Test public void sparql_function_uri_3() { test("sparql:not(123)", "false") ; } // EBV - effective boolean value + // -------- + + private static PrefixMapping pmap = ARQConstants.getGlobalPrefixMap() ; + + private static void test(String string, String result) { + Expr expr = ExprUtils.parse(string, pmap); + NodeValue nv = expr.eval(null, new FunctionEnvBase()); + NodeValue nvr = SSE.parseNodeValue(result); + boolean b = NodeValue.sameValueAs(nvr, nv); + assertTrue("Not same value: Expected: " + nvr + " : Actual = " + nv, b); + } +}
