Package org.apache.marmotta.kiwi.sparql.persistence

Source Code of org.apache.marmotta.kiwi.sparql.persistence.KiWiSparqlConnection

/*
* 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.marmotta.kiwi.sparql.persistence;

import com.google.common.base.Preconditions;
import info.aduna.iteration.CloseableIteration;
import info.aduna.iteration.CloseableIteratorIteration;
import info.aduna.iteration.Iterations;
import org.apache.commons.lang3.StringUtils;
import org.apache.marmotta.commons.sesame.model.Namespaces;
import org.apache.marmotta.commons.util.DateUtils;
import org.apache.marmotta.kiwi.model.rdf.KiWiNode;
import org.apache.marmotta.kiwi.persistence.KiWiConnection;
import org.apache.marmotta.kiwi.persistence.util.ResultSetIteration;
import org.apache.marmotta.kiwi.persistence.util.ResultTransformerFunction;
import org.apache.marmotta.kiwi.sail.KiWiValueFactory;
import org.openrdf.model.BNode;
import org.openrdf.model.Literal;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.query.Binding;
import org.openrdf.query.BindingSet;
import org.openrdf.query.algebra.*;
import org.openrdf.query.impl.MapBindingSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.*;
import java.util.regex.Pattern;

/**
* Provide improved SPARQL support by evaluating certain common compley SPARQL constructs directly on the
* database (e.g. JOIN over pattern queries).
* <p/>
* Implemented using a decorator pattern (i.e. wrapping the KiWiConnection).
*
* @author Sebastian Schaffert (sschaffert@apache.org)
*/
public class KiWiSparqlConnection {

    private static Logger log = LoggerFactory.getLogger(KiWiSparqlConnection.class);

    private static DateFormat sqlDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");

    private KiWiConnection parent;
    private KiWiValueFactory valueFactory;

    private ExecutorService executorService;

    public KiWiSparqlConnection(KiWiConnection parent, KiWiValueFactory valueFactory) throws SQLException {
        this.parent = parent;
        this.valueFactory = valueFactory;

        // interruptible queries run in a separate thread
        this.executorService = Executors.newCachedThreadPool();
    }

    /**
     * Evaluate a statement pattern join or filter on the database by translating it into an appropriate SQL statement.
     * Copied and adapted from KiWiReasoningConnection.query()
     * @param join
     * @return
     */
    public CloseableIteration<BindingSet, SQLException> evaluateJoin(TupleExpr join, final BindingSet bindings) throws SQLException, InterruptedException {
        Preconditions.checkArgument(join instanceof Join || join instanceof Filter || join instanceof StatementPattern);

        // some definitions
        String[] positions = new String[] {"subject","predicate","object","context"};

        // collect all patterns in a list, using depth-first search over the join
        List<StatementPattern> patterns = new ArrayList<StatementPattern>();
        collectPatterns(join, patterns);

        // associate a name with each pattern; the names are used in the database query to refer to the triple
        // that matched this pattern and in the construction of variable names for the HQL query
        int patternCount = 0;
        final Map<StatementPattern,String> patternNames = new HashMap<StatementPattern, String>();
        for(StatementPattern p : patterns) {
            patternNames.put(p,"P"+ (++patternCount));
        }

        // find all variables occurring in the patterns and create a map to map them to
        // field names in the database query; each variable will have one or several field names,
        // one for each pattern it occurs in; field names are constructed automatically by a counter
        // and the pattern name to ensure the name is a valid HQL identifier
        int variableCount = 0;

        // a map for the variable names; will look like { ?x -> "V1", ?y -> "V2", ... }
        final Map<Var,String> variableNames = new HashMap<Var, String>();

        // a map for mapping variables to field names; each variable might have one or more field names,
        // depending on the number of patterns it occurs in; will look like
        // { ?x -> ["P1_V1", "P2_V1"], ?y -> ["P2_V2"], ... }
        Map<Var,List<String>> queryVariables = new HashMap<Var, List<String>>();
        for(StatementPattern p : patterns) {
            Var[] fields = new Var[] {
                    p.getSubjectVar(),
                    p.getPredicateVar(),
                    p.getObjectVar(),
                    p.getContextVar()
            };
            for(int i = 0; i<fields.length; i++) {
                if(fields[i] != null && !fields[i].hasValue()) {
                    Var v = fields[i];
                    if(variableNames.get(v) == null) {
                        variableNames.put(v,"V"+ (++variableCount));
                        queryVariables.put(v,new LinkedList<String>());
                    }
                    String pName = patternNames.get(p);
                    String vName = variableNames.get(v);
                    queryVariables.get(v).add(pName + "_" + positions[i] + "_" + vName);
                }
            }
        }

        // build the select clause by projecting for each query variable the first name
        StringBuilder selectClause = new StringBuilder();
        final List<Var> selectVariables = new LinkedList<Var>();
        for(Iterator<Var> it = queryVariables.keySet().iterator(); it.hasNext(); ) {
            Var v = it.next();
            String projectedName = variableNames.get(v);
            String fromName = queryVariables.get(v).get(0);
            selectClause.append(fromName);
            selectClause.append(".id as ");
            selectClause.append(projectedName);
            if(it.hasNext()) {
                selectClause.append(", ");
            }
            selectVariables.add(v);
        }


        // build the from-clause of the query; the from clause is constructed as follows:
        // 1. for each pattern P, there will be a "KiWiTriple P" in the from clause
        // 2. for each variable V in P occurring in
        //    - subject, there will be a "inner join P.subject as P_S_V" or "left outer join P.subject as P_S_V",
        //      depending on whether the "optional" parameter is false or true
        //    - property, there will be a "inner join P.property as P_P_V" or "left outer join p.property as P_P_V"
        //    - object, there will be a "inner join P.object as P_O_V" or "left outer join p.object as P_O_V"
        //    - context, there will be a "inner join P.context as P_C_V" or "left outer join p.context as P_C_V"
        StringBuilder fromClause = new StringBuilder();
        for(Iterator<StatementPattern> it = patterns.iterator(); it.hasNext(); ) {
            StatementPattern p = it.next();
            String pName = patternNames.get(p);
            fromClause.append("triples "+pName);

            Var[] fields = new Var[] {
                    p.getSubjectVar(),
                    p.getPredicateVar(),
                    p.getObjectVar(),
                    p.getContextVar()
            };
            for(int i = 0; i<fields.length; i++) {
                if(fields[i] != null && !fields[i].hasValue()) {
                    String vName = variableNames.get(fields[i]);
                    fromClause.append(" INNER JOIN nodes AS ");
                    fromClause.append(pName + "_"+positions[i]+"_" + vName);
                    fromClause.append(" ON " + pName + "." + positions[i] + " = ");
                    fromClause.append(pName + "_"+positions[i]+"_" + vName + ".id ");
                }
            }

            if(it.hasNext()) {
                fromClause.append(",\n ");
            }
        }


        // build the where clause as follows:
        // 1. iterate over all patterns and for each resource and literal field in subject,
        //    property, object, or context, and set a query condition according to the
        //    nodes given in the pattern
        // 2. for each variable that has more than one occurrences, add a join condition
        // 3. for each variable in the initialBindings, add a condition to the where clause

        // list of where conditions that will later be connected by AND
        List<String> whereConditions = new LinkedList<String>();


        // 1. iterate over all patterns and for each resource and literal field in subject,
        //    property, object, or context, and set a query condition according to the
        //    nodes given in the pattern
        for(StatementPattern p : patterns) {
            String pName = patternNames.get(p);
            Var[] fields = new Var[] {
                    p.getSubjectVar(),
                    p.getPredicateVar(),
                    p.getObjectVar(),
                    p.getContextVar()
            };
            for(int i = 0; i<fields.length; i++) {
                // find node id of the resource or literal field and use it in the where clause
                // in this way we can avoid setting too many query parameters
                Long nodeId = null;
                if(fields[i] != null && fields[i].hasValue()) {
                    Value v = valueFactory.convert(fields[i].getValue());
                    if(v instanceof KiWiNode) {
                        nodeId = ((KiWiNode) v).getId();
                    } else {
                        throw new IllegalArgumentException("the values in this query have not been created by the KiWi value factory");
                    }

                    if(nodeId != null) {
                        String condition = pName+"."+positions[i]+" = " + nodeId;
                        whereConditions.add(condition);
                    }
                }
            }
        }

        // 2. for each variable that has more than one occurrences, add a join condition
        for(Var v : queryVariables.keySet()) {
            List<String> vNames = queryVariables.get(v);
            for(int i = 1; i < vNames.size(); i++) {
                String vName1 = vNames.get(i-1);
                String vName2 = vNames.get(i);
                whereConditions.add(vName1 + ".id = " + vName2 + ".id");
            }
        }

        // 3. for each variable in the initialBindings, add a condition to the where clause setting it
        //    to the node given as binding
        if(bindings != null) {
            for(String v : bindings.getBindingNames()) {
                for(Map.Entry<Var,List<String>> entry : queryVariables.entrySet()) {
                    if(entry.getKey().getName() != null && entry.getKey().getName().equals(v) &&
                            entry.getValue() != null && entry.getValue().size() > 0) {
                        List<String> vNames = entry.getValue();
                        String vName = vNames.get(0);
                        Value binding = valueFactory.convert(bindings.getValue(v));
                        if(binding instanceof KiWiNode) {
                            whereConditions.add(vName+".id = "+((KiWiNode)binding).getId());
                        } else {
                            throw new IllegalArgumentException("the values in this binding have not been created by the KiWi value factory");
                        }
                    }
                }
            }
        }

        // 4. for each pattern, ensure that the matched triple is not marked as deleted
        for(StatementPattern p : patterns) {
            String pName = patternNames.get(p);
            whereConditions.add(pName+".deleted = false");
        }


        // 5. for each filter condition, add a statement to the where clause
        List<ValueExpr> filters = new ArrayList<ValueExpr>();
        collectFilters(join, filters);
        for(ValueExpr expr : filters) {
            whereConditions.add(evaluateExpression(expr,queryVariables, null));
        }


        // construct the where clause
        StringBuilder whereClause = new StringBuilder();
        for(Iterator<String> it = whereConditions.iterator(); it.hasNext(); ) {
            whereClause.append(it.next());
            whereClause.append("\n ");
            if(it.hasNext()) {
                whereClause.append("AND ");
            }
        }


        // build the query string
        String queryString =
                "SELECT " + selectClause + "\n " +
                        "FROM " + fromClause + "\n " +
                        "WHERE " + whereClause;

        log.debug("original SPARQL syntax tree:\n {}", join);
        log.debug("constructed SQL query string:\n {}",queryString);
        log.debug("SPARQL -> SQL variable mappings:\n {}", queryVariables);

        final PreparedStatement queryStatement = parent.getJDBCConnection().prepareStatement(queryString);
        if(parent.getDialect().isCursorSupported()) {
            queryStatement.setFetchSize(parent.getConfiguration().getCursorSize());
        }

        Future<ResultSet> queryFuture =
                executorService.submit(new Callable<ResultSet>() {
                    @Override
                    public ResultSet call() throws Exception {
                        try {
                            return queryStatement.executeQuery();
                        } catch (SQLException ex) {
                            if(Thread.interrupted()) {
                                log.info("SQL query execution cancelled; not returning result (Thread={})", Thread.currentThread());
                                throw new InterruptedException("SPARQL query execution cancelled");
                            } else {
                                throw ex;
                            }
                        }
                    }
                }
                );

        try {
            ResultSet result = queryFuture.get();

            ResultSetIteration<BindingSet> it = new ResultSetIteration<BindingSet>(result, true, new ResultTransformerFunction<BindingSet>() {
                @Override
                public BindingSet apply(ResultSet row) throws SQLException {
                    MapBindingSet resultRow = new MapBindingSet();

                    for(Var v : selectVariables) {
                        resultRow.addBinding(v.getName(), parent.loadNodeById(row.getLong(variableNames.get(v))));
                    }


                    if(bindings != null) {
                        for(Binding binding : bindings) {
                            resultRow.addBinding(binding);
                        }
                    }
                    return resultRow;
                }
            });

            // materialize result to avoid having more than one result set open at the same time
            return new CloseableIteratorIteration<BindingSet, SQLException>(Iterations.asList(it).iterator());
        } catch (InterruptedException | CancellationException e) {
            log.info("SPARQL query execution cancelled");
            queryFuture.cancel(true);
            queryStatement.cancel();
            queryStatement.close();

            throw new InterruptedException("SPARQL query execution cancelled");
        } catch (ExecutionException e) {
            log.error("error executing SPARQL query",e.getCause());
            if(e.getCause() instanceof SQLException) {
                throw (SQLException)e.getCause();
            } else if(e.getCause() instanceof InterruptedException) {
                throw (InterruptedException)e.getCause();
            } else {
                throw new SQLException("error executing SPARQL query",e);
            }
        }
    }

    private String evaluateExpression(ValueExpr expr, Map<Var, List<String>> queryVariables, OPTypes optype) {
        if(expr instanceof And) {
            return "(" + evaluateExpression(((And) expr).getLeftArg(), queryVariables, optype) + " AND " + evaluateExpression(((And) expr).getRightArg(),queryVariables, optype) + ")";
        } else if(expr instanceof Or) {
            return "(" + evaluateExpression(((Or) expr).getLeftArg(), queryVariables, optype) + " OR " + evaluateExpression(((Or) expr).getRightArg(),queryVariables, optype) + ")";
        } else if(expr instanceof Not) {
            return "NOT (" + evaluateExpression(((Not) expr).getArg(), queryVariables, optype+ ")";
        } else if(expr instanceof Str) {
            Str str = (Str)expr;
            // get value of argument and express it as string
            if(str.getArg() instanceof Var) {
                return queryVariables.get(str.getArg()).get(0) + ".svalue";
            } else if(str.getArg() instanceof ValueConstant) {
                return "'" + ((ValueConstant) str.getArg()).getValue().stringValue() + "'";
            }
        } else if(expr instanceof Label) {
            Label str = (Label)expr;
            // get value of argument and express it as string
            if(str.getArg() instanceof Var) {
                return queryVariables.get(str.getArg()).get(0) + ".svalue";
            } else if(str.getArg() instanceof ValueConstant) {
                return "'" + ((ValueConstant) str.getArg()).getValue().stringValue() + "'";
            }
        } else if(expr instanceof Lang) {
            Lang lang = (Lang)expr;

            if(lang.getArg() instanceof Var) {
                return queryVariables.get(lang.getArg()).get(0) + ".lang";
            }
        } else if(expr instanceof Compare) {
            Compare cmp = (Compare)expr;

            OPTypes ot = determineOpType(cmp.getLeftArg(), cmp.getRightArg());

            return evaluateExpression(cmp.getLeftArg(),queryVariables, ot) + getSQLOperator(cmp.getOperator()) + evaluateExpression(cmp.getRightArg(),queryVariables, ot);
        } else if(expr instanceof MathExpr) {
            MathExpr cmp = (MathExpr)expr;

            OPTypes ot = determineOpType(cmp.getLeftArg(), cmp.getRightArg());

            if(ot == OPTypes.STRING) {
                if(cmp.getOperator() == MathExpr.MathOp.PLUS) {
                    return parent.getDialect().getConcat(evaluateExpression(cmp.getLeftArg(),queryVariables, ot), evaluateExpression(cmp.getRightArg(),queryVariables, ot));
                } else {
                    throw new IllegalArgumentException("operation "+cmp.getOperator()+" is not supported on strings");
                }
            } else {
                return evaluateExpression(cmp.getLeftArg(),queryVariables, ot) + getSQLOperator(cmp.getOperator()) + evaluateExpression(cmp.getRightArg(),queryVariables, ot);
            }
        } else if(expr instanceof Regex) {
            Regex re = (Regex)expr;

            return optimizeRegexp(evaluateExpression(re.getArg(),queryVariables, optype), evaluateExpression(re.getPatternArg(), queryVariables, OPTypes.STRING));
        } else if(expr instanceof LangMatches) {
            LangMatches lm = (LangMatches)expr;
            String value = evaluateExpression(lm.getLeftArg(), queryVariables, optype);
            ValueConstant pattern = (ValueConstant) lm.getRightArg();

            if(pattern.getValue().stringValue().equals("*")) {
                return value + " LIKE '%'";
            } else if(pattern.getValue().stringValue().equals("")) {
                return value + " IS NULL";
            } else {
                return "(" + value + " = '"+pattern.getValue().stringValue()+"' OR " + parent.getDialect().getILike(value, "'" + pattern.getValue().stringValue() + "-%' )");
            }
        } else if(expr instanceof IsResource) {
            ValueExpr arg = ((UnaryValueOperator)expr).getArg();

            // operator must be a variable or a constant
            if(arg instanceof ValueConstant) {
                return Boolean.toString(((ValueConstant) arg).getValue() instanceof URI || ((ValueConstant) arg).getValue() instanceof BNode);
            } else if(arg instanceof Var) {
                String var = queryVariables.get(arg).get(0);

                return "(" + var + ".ntype = 'uri' OR " + var + ".ntype = 'bnode')";
            }
        } else if(expr instanceof IsURI) {
            ValueExpr arg = ((UnaryValueOperator)expr).getArg();

            // operator must be a variable or a constant
            if(arg instanceof ValueConstant) {
                return Boolean.toString(((ValueConstant) arg).getValue() instanceof URI);
            } else if(arg instanceof Var) {
                String var = queryVariables.get(arg).get(0);

                return var + ".ntype = 'uri'";
            }
        } else if(expr instanceof IsBNode) {
            ValueExpr arg = ((UnaryValueOperator)expr).getArg();

            // operator must be a variable or a constant
            if(arg instanceof ValueConstant) {
                return Boolean.toString(((ValueConstant) arg).getValue() instanceof BNode);
            } else if(arg instanceof Var) {
                String var = queryVariables.get(arg).get(0);

                return var + ".ntype = 'bnode'";
            }
        } else if(expr instanceof IsLiteral) {
            ValueExpr arg = ((UnaryValueOperator)expr).getArg();

            // operator must be a variable or a constant
            if(arg instanceof ValueConstant) {
                return Boolean.toString(((ValueConstant) arg).getValue() instanceof Literal);
            } else if(arg instanceof Var) {
                String var = queryVariables.get(arg).get(0);

                return "(" + var + ".ntype = 'string' OR " + var + ".ntype = 'int' OR " + var + ".ntype = 'double'  OR " + var + ".ntype = 'date'  OR " + var + ".ntype = 'boolean')";
            }
        } else if(expr instanceof Var) {
            String var = queryVariables.get(expr).get(0);

            if(optype == null) {
                return var + ".svalue";
            } else {
                switch (optype) {
                    case STRING: return var + ".svalue";
                    case INT:    return var + ".ivalue";
                    case DOUBLE: return var + ".dvalue";
                    case DATE:   return var + ".tvalue";
                    case ANY:    return var + ".id";
                }
            }
        } else if(expr instanceof ValueConstant) {
            String val = ((ValueConstant) expr).getValue().stringValue();

            if(optype == null) {
                return "'" + val + "'";
            } else {
                switch (optype) {
                    case STRING: return "'" + val + "'";
                    case INT:    return ""  + Integer.parseInt(val);
                    case DOUBLE: return ""  + Double.parseDouble(val);
                    case DATE:   return "'" + sqlDateFormat.format(DateUtils.parseDate(val)) + "'";
                    default: throw new IllegalArgumentException("unsupported value type: " + optype);
                }
            }
        }


        throw new IllegalArgumentException("unsupported value expression: "+expr);
    }


    /**
     * Collect all statement patterns in a tuple expression in depth-first order. The tuple expression may only
     * contain Join or StatementPattern expressions, otherwise an IllegalArgumentException is thrown.
     * @param expr
     * @param patterns
     */
    private void collectPatterns(TupleExpr expr, List<StatementPattern> patterns) {
        if(expr instanceof Join) {
            collectPatterns(((Join) expr).getLeftArg(), patterns);
            collectPatterns(((Join) expr).getRightArg(), patterns);
        } else if(expr instanceof Filter) {
            collectPatterns(((Filter) expr).getArg(), patterns);
        } else if(expr instanceof StatementPattern) {
            patterns.add((StatementPattern)expr);
        } else {
            throw new IllegalArgumentException("the tuple expression was neither a join nor a statement pattern: "+expr);
        }

    }


    /**
     * Collect all filter conditions in a tuple expression in depth-first order. The tuple expression may only
     * contain Join or StatementPattern expressions, otherwise an IllegalArgumentException is thrown.
     * @param expr
     * @param filters
     */
    private void collectFilters(TupleExpr expr, List<ValueExpr> filters) {
        if(expr instanceof Join) {
            collectFilters(((Join) expr).getLeftArg(), filters);
            collectFilters(((Join) expr).getRightArg(), filters);
        } else if(expr instanceof Filter) {
            filters.add(((Filter) expr).getCondition());
        } else if(expr instanceof StatementPattern) {
            // do nothing
        } else {
            throw new IllegalArgumentException("the tuple expression was neither a join nor a statement pattern: "+expr);
        }

    }


    private String getSQLOperator(Compare.CompareOp op) {
        switch (op) {
            case EQ: return " = ";
            case GE: return " >= ";
            case GT: return " > ";
            case LE: return " <= ";
            case LT: return " < ";
            case NE: return " <> ";
        }
        throw new IllegalArgumentException("unsupported operator type for comparison: "+op);
    }


    private String getSQLOperator(MathExpr.MathOp op) {
        switch (op) {
            case PLUS: return " + ";
            case MINUS: return " - ";
            case DIVIDE: return " / ";
            case MULTIPLY: return " / ";
        }
        throw new IllegalArgumentException("unsupported operator type for math expression: "+op);
    }



    private OPTypes determineOpType(ValueExpr expr) {
        if(expr instanceof ValueConstant) {
            if(((ValueConstant) expr).getValue() instanceof Literal) {
                Literal l = (Literal)((ValueConstant) expr).getValue();
                String type = l.getDatatype() != null ? l.getDatatype().stringValue() : null;

                if(StringUtils.equals(Namespaces.NS_XSD + "double", type)
                        || StringUtils.equals(Namespaces.NS_XSD + "float", type)
                        || StringUtils.equals(Namespaces.NS_XSD + "decimal", type)) {
                    return OPTypes.DOUBLE;
                } else if(StringUtils.equals(Namespaces.NS_XSD + "integer", type)
                        || StringUtils.equals(Namespaces.NS_XSD + "long", type)
                        || StringUtils.equals(Namespaces.NS_XSD + "int", type)
                        || StringUtils.equals(Namespaces.NS_XSD + "short", type)
                        || StringUtils.equals(Namespaces.NS_XSD + "nonNegativeInteger", type)
                        || StringUtils.equals(Namespaces.NS_XSD + "nonPositiveInteger", type)
                        || StringUtils.equals(Namespaces.NS_XSD + "negativeInteger", type)
                        || StringUtils.equals(Namespaces.NS_XSD + "positiveInteger", type)
                        || StringUtils.equals(Namespaces.NS_XSD + "unsignedLong", type)
                        || StringUtils.equals(Namespaces.NS_XSD + "unsignedShort", type)
                        || StringUtils.equals(Namespaces.NS_XSD + "byte", type)
                        || StringUtils.equals(Namespaces.NS_XSD + "unsignedByte", type)) {
                    return OPTypes.INT;
                } else if(StringUtils.equals(Namespaces.NS_XSD + "dateTime", type)
                        || StringUtils.equals(Namespaces.NS_XSD + "date", type)
                        || StringUtils.equals(Namespaces.NS_XSD + "time", type)) {
                    return OPTypes.DATE;
                } else {
                    return OPTypes.STRING;
                }
            } else {
                return OPTypes.STRING;
            }
        } else if(expr instanceof Str) {
            return OPTypes.STRING;
        } else if(expr instanceof Lang) {
            return OPTypes.STRING;
        } else if(expr instanceof LocalName) {
            return OPTypes.STRING;
        } else if(expr instanceof Label) {
            return OPTypes.STRING;
        } else if(expr instanceof MathExpr) {
            return determineOpType(((MathExpr) expr).getLeftArg(), ((MathExpr) expr).getRightArg());
        } else if(expr instanceof Var) {
            return OPTypes.ANY;
        } else {
            throw new IllegalArgumentException("unsupported expression: "+expr);
        }
    }

    private OPTypes determineOpType(ValueExpr expr1, ValueExpr expr2) {
        OPTypes left  = determineOpType(expr1);
        OPTypes right = determineOpType(expr2);

        if(left == OPTypes.ANY) {
            return right;
        } else if(right == OPTypes.ANY) {
            return left;
        } else if(left == right) {
            return left;
        } else if( (left == OPTypes.INT && right == OPTypes.DOUBLE) || (left == OPTypes.DOUBLE && right == OPTypes.INT)) {
            return OPTypes.DOUBLE;
        } else if( (left == OPTypes.STRING) || (right == OPTypes.STRING)) {
            return OPTypes.STRING;
        } else {
            throw new IllegalArgumentException("unsupported type coercion: " + left + " and " + right);
        }
    }


    /**
     * Test if the regular expression given in the pattern can be simplified to a LIKE SQL statement; these are
     * considerably more efficient to evaluate in most databases, so in case we can simplify, we return a LIKE.
     *
     * @param value
     * @param pattern
     * @return
     */
    private String optimizeRegexp(String value, String pattern) {
        String simplified = pattern;

        // apply simplifications

        // remove SQL quotes at beginning and end
        simplified = simplified.replaceFirst("^'","");
        simplified = simplified.replaceFirst("'$","");


        // remove .* at beginning and end, they are the default anyways
        simplified = simplified.replaceFirst("^\\.\\*","");
        simplified = simplified.replaceFirst("\\.\\*$","");

        // replace all occurrences of % with \% and _ with \_, as they are special characters in SQL
        simplified = simplified.replaceAll("%","\\%");
        simplified = simplified.replaceAll("_","\\_");

        // if pattern now does not start with a ^, we put a "%" in front
        if(!simplified.startsWith("^")) {
            simplified = "%" + simplified;
        } else {
            simplified = simplified.substring(1);
        }

        // if pattern does not end with a "$", we put a "%" at the end
        if(!simplified.endsWith("$")) {
            simplified = simplified + "%";
        } else {
            simplified = simplified.substring(0,simplified.length()-2);
        }

        // replace all non-escaped occurrences of .* with %
        simplified = simplified.replaceAll("(?<!\\\\)\\.\\*","%");

        // replace all non-escaped occurrences of .+ with _%
        simplified = simplified.replaceAll("(?<!\\\\)\\.\\+","_%");

        // the pattern is not simplifiable if the simplification still contains unescaped regular expression constructs
        Pattern notSimplifiable = Pattern.compile("(?<!\\\\)[\\.\\*\\+\\{\\}\\[\\]\\|]");

        if(notSimplifiable.matcher(simplified).find()) {
            return parent.getDialect().getRegexp(value, pattern);
        } else {
            return value + " LIKE '"+simplified+"'";
        }

    }


    private static enum OPTypes {
        STRING, DOUBLE, INT, DATE, ANY
    }
}
TOP

Related Classes of org.apache.marmotta.kiwi.sparql.persistence.KiWiSparqlConnection

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.