/* * 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 org.apache.jena.graph.Node ; import org.apache.jena.sparql.ARQInternalErrorException ; import org.apache.jena.sparql.algebra.optimize.ExprTransformConstantFold ; import org.apache.jena.sparql.algebra.walker.Walker ; import org.apache.jena.sparql.core.Var ; import org.apache.jena.sparql.engine.binding.Binding ; import org.apache.jena.sparql.function.FunctionEnv ; public class ExprLib { /** Evaluate or return null. * <p> * This is better (faster) than the simple implementation * which captures {@link ExprEvalException} and returns null. */ public static NodeValue evalOrNull(Expr expr, Binding binding, FunctionEnv functionEnv) { return evalOrElse(expr, binding, functionEnv, null) ; } /** evaluate or throw an exception */ // This post dates a lot of code that uses expr.eval directly. // Placeholder for now. private static NodeValue evalOrException(Expr expr, Binding binding, FunctionEnv functionEnv) { return expr.eval(binding, functionEnv) ; } private static NodeValue evalOrElse(Expr expr, Binding binding, FunctionEnv functionEnv, NodeValue exceptionValue) { // Exceptions in java are expensive if the stack information is // collected which is the default behaviour. The expensive step is // Throwable.fillInStackTrace. // // Otherwise, they are reasonable cheap. It needs special exceptions // which overrides fillInStackTrace to be cheap but they loose the // general information for development. // // Instead, pick out specal cases, the expression being a single variable // being the important one. // // BOUND(?x) is a important case where the expression is often an exception // in general evaluation. if ( expr.isConstant() ) // Easy case. return expr.getConstant() ; if ( expr.isVariable() ) { // The case of the expr being a single variable. Var v = expr.asVar() ; Node n = binding.get(v) ; if ( n == null ) return exceptionValue ; NodeValue nv = NodeValue.makeNode(n) ; return nv ; } try { return expr.eval(binding, functionEnv) ; } catch (ExprEvalException ex) { return exceptionValue ; } } /** Attempt to fold any sub-expressions of the Expr. * Return an expression that is euqivalent to the argument but maybe simpler. * @param expr * @return Expression */ public static Expr foldConstants(Expr expr) { return ExprTransformer.transform(new ExprTransformConstantFold(), expr) ; } /** transform an expression that may involve aggregates into one that just uses the variable for the aggregate */ public static Expr replaceAggregateByVariable(Expr expr) { return ExprTransformer.transform(replaceAgg, expr) ; } // /** transform expressions that may involve aggregates into one that just uses the variable for the aggregate */ // public static ExprList replaceAggregateByVariable(ExprList exprs) // { // return ExprTransformer.transform(replaceAgg, exprs) ; // } private static ExprTransform replaceAgg = new ExprTransformCopy() { @Override public Expr transform(ExprAggregator eAgg) { return eAgg.getAggVar() ; } } ; /** Decide whether an expression is safe for using a a graph substitution. * Need to be careful about value-like tests when the graph is not * matched in a value fashion. */ public static boolean isAssignmentSafeEquality(Expr expr) { return isAssignmentSafeEquality(expr, false, false) ; } /** * @param graphHasStringEquality True if the graph triple matching equates xsd:string and plain literal * @param graphHasNumercialValueEquality True if the graph triple matching equates numeric values */ public static boolean isAssignmentSafeEquality(Expr expr, boolean graphHasStringEquality, boolean graphHasNumercialValueEquality) { if ( !(expr instanceof E_Equals) && !(expr instanceof E_SameTerm) ) return false ; // Corner case: sameTerm is false for string/plain literal, // but true in the graph. ExprFunction2 eq = (ExprFunction2)expr ; Expr left = eq.getArg1() ; Expr right = eq.getArg2() ; Var var = null ; NodeValue constant = null ; if ( left.isVariable() && right.isConstant() ) { var = left.asVar() ; constant = right.getConstant() ; } else if ( right.isVariable() && left.isConstant() ) { var = right.asVar() ; constant = left.getConstant() ; } // Not between a variable and a constant if ( var == null || constant == null ) return false ; if ( ! constant.isLiteral() ) // URIs, bNodes. Any bNode will have come from a substitution - not legal syntax in filters return true ; if (expr instanceof E_SameTerm) { if ( graphHasStringEquality && constant.isString() ) // Graph is not same term return false ; if ( graphHasNumercialValueEquality && constant.isNumber() ) return false ; return true ; } // Final check for "=" where a FILTER = can do value matching when the graph does not. if ( expr instanceof E_Equals ) { if ( ! graphHasStringEquality && constant.isString() ) return false ; if ( ! graphHasNumercialValueEquality && constant.isNumber() ) return false ; return true ; } // Unreachable. throw new ARQInternalErrorException() ; } /** Some "functions" are non-deterministic (unstable) - * calling them with the same arguments * does not yields the same answer each time. * Therefore how and when they are called * matters. * * Functions: RAND, UUID, StrUUID, BNode * * NOW() is safe. */ public static boolean isStable(Expr expr) { try { Walker.walk(expr, exprVisitorCheckForNonFunctions) ; return true ; } catch ( ExprUnstable ex ) { return false ; } } private static ExprVisitor exprVisitorCheckForNonFunctions = new ExprVisitorBase() { @Override public void visit(ExprFunction0 func) { if ( func instanceof E_Random || func instanceof E_UUID || func instanceof E_StrUUID) throw new ExprUnstable() ; } @Override public void visit(ExprFunctionN func) { if (func instanceof E_BNode ) throw new ExprUnstable() ; } } ; private static class ExprUnstable extends ExprException { // Filling in the stack trace is the expensive part of // an exception but we don't need it. @Override public Throwable fillInStackTrace() { return this ; } } }