You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@marmotta.apache.org by ss...@apache.org on 2014/11/07 14:42:42 UTC

marmotta git commit: SPARQL: - support GROUP_CONCAT - support IF - implement SupportFinder as visitor

Repository: marmotta
Updated Branches:
  refs/heads/develop 9926a4e81 -> 60867dac7


SPARQL:
- support GROUP_CONCAT
- support IF
- implement SupportFinder as visitor


Project: http://git-wip-us.apache.org/repos/asf/marmotta/repo
Commit: http://git-wip-us.apache.org/repos/asf/marmotta/commit/60867dac
Tree: http://git-wip-us.apache.org/repos/asf/marmotta/tree/60867dac
Diff: http://git-wip-us.apache.org/repos/asf/marmotta/diff/60867dac

Branch: refs/heads/develop
Commit: 60867dac79460fb3399ce2860302cf15989243d6
Parents: 9926a4e
Author: Sebastian Schaffert <ss...@apache.org>
Authored: Fri Nov 7 14:43:21 2014 +0100
Committer: Sebastian Schaffert <ss...@apache.org>
Committed: Fri Nov 7 14:43:21 2014 +0100

----------------------------------------------------------------------
 .../kiwi/sparql/builder/SQLBuilder.java         |   4 +-
 .../sparql/builder/collect/OPTypeFinder.java    |  13 +-
 .../sparql/builder/collect/SupportedFinder.java | 161 ++++
 .../builder/eval/ExpressionEvaluator.java       | 752 -----------------
 .../builder/eval/ValueExpressionEvaluator.java  | 810 +++++++++++++++++++
 .../evaluation/KiWiEvaluationStrategyImpl.java  | 192 +----
 .../marmotta/kiwi/persistence/KiWiDialect.java  |   5 +
 .../marmotta/kiwi/persistence/h2/H2Dialect.java |  18 +
 .../kiwi/persistence/mysql/MySQLDialect.java    |  18 +
 .../persistence/pgsql/PostgreSQLDialect.java    |  18 +
 10 files changed, 1046 insertions(+), 945 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/marmotta/blob/60867dac/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLBuilder.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLBuilder.java b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLBuilder.java
index 99a3537..08b602c 100644
--- a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLBuilder.java
+++ b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLBuilder.java
@@ -23,7 +23,7 @@ import org.apache.marmotta.kiwi.model.rdf.KiWiNode;
 import org.apache.marmotta.kiwi.persistence.KiWiDialect;
 import org.apache.marmotta.kiwi.sail.KiWiValueFactory;
 import org.apache.marmotta.kiwi.sparql.builder.collect.*;
-import org.apache.marmotta.kiwi.sparql.builder.eval.ExpressionEvaluator;
+import org.apache.marmotta.kiwi.sparql.builder.eval.ValueExpressionEvaluator;
 import org.apache.marmotta.kiwi.sparql.builder.model.SQLAbstractSubquery;
 import org.apache.marmotta.kiwi.sparql.builder.model.SQLFragment;
 import org.apache.marmotta.kiwi.sparql.builder.model.SQLPattern;
@@ -807,7 +807,7 @@ public class SQLBuilder {
 
 
     private String evaluateExpression(ValueExpr expr, final OPTypes optype) {
-        return new ExpressionEvaluator(expr, this, optype).build();
+        return new ValueExpressionEvaluator(expr, this, optype).build();
     }
 
 

http://git-wip-us.apache.org/repos/asf/marmotta/blob/60867dac/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/collect/OPTypeFinder.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/collect/OPTypeFinder.java b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/collect/OPTypeFinder.java
index 5dc7938..47c0d5a 100644
--- a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/collect/OPTypeFinder.java
+++ b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/collect/OPTypeFinder.java
@@ -72,7 +72,7 @@ public class OPTypeFinder extends QueryModelVisitorBase<RuntimeException> {
                     || StringUtils.equals(Namespaces.NS_XSD + "time", type)) {
                 optypes.add(OPTypes.DATE);
             } else {
-                optypes.add(OPTypes.STRING);
+                optypes.add(OPTypes.ANY);
             }
         } else {
             optypes.add(OPTypes.STRING);
@@ -80,6 +80,12 @@ public class OPTypeFinder extends QueryModelVisitorBase<RuntimeException> {
     }
 
     @Override
+    public void meet(SameTerm node) throws RuntimeException {
+        optypes.add(OPTypes.BOOL);
+    }
+
+
+    @Override
     public void meet(Str node) throws RuntimeException {
         optypes.add(OPTypes.STRING);
     }
@@ -109,6 +115,11 @@ public class OPTypeFinder extends QueryModelVisitorBase<RuntimeException> {
         }
     }
 
+    @Override
+    public void meet(If node) throws RuntimeException {
+        node.getResult().visit(this);
+        node.getAlternative().visit(this);
+    }
 
     public OPTypes coerce() {
         OPTypes left = OPTypes.ANY;

http://git-wip-us.apache.org/repos/asf/marmotta/blob/60867dac/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/collect/SupportedFinder.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/collect/SupportedFinder.java b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/collect/SupportedFinder.java
new file mode 100644
index 0000000..02946a3
--- /dev/null
+++ b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/collect/SupportedFinder.java
@@ -0,0 +1,161 @@
+/*
+ * 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.builder.collect;
+
+import org.apache.marmotta.kiwi.persistence.KiWiDialect;
+import org.apache.marmotta.kiwi.sparql.function.NativeFunctionRegistry;
+import org.openrdf.query.algebra.*;
+import org.openrdf.query.algebra.helpers.QueryModelVisitorBase;
+
+/**
+ * Check if all constructs in the query are supported natively. Whenever you add a new construct to SQLBuilder
+ * or ValueExpressionEvaluator, it should be removed here.
+ *
+ * @author Sebastian Schaffert (sschaffert@apache.org)
+ */
+public class SupportedFinder extends QueryModelVisitorBase<RuntimeException> {
+
+    private boolean supported = true;
+    private KiWiDialect dialect;
+
+    public SupportedFinder(TupleExpr expr, KiWiDialect dialect) {
+        this.dialect = dialect;
+
+        expr.visit(this);
+    }
+
+    public SupportedFinder(ValueExpr expr, KiWiDialect dialect) {
+        this.dialect = dialect;
+
+        expr.visit(this);
+    }
+
+    public boolean isSupported() {
+        return supported;
+    }
+
+
+    @Override
+    public void meet(ArbitraryLengthPath node) throws RuntimeException {
+        supported = false;
+    }
+
+    @Override
+    public void meet(BindingSetAssignment node) throws RuntimeException {
+        supported = false;
+    }
+
+    @Override
+    public void meet(CompareAll node) throws RuntimeException {
+        supported = false;
+    }
+
+    @Override
+    public void meet(CompareAny node) throws RuntimeException {
+        supported = false;
+    }
+
+    @Override
+    public void meet(Count node) throws RuntimeException {
+        if(!dialect.isArraySupported()) {
+            supported = false;
+        } else {
+            super.meet(node);
+        }
+    }
+
+
+    @Override
+    public void meet(DescribeOperator node) throws RuntimeException {
+        supported = false;
+    }
+
+
+    @Override
+    public void meet(Difference node) throws RuntimeException {
+        supported = false;
+    }
+
+    @Override
+    public void meet(EmptySet node) throws RuntimeException {
+        supported = false;
+    }
+
+    @Override
+    public void meet(FunctionCall node) throws RuntimeException {
+        if(!isFunctionSupported(node)) {
+            supported = false;
+        } else {
+            super.meet(node);
+        }
+    }
+
+    @Override
+    public void meet(Intersection node) throws RuntimeException {
+        supported = false;
+    }
+
+
+
+    @Override
+    public void meet(MultiProjection node) throws RuntimeException {
+        supported = false;
+    }
+
+    @Override
+    public void meet(Sample node) throws RuntimeException {
+        supported = false;
+    }
+
+    @Override
+    public void meet(Service node) throws RuntimeException {
+        supported = false;
+    }
+
+    @Override
+    public void meet(ZeroLengthPath node) throws RuntimeException {
+        supported = false;
+    }
+
+    @Override
+    public void meet(ListMemberOperator node) throws RuntimeException {
+        supported = false;
+    }
+
+    /**
+     * All update expressions are not directly supported; however, their query parts should work fine!
+     */
+    @Override
+    protected void meetUpdateExpr(UpdateExpr node) throws RuntimeException {
+        supported = false;
+    }
+
+    private boolean isFunctionSupported(FunctionCall fc) {
+        return NativeFunctionRegistry.getInstance().get(fc.getURI()) != null && NativeFunctionRegistry.getInstance().get(fc.getURI()).isSupported(dialect);
+    }
+
+
+    private static boolean isAtomic(ValueExpr expr) {
+        return expr instanceof Var || expr instanceof ValueConstant;
+    }
+
+    private static boolean isConstant(ValueExpr expr) {
+        return expr instanceof ValueConstant;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/marmotta/blob/60867dac/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/eval/ExpressionEvaluator.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/eval/ExpressionEvaluator.java b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/eval/ExpressionEvaluator.java
deleted file mode 100644
index 8518070..0000000
--- a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/eval/ExpressionEvaluator.java
+++ /dev/null
@@ -1,752 +0,0 @@
-/*
- * 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.builder.eval;
-
-import com.google.common.base.Preconditions;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.marmotta.commons.collections.CollectionUtils;
-import org.apache.marmotta.commons.util.DateUtils;
-import org.apache.marmotta.kiwi.model.rdf.KiWiNode;
-import org.apache.marmotta.kiwi.sparql.builder.OPTypes;
-import org.apache.marmotta.kiwi.sparql.builder.ProjectionType;
-import org.apache.marmotta.kiwi.sparql.builder.SQLBuilder;
-import org.apache.marmotta.kiwi.sparql.builder.collect.OPTypeFinder;
-import org.apache.marmotta.kiwi.sparql.builder.model.SQLVariable;
-import org.apache.marmotta.kiwi.sparql.function.NativeFunction;
-import org.apache.marmotta.kiwi.sparql.function.NativeFunctionRegistry;
-import org.openrdf.model.BNode;
-import org.openrdf.model.Literal;
-import org.openrdf.model.URI;
-import org.openrdf.model.vocabulary.FN;
-import org.openrdf.model.vocabulary.XMLSchema;
-import org.openrdf.query.algebra.*;
-import org.openrdf.query.algebra.helpers.QueryModelVisitorBase;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.*;
-import java.util.regex.Pattern;
-
-/**
- * Evaluate a SPARQL ValueExpr by translating it into a SQL expression.
- *
- * @author Sebastian Schaffert (sschaffert@apache.org)
- */
-public class ExpressionEvaluator extends QueryModelVisitorBase<RuntimeException> {
-
-    private static Logger log = LoggerFactory.getLogger(ExpressionEvaluator.class);
-
-    /**
-     * Date format used for SQL timestamps.
-     */
-    private static final DateFormat sqlDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
-
-    /**
-     * Reference to the registry of natively supported functions with parameter and return types as well as SQL translation
-     */
-    private static NativeFunctionRegistry functionRegistry = NativeFunctionRegistry.getInstance();
-
-    // used by BNodeGenerator
-    private static Random anonIdGenerator = new Random();
-
-
-    private StringBuilder builder = new StringBuilder();
-
-    private Deque<OPTypes> optypes = new ArrayDeque<>();
-
-    private SQLBuilder parent;
-
-    public ExpressionEvaluator(ValueExpr expr, SQLBuilder parent) {
-        this(expr,parent, OPTypes.ANY);
-    }
-
-    public ExpressionEvaluator(ValueExpr expr, SQLBuilder parent, OPTypes optype) {
-        this.parent = parent;
-
-        optypes.push(optype);
-
-        if(log.isTraceEnabled()) {
-            long start = System.currentTimeMillis();
-            expr.visit(this);
-            log.trace("expression evaluated in {} ms", (System.currentTimeMillis()-start));
-        } else {
-            expr.visit(this);
-        }
-    }
-
-
-    /**
-     * Create the actual SQL string generated by this evaluator.
-     *
-     * @return
-     */
-    public String build() {
-        return builder.toString();
-    }
-
-    @Override
-    public void meet(And node) throws RuntimeException {
-        builder.append("(");
-        node.getLeftArg().visit(this);
-        builder.append(" AND ");
-        node.getRightArg().visit(this);
-        builder.append(")");
-    }
-
-    @Override
-    public void meet(Or node) throws RuntimeException {
-        builder.append("(");
-        node.getLeftArg().visit(this);
-        builder.append(" OR ");
-        node.getRightArg().visit(this);
-        builder.append(")");
-    }
-
-    @Override
-    public void meet(Not node) throws RuntimeException {
-        builder.append("NOT (");
-        node.getArg().visit(this);
-        builder.append(")");
-    }
-
-    @Override
-    public void meet(Exists node) throws RuntimeException {
-        // TODO: need to make sure that variables of the parent are visible in the subquery
-        //       - pattern names need to be unique even in subqueries
-        //       - variable lookup for expressions in the subquery need to refer to the parent
-        SQLBuilder sq_builder = new SQLBuilder(node.getSubQuery(), parent.getBindings(), parent.getDataset(), parent.getConverter(), parent.getDialect(), "_", Collections.EMPTY_SET, copyVariables(parent.getVariables()));
-
-        builder.append("EXISTS (").append(sq_builder.build()).append(")");
-    }
-
-    @Override
-    public void meet(FunctionCall fc) throws RuntimeException {
-        // special optimizations for frequent cases with variables
-        if((XMLSchema.DOUBLE.toString().equals(fc.getURI()) || XMLSchema.FLOAT.toString().equals(fc.getURI()) ) && fc.getArgs().size() == 1) {
-            optypes.push(OPTypes.DOUBLE);
-            fc.getArgs().get(0).visit(this);
-            optypes.pop();
-        } else if((XMLSchema.INTEGER.toString().equals(fc.getURI()) || XMLSchema.INT.toString().equals(fc.getURI())) && fc.getArgs().size() == 1) {
-            optypes.push(OPTypes.INT);
-            fc.getArgs().get(0).visit(this);
-            optypes.pop();
-        } else if(XMLSchema.BOOLEAN.toString().equals(fc.getURI()) && fc.getArgs().size() == 1) {
-            optypes.push(OPTypes.BOOL);
-            fc.getArgs().get(0).visit(this);
-            optypes.pop();
-        } else if(XMLSchema.DATE.toString().equals(fc.getURI()) && fc.getArgs().size() == 1) {
-            optypes.push(OPTypes.DATE);
-            fc.getArgs().get(0).visit(this);
-            optypes.pop();
-        } else {
-
-            String fnUri = fc.getURI();
-
-            String[] args = new String[fc.getArgs().size()];
-
-            NativeFunction nf = functionRegistry.get(fnUri);
-
-            if (nf != null && nf.isSupported(parent.getDialect())) {
-
-                for (int i = 0; i < args.length; i++) {
-                    args[i] = new ExpressionEvaluator(fc.getArgs().get(i), parent, nf.getArgumentType(i)).build();
-                }
-
-                if (optypes.peek() != nf.getReturnType()) {
-                    builder.append(castExpression(nf.getNative(parent.getDialect(), args), optypes.peek()));
-                } else {
-                    builder.append(nf.getNative(parent.getDialect(), args));
-                }
-            } else {
-                throw new IllegalArgumentException("the function " + fnUri + " is not supported by the SQL translation");
-            }
-        }
-
-    }
-
-    @Override
-    public void meet(Avg node) throws RuntimeException {
-        builder.append("AVG(");
-        optypes.push(OPTypes.DOUBLE);
-        node.getArg().visit(this);
-        optypes.pop();
-        builder.append(")");
-    }
-
-    @Override
-    public void meet(BNodeGenerator gen) throws RuntimeException {
-        if(gen.getNodeIdExpr() != null) {
-            // get value of argument and express it as string
-            optypes.push(OPTypes.STRING);
-            gen.getNodeIdExpr().visit(this);
-            optypes.pop();
-        } else {
-            builder.append("'").append(Long.toHexString(System.currentTimeMillis())+Integer.toHexString(anonIdGenerator.nextInt(1000))).append("'");
-        }
-    }
-
-    @Override
-    public void meet(Bound node) throws RuntimeException {
-        ValueExpr arg = node.getArg();
-
-        if(arg instanceof ValueConstant) {
-            builder.append(Boolean.toString(true));
-        } else if(arg instanceof Var) {
-            builder.append("(");
-            arg.visit(this);
-            builder.append(" IS NOT NULL)");
-        }
-    }
-
-    @Override
-    public void meet(Coalesce node) throws RuntimeException {
-        builder.append("COALESCE(");
-        for(Iterator<ValueExpr> it = node.getArguments().iterator(); it.hasNext(); ) {
-            it.next().visit(this);
-            if(it.hasNext()) {
-                builder.append(", ");
-            }
-        }
-        builder.append(")");
-    }
-
-    @Override
-    public void meet(Compare cmp) throws RuntimeException {
-        optypes.push(new OPTypeFinder(cmp).coerce());
-        cmp.getLeftArg().visit(this);
-        builder.append(getSQLOperator(cmp.getOperator()));
-        cmp.getRightArg().visit(this);
-        optypes.pop();
-    }
-
-    @Override
-    public void meet(Count node) throws RuntimeException {
-        builder.append("COUNT(");
-
-        if(node.isDistinct()) {
-            builder.append("DISTINCT ");
-        }
-
-        if(node.getArg() == null) {
-            // this is a weird special case where we need to expand to all variables selected in the query wrapped
-            // by the group; we cannot simply use "*" because the concept of variables is a different one in SQL,
-            // so instead we construct an ARRAY of the bindings of all variables
-
-            List<String> countVariables = new ArrayList<>();
-            for(SQLVariable v : parent.getVariables().values()) {
-                if(v.getProjectionType() == ProjectionType.NONE) {
-                    Preconditions.checkState(v.getExpressions().size() > 0, "no expressions available for variable");
-
-                    countVariables.add(v.getExpressions().get(0));
-                }
-            }
-            builder.append("ARRAY[");
-            builder.append(CollectionUtils.fold(countVariables,","));
-            builder.append("]");
-
-        } else {
-            optypes.push(OPTypes.ANY);
-            node.getArg().visit(this);
-            optypes.pop();
-        }
-        builder.append(")");
-    }
-
-    @Override
-    public void meet(IsBNode node) throws RuntimeException {
-        ValueExpr arg = node.getArg();
-
-        // operator must be a variable or a constant
-        if(arg instanceof ValueConstant) {
-            builder.append(Boolean.toString(((ValueConstant) arg).getValue() instanceof BNode));
-        } else if(arg instanceof Var) {
-            String var = getVariableAlias((Var) arg);
-
-            builder.append(var).append(".ntype = 'bnode'");
-        }
-    }
-
-    @Override
-    public void meet(IsLiteral node) throws RuntimeException {
-        ValueExpr arg = node.getArg();
-
-        // operator must be a variable or a constant
-        if (arg instanceof ValueConstant) {
-            builder.append(Boolean.toString(((ValueConstant) arg).getValue() instanceof Literal));
-        } else if(arg instanceof Var) {
-            String var = getVariableAlias((Var) arg);
-
-            Preconditions.checkState(var != null, "no alias available for variable");
-
-            builder.append("(")
-                    .append(var)
-                    .append(".ntype = 'string' OR ")
-                    .append(var)
-                    .append(".ntype = 'int' OR ")
-                    .append(var)
-                    .append(".ntype = 'double' OR ")
-                    .append(var)
-                    .append(".ntype = 'date' OR ")
-                    .append(var)
-                    .append(".ntype = 'boolean')");
-        }
-    }
-
-    @Override
-    public void meet(IsNumeric node) throws RuntimeException {
-        ValueExpr arg = node.getArg();
-
-        // operator must be a variable or a constant
-        if (arg instanceof ValueConstant) {
-            try {
-                Double.parseDouble(((ValueConstant) arg).getValue().stringValue());
-                builder.append(Boolean.toString(true));
-            } catch (NumberFormatException ex) {
-                builder.append(Boolean.toString(false));
-            }
-        } else if(arg instanceof Var) {
-            String var = getVariableAlias((Var) arg);
-
-            Preconditions.checkState(var != null, "no alias available for variable");
-
-            builder.append("(")
-                    .append(var)
-                    .append(".ntype = 'int' OR ")
-                    .append(var)
-                    .append(".ntype = 'double')");
-        }
-    }
-
-    @Override
-    public void meet(IsResource node) throws RuntimeException {
-        ValueExpr arg = node.getArg();
-
-        // operator must be a variable or a constant
-        if(arg instanceof ValueConstant) {
-            builder.append(Boolean.toString(((ValueConstant) arg).getValue() instanceof URI || ((ValueConstant) arg).getValue() instanceof BNode));
-        } else if(arg instanceof Var) {
-            String var = getVariableAlias((Var) arg);
-
-            Preconditions.checkState(var != null, "no alias available for variable");
-
-            builder .append("(")
-                    .append(var)
-                    .append(".ntype = 'uri' OR ")
-                    .append(var)
-                    .append(".ntype = 'bnode')");
-        }
-    }
-
-    @Override
-    public void meet(IsURI node) throws RuntimeException {
-        ValueExpr arg = node.getArg();
-
-        // operator must be a variable or a constant
-        if(arg instanceof ValueConstant) {
-            builder.append(Boolean.toString(((ValueConstant) arg).getValue() instanceof URI));
-        } else if(arg instanceof Var) {
-            String var = getVariableAlias((Var) arg);
-
-            Preconditions.checkState(var != null, "no alias available for variable");
-
-            builder.append(var).append(".ntype = 'uri'");
-        }
-    }
-
-    @Override
-    public void meet(IRIFunction fun) throws RuntimeException {
-        if(fun.getBaseURI() != null) {
-
-            String ex = new ExpressionEvaluator(fun.getArg(), parent, OPTypes.STRING).build();
-
-            builder
-                    .append("CASE WHEN position(':' IN ").append(ex).append(") > 0 THEN ").append(ex)
-                    .append(" ELSE ").append(functionRegistry.get(FN.CONCAT.stringValue()).getNative(parent.getDialect(), "'" + fun.getBaseURI() + "'", ex))
-                    .append(" END ");
-        } else {
-            // get value of argument and express it as string
-            optypes.push(OPTypes.STRING);
-            fun.getArg().visit(this);
-            optypes.pop();
-        }
-    }
-
-    @Override
-    public void meet(Label node) throws RuntimeException {
-        optypes.push(OPTypes.STRING);
-        node.getArg().visit(this);
-        optypes.pop();
-    }
-
-    @Override
-    public void meet(Lang lang) throws RuntimeException {
-        if(lang.getArg() instanceof Var) {
-            String var = getVariableAlias((Var) lang.getArg());
-            Preconditions.checkState(var != null, "no alias available for variable");
-
-            builder.append(var);
-            builder.append(".lang");
-        }
-    }
-
-    @Override
-    public void meet(LangMatches lm) throws RuntimeException {
-        ValueConstant pattern = (ValueConstant) lm.getRightArg();
-
-        if(pattern.getValue().stringValue().equals("*")) {
-            lm.getLeftArg().visit(this);
-            builder.append(" LIKE '%'");
-        } else if(pattern.getValue().stringValue().equals("")) {
-            lm.getLeftArg().visit(this);
-            builder.append(" IS NULL");
-        } else {
-            builder.append("(");
-            lm.getLeftArg().visit(this);
-            builder.append(" = '");
-            builder.append(pattern.getValue().stringValue().toLowerCase());
-            builder.append("' OR ");
-            lm.getLeftArg().visit(this);
-            builder.append(" LIKE '");
-            builder.append(pattern.getValue().stringValue().toLowerCase());
-            builder.append("-%' )");
-        }
-    }
-
-    @Override
-    public void meet(LocalName node) throws RuntimeException {
-        super.meet(node);
-    }
-
-    @Override
-    public void meet(MathExpr expr) throws RuntimeException {
-        OPTypes ot = new OPTypeFinder(expr).coerce();
-
-        if(ot == OPTypes.STRING) {
-            if(expr.getOperator() == MathExpr.MathOp.PLUS) {
-                builder.append(functionRegistry.get(FN.CONCAT.stringValue()).getNative(parent.getDialect(),new ExpressionEvaluator(expr.getLeftArg(), parent, ot).build(), new ExpressionEvaluator(expr.getRightArg(), parent, ot).build()));
-            } else {
-                throw new IllegalArgumentException("operation "+expr.getOperator()+" is not supported on strings");
-            }
-        } else {
-            if(ot == OPTypes.ANY || ot == OPTypes.TERM) {
-                ot = OPTypes.DOUBLE;
-            }
-
-            optypes.push(ot);
-            expr.getLeftArg().visit(this);
-            builder.append(getSQLOperator(expr.getOperator()));
-            expr.getRightArg().visit(this);
-            optypes.pop();
-        }
-    }
-
-    @Override
-    public void meet(Max node) throws RuntimeException {
-        builder.append("MAX(");
-        optypes.push(OPTypes.DOUBLE);
-        node.getArg().visit(this);
-        optypes.pop();
-        builder.append(")");
-    }
-
-    @Override
-    public void meet(Min node) throws RuntimeException {
-        builder.append("MIN(");
-        optypes.push(OPTypes.DOUBLE);
-        node.getArg().visit(this);
-        optypes.pop();
-        builder.append(")");
-    }
-
-    @Override
-    public void meet(Regex re) throws RuntimeException {
-        builder.append(optimizeRegexp(
-                new ExpressionEvaluator(re.getArg(), parent, OPTypes.STRING).build(),
-                new ExpressionEvaluator(re.getPatternArg(), parent, OPTypes.STRING).build(),
-                re.getFlagsArg()
-        ));
-    }
-
-    @Override
-    public void meet(SameTerm cmp) throws RuntimeException {
-        // covered by value binding in variables
-        optypes.push(OPTypes.TERM);
-        cmp.getLeftArg().visit(this);
-        builder.append(" = ");
-        cmp.getRightArg().visit(this);
-        optypes.pop();
-    }
-
-    @Override
-    public void meet(Str node) throws RuntimeException {
-        optypes.push(OPTypes.STRING);
-        node.getArg().visit(this);
-        optypes.pop();
-    }
-
-    @Override
-    public void meet(Sum node) throws RuntimeException {
-        builder.append("SUM(");
-        optypes.push(OPTypes.DOUBLE);
-        node.getArg().visit(this);
-        optypes.pop();
-        builder.append(")");
-    }
-
-    @Override
-    public void meet(Var node) throws RuntimeException {
-        // distinguish between the case where the variable is plain and the variable is bound
-        SQLVariable sv = parent.getVariables().get(node.getName());
-
-        if(sv == null) {
-            builder.append("NULL");
-        } else if(sv.getBindings().size() > 0) {
-            // in case the variable is actually an alias for an expression, we evaluate that expression instead, effectively replacing the
-            // variable occurrence with its value
-            sv.getBindings().get(0).visit(this);
-        } else {
-            String var = sv.getAlias();
-
-            if(sv.getProjectionType() != ProjectionType.NODE && sv.getProjectionType() != ProjectionType.NONE) {
-                // in case the variable represents a constructed or bound value instead of a node, we need to
-                // use the SQL expression as value; SQL should take care of proper casting...
-                // TODO: explicit casting needed?
-                builder.append(sv.getExpressions().get(0));
-            } else {
-                // in case the variable represents an entry from the NODES table (i.e. has been bound to a node
-                // in the database, we take the NODES alias and resolve to the correct column according to the
-                // operator type
-                switch (optypes.peek()) {
-                    case STRING:
-                        Preconditions.checkState(var != null, "no alias available for variable");
-                        builder.append(var).append(".svalue");
-                        break;
-                    case INT:
-                        Preconditions.checkState(var != null, "no alias available for variable");
-                        builder.append(var).append(".ivalue");
-                        break;
-                    case DOUBLE:
-                        Preconditions.checkState(var != null, "no alias available for variable");
-                        builder.append(var).append(".dvalue");
-                        break;
-                    case DATE:
-                        Preconditions.checkState(var != null, "no alias available for variable");
-                        builder.append(var).append(".tvalue");
-                        break;
-                    case VALUE:
-                        Preconditions.checkState(var != null, "no alias available for variable");
-                        builder.append(var).append(".svalue");
-                        break;
-                    case URI:
-                        Preconditions.checkState(var != null, "no alias available for variable");
-                        builder.append(var).append(".svalue");
-                        break;
-                    case TERM:
-                    case ANY:
-                        if(sv.getExpressions().size() > 0) {
-                            // this allows us to avoid joins with the nodes table for simple expressions that only need the ID
-                            builder.append(sv.getExpressions().get(0));
-                        } else {
-                            Preconditions.checkState(var != null, "no alias available for variable");
-                            builder.append(var).append(".id");
-                        }
-                        break;
-                }
-            }
-        }
-    }
-
-    @Override
-    public void meet(ValueConstant node) throws RuntimeException {
-        String val = node.getValue().stringValue();
-
-            switch (optypes.peek()) {
-                case STRING:
-                case VALUE:
-                case URI:
-                    builder.append("'").append(val).append("'");
-                    break;
-                case INT:
-                    builder.append(Integer.parseInt(val));
-                    break;
-                case DOUBLE:
-                    builder.append(Double.parseDouble(val));
-                    break;
-                case DATE:
-                    builder.append("'").append(sqlDateFormat.format(DateUtils.parseDate(val))).append("'");
-                    break;
-
-                // in this case we should return a node ID and also need to make sure it actually exists
-                case TERM:
-                case ANY:
-                    KiWiNode n = parent.getConverter().convert(node.getValue());
-                    builder.append(n.getId());
-                    break;
-
-                default: throw new IllegalArgumentException("unsupported value type: " + optypes.peek());
-            }
-    }
-
-    private String getVariableAlias(Var var) {
-        return parent.getVariables().get(var.getName()).getAlias();
-    }
-
-
-    private String getVariableAlias(String varName) {
-        return parent.getVariables().get(varName).getAlias();
-    }
-
-    /**
-     * Copy variables from the set to a new set suitable for a subquery; this allows passing over variable expressions
-     * from parent queries to subqueries without the subquery adding expressions that are then not visible outside
-     * @param variables
-     * @return
-     */
-    private static Map<String, SQLVariable> copyVariables(Map<String, SQLVariable> variables) {
-        Map<String,SQLVariable> copy = new HashMap<>();
-        try {
-            for(Map.Entry<String,SQLVariable> entry : variables.entrySet()) {
-                copy.put(entry.getKey(), (SQLVariable) entry.getValue().clone());
-            }
-        } catch (CloneNotSupportedException e) {
-            log.error("could not clone SQL variable:",e);
-        }
-
-        return copy;
-    }
-
-    private String castExpression(String arg, OPTypes type) {
-        if(type == null) {
-            return arg;
-        }
-
-        switch (type) {
-            case DOUBLE:
-                return functionRegistry.get(XMLSchema.DOUBLE).getNative(parent.getDialect(), arg);
-            case INT:
-                return functionRegistry.get(XMLSchema.INTEGER).getNative(parent.getDialect(), arg);
-            case BOOL:
-                return functionRegistry.get(XMLSchema.BOOLEAN).getNative(parent.getDialect(), arg);
-            case DATE:
-                return functionRegistry.get(XMLSchema.DATETIME).getNative(parent.getDialect(), arg);
-            case VALUE:
-                return arg;
-            case ANY:
-                return arg;
-            default:
-                return arg;
-        }
-    }
-
-    private static 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 static 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);
-    }
-
-    /**
-     * 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, ValueExpr flags) {
-        String _flags = flags != null && flags instanceof ValueConstant ? ((ValueConstant)flags).getValue().stringValue() : null;
-
-        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()-1);
-        }
-
-        // 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, _flags);
-        } else {
-            if(!simplified.startsWith("%") && !simplified.endsWith("%")) {
-                if(StringUtils.containsIgnoreCase(_flags, "i")) {
-                    return String.format("lower(%s) = lower('%s')", value, simplified);
-                } else {
-                    return String.format("%s = '%s'", value, simplified);
-                }
-            } else {
-                if(StringUtils.containsIgnoreCase(_flags,"i")) {
-                    return parent.getDialect().getILike(value, "'" + simplified + "'");
-                } else {
-                    return value + " LIKE '"+simplified+"'";
-                }
-            }
-        }
-
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/marmotta/blob/60867dac/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/eval/ValueExpressionEvaluator.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/eval/ValueExpressionEvaluator.java b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/eval/ValueExpressionEvaluator.java
new file mode 100644
index 0000000..4edf154
--- /dev/null
+++ b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/eval/ValueExpressionEvaluator.java
@@ -0,0 +1,810 @@
+/*
+ * 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.builder.eval;
+
+import com.google.common.base.Preconditions;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.marmotta.commons.collections.CollectionUtils;
+import org.apache.marmotta.commons.util.DateUtils;
+import org.apache.marmotta.kiwi.model.rdf.KiWiNode;
+import org.apache.marmotta.kiwi.sparql.builder.OPTypes;
+import org.apache.marmotta.kiwi.sparql.builder.ProjectionType;
+import org.apache.marmotta.kiwi.sparql.builder.SQLBuilder;
+import org.apache.marmotta.kiwi.sparql.builder.collect.OPTypeFinder;
+import org.apache.marmotta.kiwi.sparql.builder.model.SQLVariable;
+import org.apache.marmotta.kiwi.sparql.function.NativeFunction;
+import org.apache.marmotta.kiwi.sparql.function.NativeFunctionRegistry;
+import org.openrdf.model.BNode;
+import org.openrdf.model.Literal;
+import org.openrdf.model.URI;
+import org.openrdf.model.vocabulary.FN;
+import org.openrdf.model.vocabulary.XMLSchema;
+import org.openrdf.query.algebra.*;
+import org.openrdf.query.algebra.helpers.QueryModelVisitorBase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.regex.Pattern;
+
+/**
+ * Evaluate a SPARQL ValueExpr by translating it into a SQL expression.
+ *
+ * @author Sebastian Schaffert (sschaffert@apache.org)
+ */
+public class ValueExpressionEvaluator extends QueryModelVisitorBase<RuntimeException> {
+
+    private static Logger log = LoggerFactory.getLogger(ValueExpressionEvaluator.class);
+
+    /**
+     * Date format used for SQL timestamps.
+     */
+    private static final DateFormat sqlDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
+
+    /**
+     * Reference to the registry of natively supported functions with parameter and return types as well as SQL translation
+     */
+    private static NativeFunctionRegistry functionRegistry = NativeFunctionRegistry.getInstance();
+
+    // used by BNodeGenerator
+    private static Random anonIdGenerator = new Random();
+
+
+    private StringBuilder builder = new StringBuilder();
+
+    private Deque<OPTypes> optypes = new ArrayDeque<>();
+
+    private SQLBuilder parent;
+
+    public ValueExpressionEvaluator(ValueExpr expr, SQLBuilder parent) {
+        this(expr,parent, OPTypes.ANY);
+    }
+
+    public ValueExpressionEvaluator(ValueExpr expr, SQLBuilder parent, OPTypes optype) {
+        this.parent = parent;
+
+        optypes.push(optype);
+
+        if(log.isTraceEnabled()) {
+            long start = System.currentTimeMillis();
+            expr.visit(this);
+            log.trace("expression evaluated in {} ms", (System.currentTimeMillis()-start));
+        } else {
+            expr.visit(this);
+        }
+    }
+
+
+    /**
+     * Create the actual SQL string generated by this evaluator.
+     *
+     * @return
+     */
+    public String build() {
+        return builder.toString();
+    }
+
+    @Override
+    public void meet(And node) throws RuntimeException {
+        builder.append("(");
+        node.getLeftArg().visit(this);
+        builder.append(" AND ");
+        node.getRightArg().visit(this);
+        builder.append(")");
+    }
+
+    @Override
+    public void meet(Or node) throws RuntimeException {
+        builder.append("(");
+        node.getLeftArg().visit(this);
+        builder.append(" OR ");
+        node.getRightArg().visit(this);
+        builder.append(")");
+    }
+
+    @Override
+    public void meet(Not node) throws RuntimeException {
+        builder.append("NOT (");
+        node.getArg().visit(this);
+        builder.append(")");
+    }
+
+    @Override
+    public void meet(Exists node) throws RuntimeException {
+        // TODO: need to make sure that variables of the parent are visible in the subquery
+        //       - pattern names need to be unique even in subqueries
+        //       - variable lookup for expressions in the subquery need to refer to the parent
+        SQLBuilder sq_builder = new SQLBuilder(node.getSubQuery(), parent.getBindings(), parent.getDataset(), parent.getConverter(), parent.getDialect(), "_", Collections.EMPTY_SET, copyVariables(parent.getVariables()));
+
+        builder.append("EXISTS (").append(sq_builder.build()).append(")");
+    }
+
+    @Override
+    public void meet(FunctionCall fc) throws RuntimeException {
+        // special optimizations for frequent cases with variables
+        if((XMLSchema.DOUBLE.toString().equals(fc.getURI()) || XMLSchema.FLOAT.toString().equals(fc.getURI()) ) && fc.getArgs().size() == 1) {
+            optypes.push(OPTypes.DOUBLE);
+            fc.getArgs().get(0).visit(this);
+            optypes.pop();
+        } else if((XMLSchema.INTEGER.toString().equals(fc.getURI()) || XMLSchema.INT.toString().equals(fc.getURI())) && fc.getArgs().size() == 1) {
+            optypes.push(OPTypes.INT);
+            fc.getArgs().get(0).visit(this);
+            optypes.pop();
+        } else if(XMLSchema.BOOLEAN.toString().equals(fc.getURI()) && fc.getArgs().size() == 1) {
+            optypes.push(OPTypes.BOOL);
+            fc.getArgs().get(0).visit(this);
+            optypes.pop();
+        } else if(XMLSchema.DATE.toString().equals(fc.getURI()) && fc.getArgs().size() == 1) {
+            optypes.push(OPTypes.DATE);
+            fc.getArgs().get(0).visit(this);
+            optypes.pop();
+        } else {
+
+            String fnUri = fc.getURI();
+
+            String[] args = new String[fc.getArgs().size()];
+
+            NativeFunction nf = functionRegistry.get(fnUri);
+
+            if (nf != null && nf.isSupported(parent.getDialect())) {
+
+                for (int i = 0; i < args.length; i++) {
+                    args[i] = new ValueExpressionEvaluator(fc.getArgs().get(i), parent, nf.getArgumentType(i)).build();
+                }
+
+                if (optypes.peek() != nf.getReturnType()) {
+                    builder.append(castExpression(nf.getNative(parent.getDialect(), args), optypes.peek()));
+                } else {
+                    builder.append(nf.getNative(parent.getDialect(), args));
+                }
+            } else {
+                throw new IllegalArgumentException("the function " + fnUri + " is not supported by the SQL translation");
+            }
+        }
+
+    }
+
+    @Override
+    public void meet(Avg node) throws RuntimeException {
+        builder.append("AVG(");
+        optypes.push(OPTypes.DOUBLE);
+        node.getArg().visit(this);
+        optypes.pop();
+        builder.append(")");
+    }
+
+    @Override
+    public void meet(BNodeGenerator gen) throws RuntimeException {
+        if(gen.getNodeIdExpr() != null) {
+            // get value of argument and express it as string
+            optypes.push(OPTypes.STRING);
+            gen.getNodeIdExpr().visit(this);
+            optypes.pop();
+        } else {
+            builder.append("'").append(Long.toHexString(System.currentTimeMillis())+Integer.toHexString(anonIdGenerator.nextInt(1000))).append("'");
+        }
+    }
+
+    @Override
+    public void meet(Bound node) throws RuntimeException {
+        ValueExpr arg = node.getArg();
+
+        if(arg instanceof ValueConstant) {
+            builder.append(Boolean.toString(true));
+        } else if(arg instanceof Var) {
+            builder.append("(");
+            optypes.push(OPTypes.ANY);
+            arg.visit(this);
+            optypes.pop();
+            builder.append(" IS NOT NULL)");
+        }
+    }
+
+    @Override
+    public void meet(Coalesce node) throws RuntimeException {
+        builder.append("COALESCE(");
+        for(Iterator<ValueExpr> it = node.getArguments().iterator(); it.hasNext(); ) {
+            it.next().visit(this);
+            if(it.hasNext()) {
+                builder.append(", ");
+            }
+        }
+        builder.append(")");
+    }
+
+    @Override
+    public void meet(Compare cmp) throws RuntimeException {
+        optypes.push(new OPTypeFinder(cmp).coerce());
+        cmp.getLeftArg().visit(this);
+        builder.append(getSQLOperator(cmp.getOperator()));
+        cmp.getRightArg().visit(this);
+        optypes.pop();
+    }
+
+    @Override
+    public void meet(Count node) throws RuntimeException {
+        builder.append("COUNT(");
+
+        if(node.isDistinct()) {
+            builder.append("DISTINCT ");
+        }
+
+        if(node.getArg() == null) {
+            // this is a weird special case where we need to expand to all variables selected in the query wrapped
+            // by the group; we cannot simply use "*" because the concept of variables is a different one in SQL,
+            // so instead we construct an ARRAY of the bindings of all variables
+
+            List<String> countVariables = new ArrayList<>();
+            for(SQLVariable v : parent.getVariables().values()) {
+                if(v.getProjectionType() == ProjectionType.NONE) {
+                    Preconditions.checkState(v.getExpressions().size() > 0, "no expressions available for variable");
+
+                    countVariables.add(v.getExpressions().get(0));
+                }
+            }
+            builder.append("ARRAY[");
+            builder.append(CollectionUtils.fold(countVariables,","));
+            builder.append("]");
+
+        } else {
+            optypes.push(OPTypes.ANY);
+            node.getArg().visit(this);
+            optypes.pop();
+        }
+        builder.append(")");
+    }
+
+
+    @Override
+    public void meet(GroupConcat node) throws RuntimeException {
+        if(node.getSeparator() == null) {
+            builder.append(parent.getDialect().getGroupConcat(new ValueExpressionEvaluator(node.getArg(), parent, OPTypes.STRING).build(), null, node.isDistinct()));
+        } else {
+            builder.append(parent.getDialect().getGroupConcat(
+                    new ValueExpressionEvaluator(node.getArg(), parent, OPTypes.STRING).build(),
+                    new ValueExpressionEvaluator(node.getSeparator(), parent, OPTypes.STRING).build(),
+                    node.isDistinct()
+            ));
+        }
+    }
+
+
+    @Override
+    public void meet(If node) throws RuntimeException {
+        builder.append("CASE WHEN ");
+
+        optypes.push(OPTypes.BOOL);
+        node.getCondition().visit(this);
+        optypes.pop();
+
+        optypes.push(new OPTypeFinder(node).coerce());
+        builder.append(" THEN ");
+        node.getResult().visit(this);
+        builder.append(" ELSE ");
+        node.getAlternative().visit(this);
+        builder.append(" END");
+        optypes.pop();
+    }
+
+
+    @Override
+    public void meet(IsBNode node) throws RuntimeException {
+        ValueExpr arg = node.getArg();
+
+        // operator must be a variable or a constant
+        if(arg instanceof ValueConstant) {
+            builder.append(Boolean.toString(((ValueConstant) arg).getValue() instanceof BNode));
+        } else if(arg instanceof Var) {
+            String var = getVariableAlias((Var) arg);
+
+            builder.append(var).append(".ntype = 'bnode'");
+        }
+    }
+
+    @Override
+    public void meet(IsLiteral node) throws RuntimeException {
+        ValueExpr arg = node.getArg();
+
+        // operator must be a variable or a constant
+        if (arg instanceof ValueConstant) {
+            builder.append(Boolean.toString(((ValueConstant) arg).getValue() instanceof Literal));
+        } else if(arg instanceof Var) {
+            String var = getVariableAlias((Var) arg);
+
+            Preconditions.checkState(var != null, "no alias available for variable");
+
+            builder.append("(")
+                    .append(var)
+                    .append(".ntype = 'string' OR ")
+                    .append(var)
+                    .append(".ntype = 'int' OR ")
+                    .append(var)
+                    .append(".ntype = 'double' OR ")
+                    .append(var)
+                    .append(".ntype = 'date' OR ")
+                    .append(var)
+                    .append(".ntype = 'boolean')");
+        }
+    }
+
+    @Override
+    public void meet(IsNumeric node) throws RuntimeException {
+        ValueExpr arg = node.getArg();
+
+        // operator must be a variable or a constant
+        if (arg instanceof ValueConstant) {
+            try {
+                Double.parseDouble(((ValueConstant) arg).getValue().stringValue());
+                builder.append(Boolean.toString(true));
+            } catch (NumberFormatException ex) {
+                builder.append(Boolean.toString(false));
+            }
+        } else if(arg instanceof Var) {
+            String var = getVariableAlias((Var) arg);
+
+            Preconditions.checkState(var != null, "no alias available for variable");
+
+            builder.append("(")
+                    .append(var)
+                    .append(".ntype = 'int' OR ")
+                    .append(var)
+                    .append(".ntype = 'double')");
+        }
+    }
+
+    @Override
+    public void meet(IsResource node) throws RuntimeException {
+        ValueExpr arg = node.getArg();
+
+        // operator must be a variable or a constant
+        if(arg instanceof ValueConstant) {
+            builder.append(Boolean.toString(((ValueConstant) arg).getValue() instanceof URI || ((ValueConstant) arg).getValue() instanceof BNode));
+        } else if(arg instanceof Var) {
+            String var = getVariableAlias((Var) arg);
+
+            Preconditions.checkState(var != null, "no alias available for variable");
+
+            builder .append("(")
+                    .append(var)
+                    .append(".ntype = 'uri' OR ")
+                    .append(var)
+                    .append(".ntype = 'bnode')");
+        }
+    }
+
+    @Override
+    public void meet(IsURI node) throws RuntimeException {
+        ValueExpr arg = node.getArg();
+
+        // operator must be a variable or a constant
+        if(arg instanceof ValueConstant) {
+            builder.append(Boolean.toString(((ValueConstant) arg).getValue() instanceof URI));
+        } else if(arg instanceof Var) {
+            String var = getVariableAlias((Var) arg);
+
+            Preconditions.checkState(var != null, "no alias available for variable");
+
+            builder.append(var).append(".ntype = 'uri'");
+        }
+    }
+
+    @Override
+    public void meet(IRIFunction fun) throws RuntimeException {
+        if(fun.getBaseURI() != null) {
+
+            String ex = new ValueExpressionEvaluator(fun.getArg(), parent, OPTypes.STRING).build();
+
+            builder
+                    .append("CASE WHEN position(':' IN ").append(ex).append(") > 0 THEN ").append(ex)
+                    .append(" ELSE ").append(functionRegistry.get(FN.CONCAT.stringValue()).getNative(parent.getDialect(), "'" + fun.getBaseURI() + "'", ex))
+                    .append(" END ");
+        } else {
+            // get value of argument and express it as string
+            optypes.push(OPTypes.STRING);
+            fun.getArg().visit(this);
+            optypes.pop();
+        }
+    }
+
+    @Override
+    public void meet(Label node) throws RuntimeException {
+        optypes.push(OPTypes.STRING);
+        node.getArg().visit(this);
+        optypes.pop();
+    }
+
+    @Override
+    public void meet(Lang lang) throws RuntimeException {
+        if(lang.getArg() instanceof Var) {
+            String var = getVariableAlias((Var) lang.getArg());
+            Preconditions.checkState(var != null, "no alias available for variable");
+
+            builder.append(var);
+            builder.append(".lang");
+        }
+    }
+
+    @Override
+    public void meet(LangMatches lm) throws RuntimeException {
+        ValueConstant pattern = (ValueConstant) lm.getRightArg();
+
+        if(pattern.getValue().stringValue().equals("*")) {
+            lm.getLeftArg().visit(this);
+            builder.append(" LIKE '%'");
+        } else if(pattern.getValue().stringValue().equals("")) {
+            lm.getLeftArg().visit(this);
+            builder.append(" IS NULL");
+        } else {
+            builder.append("(");
+            lm.getLeftArg().visit(this);
+            builder.append(" = '");
+            builder.append(pattern.getValue().stringValue().toLowerCase());
+            builder.append("' OR ");
+            lm.getLeftArg().visit(this);
+            builder.append(" LIKE '");
+            builder.append(pattern.getValue().stringValue().toLowerCase());
+            builder.append("-%' )");
+        }
+    }
+
+    @Override
+    public void meet(Like node) throws RuntimeException {
+        if(node.isCaseSensitive()) {
+            optypes.push(OPTypes.STRING);
+            node.getArg().visit(this);
+            optypes.pop();
+
+            builder.append(" LIKE ");
+            node.getPattern();
+        } else {
+            builder.append(parent.getDialect().getILike(new ValueExpressionEvaluator(node.getArg(),parent, OPTypes.STRING).build(), node.getOpPattern()));
+        }
+
+    }
+
+
+    @Override
+    public void meet(LocalName node) throws RuntimeException {
+        super.meet(node);
+    }
+
+    @Override
+    public void meet(MathExpr expr) throws RuntimeException {
+        OPTypes ot = new OPTypeFinder(expr).coerce();
+
+        if(ot == OPTypes.STRING) {
+            if(expr.getOperator() == MathExpr.MathOp.PLUS) {
+                builder.append(functionRegistry.get(FN.CONCAT.stringValue()).getNative(parent.getDialect(),new ValueExpressionEvaluator(expr.getLeftArg(), parent, ot).build(), new ValueExpressionEvaluator(expr.getRightArg(), parent, ot).build()));
+            } else {
+                throw new IllegalArgumentException("operation "+expr.getOperator()+" is not supported on strings");
+            }
+        } else {
+            if(ot == OPTypes.ANY || ot == OPTypes.TERM) {
+                ot = OPTypes.DOUBLE;
+            }
+
+            optypes.push(ot);
+            expr.getLeftArg().visit(this);
+            builder.append(getSQLOperator(expr.getOperator()));
+            expr.getRightArg().visit(this);
+            optypes.pop();
+        }
+    }
+
+    @Override
+    public void meet(Max node) throws RuntimeException {
+        builder.append("MAX(");
+        optypes.push(OPTypes.DOUBLE);
+        node.getArg().visit(this);
+        optypes.pop();
+        builder.append(")");
+    }
+
+    @Override
+    public void meet(Min node) throws RuntimeException {
+        builder.append("MIN(");
+        optypes.push(OPTypes.DOUBLE);
+        node.getArg().visit(this);
+        optypes.pop();
+        builder.append(")");
+    }
+
+    @Override
+    public void meet(Regex re) throws RuntimeException {
+        builder.append(optimizeRegexp(
+                new ValueExpressionEvaluator(re.getArg(), parent, OPTypes.STRING).build(),
+                new ValueExpressionEvaluator(re.getPatternArg(), parent, OPTypes.STRING).build(),
+                re.getFlagsArg()
+        ));
+    }
+
+    @Override
+    public void meet(SameTerm cmp) throws RuntimeException {
+        // covered by value binding in variables
+        optypes.push(OPTypes.TERM);
+        cmp.getLeftArg().visit(this);
+        builder.append(" = ");
+        cmp.getRightArg().visit(this);
+        optypes.pop();
+    }
+
+    @Override
+    public void meet(Str node) throws RuntimeException {
+        optypes.push(OPTypes.STRING);
+        node.getArg().visit(this);
+        optypes.pop();
+    }
+
+    @Override
+    public void meet(Sum node) throws RuntimeException {
+        builder.append("SUM(");
+        optypes.push(OPTypes.DOUBLE);
+        node.getArg().visit(this);
+        optypes.pop();
+        builder.append(")");
+    }
+
+    @Override
+    public void meet(Var node) throws RuntimeException {
+        // distinguish between the case where the variable is plain and the variable is bound
+        SQLVariable sv = parent.getVariables().get(node.getName());
+
+        if(sv == null) {
+            builder.append("NULL");
+        } else if(sv.getBindings().size() > 0) {
+            // in case the variable is actually an alias for an expression, we evaluate that expression instead, effectively replacing the
+            // variable occurrence with its value
+            sv.getBindings().get(0).visit(this);
+        } else {
+            String var = sv.getAlias();
+
+            if(sv.getProjectionType() != ProjectionType.NODE && sv.getProjectionType() != ProjectionType.NONE) {
+                // in case the variable represents a constructed or bound value instead of a node, we need to
+                // use the SQL expression as value; SQL should take care of proper casting...
+                // TODO: explicit casting needed?
+                builder.append(sv.getExpressions().get(0));
+            } else {
+                // in case the variable represents an entry from the NODES table (i.e. has been bound to a node
+                // in the database, we take the NODES alias and resolve to the correct column according to the
+                // operator type
+                switch (optypes.peek()) {
+                    case STRING:
+                        Preconditions.checkState(var != null, "no alias available for variable");
+                        builder.append(var).append(".svalue");
+                        break;
+                    case INT:
+                        Preconditions.checkState(var != null, "no alias available for variable");
+                        builder.append(var).append(".ivalue");
+                        break;
+                    case DOUBLE:
+                        Preconditions.checkState(var != null, "no alias available for variable");
+                        builder.append(var).append(".dvalue");
+                        break;
+                    case BOOL:
+                        Preconditions.checkState(var != null, "no alias available for variable");
+                        builder.append(var).append(".bvalue");
+                        break;
+                    case DATE:
+                        Preconditions.checkState(var != null, "no alias available for variable");
+                        builder.append(var).append(".tvalue");
+                        break;
+                    case VALUE:
+                        Preconditions.checkState(var != null, "no alias available for variable");
+                        builder.append(var).append(".svalue");
+                        break;
+                    case URI:
+                        Preconditions.checkState(var != null, "no alias available for variable");
+                        builder.append(var).append(".svalue");
+                        break;
+                    case TERM:
+                    case ANY:
+                        if(sv.getExpressions().size() > 0) {
+                            // this allows us to avoid joins with the nodes table for simple expressions that only need the ID
+                            builder.append(sv.getExpressions().get(0));
+                        } else {
+                            Preconditions.checkState(var != null, "no alias available for variable");
+                            builder.append(var).append(".id");
+                        }
+                        break;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void meet(ValueConstant node) throws RuntimeException {
+        String val = node.getValue().stringValue();
+
+            switch (optypes.peek()) {
+                case STRING:
+                case VALUE:
+                case URI:
+                    builder.append("'").append(val).append("'");
+                    break;
+                case INT:
+                    builder.append(Integer.parseInt(val));
+                    break;
+                case DOUBLE:
+                    builder.append(Double.parseDouble(val));
+                    break;
+                case BOOL:
+                    builder.append(Boolean.parseBoolean(val));
+                    break;
+                case DATE:
+                    builder.append("'").append(sqlDateFormat.format(DateUtils.parseDate(val))).append("'");
+                    break;
+
+                // in this case we should return a node ID and also need to make sure it actually exists
+                case TERM:
+                case ANY:
+                    KiWiNode n = parent.getConverter().convert(node.getValue());
+                    builder.append(n.getId());
+                    break;
+
+                default: throw new IllegalArgumentException("unsupported value type: " + optypes.peek());
+            }
+    }
+
+    private String getVariableAlias(Var var) {
+        return parent.getVariables().get(var.getName()).getAlias();
+    }
+
+
+    private String getVariableAlias(String varName) {
+        return parent.getVariables().get(varName).getAlias();
+    }
+
+    /**
+     * Copy variables from the set to a new set suitable for a subquery; this allows passing over variable expressions
+     * from parent queries to subqueries without the subquery adding expressions that are then not visible outside
+     * @param variables
+     * @return
+     */
+    private static Map<String, SQLVariable> copyVariables(Map<String, SQLVariable> variables) {
+        Map<String,SQLVariable> copy = new HashMap<>();
+        try {
+            for(Map.Entry<String,SQLVariable> entry : variables.entrySet()) {
+                copy.put(entry.getKey(), (SQLVariable) entry.getValue().clone());
+            }
+        } catch (CloneNotSupportedException e) {
+            log.error("could not clone SQL variable:",e);
+        }
+
+        return copy;
+    }
+
+    private String castExpression(String arg, OPTypes type) {
+        if(type == null) {
+            return arg;
+        }
+
+        switch (type) {
+            case DOUBLE:
+                return functionRegistry.get(XMLSchema.DOUBLE).getNative(parent.getDialect(), arg);
+            case INT:
+                return functionRegistry.get(XMLSchema.INTEGER).getNative(parent.getDialect(), arg);
+            case BOOL:
+                return functionRegistry.get(XMLSchema.BOOLEAN).getNative(parent.getDialect(), arg);
+            case DATE:
+                return functionRegistry.get(XMLSchema.DATETIME).getNative(parent.getDialect(), arg);
+            case VALUE:
+                return arg;
+            case ANY:
+                return arg;
+            default:
+                return arg;
+        }
+    }
+
+    private static 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 static 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);
+    }
+
+    /**
+     * 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, ValueExpr flags) {
+        String _flags = flags != null && flags instanceof ValueConstant ? ((ValueConstant)flags).getValue().stringValue() : null;
+
+        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()-1);
+        }
+
+        // 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, _flags);
+        } else {
+            if(!simplified.startsWith("%") && !simplified.endsWith("%")) {
+                if(StringUtils.containsIgnoreCase(_flags, "i")) {
+                    return String.format("lower(%s) = lower('%s')", value, simplified);
+                } else {
+                    return String.format("%s = '%s'", value, simplified);
+                }
+            } else {
+                if(StringUtils.containsIgnoreCase(_flags,"i")) {
+                    return parent.getDialect().getILike(value, "'" + simplified + "'");
+                } else {
+                    return value + " LIKE '"+simplified+"'";
+                }
+            }
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/marmotta/blob/60867dac/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/evaluation/KiWiEvaluationStrategyImpl.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/evaluation/KiWiEvaluationStrategyImpl.java b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/evaluation/KiWiEvaluationStrategyImpl.java
index b21b2ab..7e6d7bb 100644
--- a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/evaluation/KiWiEvaluationStrategyImpl.java
+++ b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/evaluation/KiWiEvaluationStrategyImpl.java
@@ -19,7 +19,7 @@ package org.apache.marmotta.kiwi.sparql.evaluation;
 
 import info.aduna.iteration.CloseableIteration;
 import info.aduna.iteration.ExceptionConvertingIteration;
-import org.apache.marmotta.kiwi.sparql.function.NativeFunctionRegistry;
+import org.apache.marmotta.kiwi.sparql.builder.collect.SupportedFinder;
 import org.apache.marmotta.kiwi.sparql.persistence.KiWiSparqlConnection;
 import org.openrdf.query.BindingSet;
 import org.openrdf.query.Dataset;
@@ -53,48 +53,6 @@ public class KiWiEvaluationStrategyImpl extends EvaluationStrategyImpl{
 
     private static Logger log = LoggerFactory.getLogger(KiWiEvaluationStrategyImpl.class);
 
-    // TODO: supported features should be checked based on this Set
-    private static Set<Class> supportedConstructs = new HashSet<>();
-    static {
-        supportedConstructs.add(Join.class);
-        supportedConstructs.add(LeftJoin.class);
-        supportedConstructs.add(Filter.class);
-        supportedConstructs.add(Extension.class);
-        supportedConstructs.add(StatementPattern.class);
-        supportedConstructs.add(Slice.class);
-        supportedConstructs.add(Reduced.class);
-        supportedConstructs.add(Distinct.class);
-        supportedConstructs.add(Union.class);
-        supportedConstructs.add(Projection.class); // subquery only
-        supportedConstructs.add(Order.class);
-        supportedConstructs.add(Group.class);
-
-        supportedConstructs.add(Coalesce.class);
-        supportedConstructs.add(Count.class);
-        supportedConstructs.add(Avg.class);
-        supportedConstructs.add(Min.class);
-        supportedConstructs.add(Max.class);
-        supportedConstructs.add(Sum.class);
-        supportedConstructs.add(Compare.class);
-        supportedConstructs.add(MathExpr.class);
-        supportedConstructs.add(And.class);
-        supportedConstructs.add(Or.class);
-        supportedConstructs.add(Not.class);
-        supportedConstructs.add(Var.class);
-        supportedConstructs.add(Str.class);
-        supportedConstructs.add(Label.class);
-        supportedConstructs.add(BNodeGenerator.class);
-        supportedConstructs.add(IRIFunction.class);
-        supportedConstructs.add(IsResource.class);
-        supportedConstructs.add(IsURI.class);
-        supportedConstructs.add(IsBNode.class);
-        supportedConstructs.add(IsLiteral.class);
-        supportedConstructs.add(Lang.class);
-        supportedConstructs.add(LangMatches.class);
-        supportedConstructs.add(Regex.class);
-        supportedConstructs.add(FunctionCall.class); // need to check for supported functions
-    }
-
 
     /**
      * The database connection offering specific SPARQL-SQL optimizations.
@@ -234,157 +192,11 @@ public class KiWiEvaluationStrategyImpl extends EvaluationStrategyImpl{
     /**
      * Test if a tuple expression is supported nby the optimized evaluation; in this case we can apply a specific optimization.
      *
-     * TODO: implement as visitor
-     *
      * @param expr
      * @return
      */
     private boolean isSupported(TupleExpr expr) {
-        if(expr instanceof Join) {
-            return isSupported(((Join) expr).getLeftArg()) && isSupported(((Join) expr).getRightArg());
-        } else if(expr instanceof LeftJoin) {
-            return isSupported(((LeftJoin) expr).getLeftArg()) && isSupported(((LeftJoin) expr).getRightArg()) && isSupported(((LeftJoin)expr).getCondition());
-        } else if(expr instanceof Filter) {
-            return isSupported(((Filter) expr).getArg()) && isSupported(((Filter) expr).getCondition());
-        } else if(expr instanceof Extension) {
-            for(ExtensionElem elem : ((Extension) expr).getElements()) {
-                if(!isSupported(elem.getExpr())) {
-                    return false;
-                }
-            }
-            return isSupported(((Extension) expr).getArg());
-        } else if(expr instanceof StatementPattern) {
-            return true;
-        } else if(expr instanceof Slice) {
-            return isSupported(((Slice) expr).getArg());
-        } else if(expr instanceof Reduced) {
-            return isSupported(((Reduced) expr).getArg());
-        } else if(expr instanceof Distinct) {
-            return isSupported(((Distinct) expr).getArg());
-        } else if(expr instanceof Union) {
-            return isSupported(((Union) expr).getLeftArg()) && isSupported(((Union)expr).getRightArg());
-        } else if(expr instanceof Projection) {
-            return isSupported(((Projection) expr).getArg());
-        } else if(expr instanceof Order) {
-            for(OrderElem elem : ((Order) expr).getElements()) {
-                if(!isSupported(elem.getExpr())) {
-                    return false;
-                }
-            }
-            return isSupported(((Order) expr).getArg());
-        } else if(expr instanceof Group) {
-            for(GroupElem elem : ((Group) expr).getGroupElements()) {
-                if(!isSupported(elem.getOperator())) {
-                    return false;
-                }
-            }
-            return isSupported(((Group) expr).getArg());
-        } else if(expr instanceof SingletonSet) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Test if the value expression construct and all its subexpressions are supported by the optimized evaluation
-     * strategy. Returns true if yes, false otherwise.
-     *
-     * TODO: implement as visitor
-     *
-     * @param expr
-     * @return
-     */
-    private boolean isSupported(ValueExpr expr) {
-        if(expr == null) {
-            return true;
-        } else if(expr instanceof Coalesce) {
-            for(ValueExpr e : ((Coalesce) expr).getArguments()) {
-                if(!isSupported(e)) {
-                    return false;
-                }
-            }
-            return true;
-        } else if(expr instanceof Count) {
-            if(((Count) expr).getArg() == null) {
-                return connection.getDialect().isArraySupported();
-            } else {
-                return isSupported(((Count) expr).getArg());
-            }
-        } else if(expr instanceof Avg) {
-            return isSupported(((Avg) expr).getArg());
-        } else if(expr instanceof Min) {
-            return isSupported(((Min) expr).getArg());
-        } else if(expr instanceof Max) {
-            return isSupported(((Max) expr).getArg());
-        } else if(expr instanceof Sum) {
-            return isSupported(((Sum) expr).getArg());
-        } else if(expr instanceof Compare) {
-            return isSupported(((Compare) expr).getLeftArg()) && isSupported(((Compare) expr).getRightArg());
-        } else if(expr instanceof SameTerm) {
-            return isSupported(((SameTerm) expr).getLeftArg()) && isSupported(((SameTerm) expr).getRightArg());
-        } else if(expr instanceof MathExpr) {
-            return isSupported(((MathExpr) expr).getLeftArg()) && isSupported(((MathExpr) expr).getRightArg());
-        } else if(expr instanceof And) {
-            return isSupported(((And) expr).getLeftArg()) && isSupported(((And) expr).getRightArg());
-        } else if(expr instanceof Or) {
-            return isSupported(((Or) expr).getLeftArg()) && isSupported(((Or) expr).getRightArg());
-        } else if(expr instanceof Not) {
-            return isSupported(((Not) expr).getArg());
-        } else if(expr instanceof Exists) {
-            return isSupported(((Exists) expr).getSubQuery());
-        } else if(expr instanceof ValueConstant) {
-            return true;
-        } else if(expr instanceof Var) {
-            return true;
-        } else if(expr instanceof Str) {
-            return isAtomic(((Str) expr).getArg());
-        } else if(expr instanceof Label) {
-            return isAtomic(((UnaryValueOperator) expr).getArg());
-        } else if(expr instanceof BNodeGenerator) {
-            if(((BNodeGenerator) expr).getNodeIdExpr() != null) {
-                return isAtomic(((BNodeGenerator) expr).getNodeIdExpr());
-            } else {
-                return true;
-            }
-        } else if(expr instanceof IRIFunction) {
-            return isAtomic(((UnaryValueOperator) expr).getArg());
-        } else if(expr instanceof Bound) {
-            return true;
-        } else if(expr instanceof IsResource) {
-            return isAtomic(((UnaryValueOperator) expr).getArg());
-        } else if(expr instanceof IsURI) {
-            return isAtomic(((UnaryValueOperator) expr).getArg());
-        } else if(expr instanceof IsBNode) {
-            return isAtomic(((UnaryValueOperator) expr).getArg());
-        } else if(expr instanceof IsLiteral) {
-            return isAtomic(((UnaryValueOperator) expr).getArg());
-        } else if(expr instanceof Lang) {
-            return isAtomic(((Lang) expr).getArg());
-        } else if(expr instanceof LangMatches) {
-            return isSupported(((LangMatches) expr).getLeftArg()) && isConstant(((LangMatches) expr).getRightArg());
-        } else if(expr instanceof Regex) {
-            ValueExpr flags = ((Regex) expr).getFlagsArg();
-            String _flags = flags != null && flags instanceof ValueConstant ? ((ValueConstant)flags).getValue().stringValue() : null;
-            return isSupported(((Regex) expr).getArg()) && isAtomic(((Regex) expr).getPatternArg()) && connection.getDialect().isRegexpSupported(_flags);
-        } else if(expr instanceof FunctionCall) {
-            return isFunctionSupported((FunctionCall)expr);
-        } else {
-            return false;
-        }
-    }
-
-    private boolean isFunctionSupported(FunctionCall fc) {
-        return NativeFunctionRegistry.getInstance().get(fc.getURI()) != null && NativeFunctionRegistry.getInstance().get(fc.getURI()).isSupported(connection.getDialect());
-    }
-
-
-    private static boolean isAtomic(ValueExpr expr) {
-        return expr instanceof Var || expr instanceof ValueConstant;
-    }
-
-    private static boolean isConstant(ValueExpr expr) {
-        return expr instanceof ValueConstant;
+        return new SupportedFinder(expr, connection.getDialect()).isSupported();
     }
 
 }

http://git-wip-us.apache.org/repos/asf/marmotta/blob/60867dac/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/KiWiDialect.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/KiWiDialect.java b/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/KiWiDialect.java
index b87972a..d54df6d 100644
--- a/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/KiWiDialect.java
+++ b/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/KiWiDialect.java
@@ -199,6 +199,11 @@ public abstract class KiWiDialect {
     public abstract String getILike(String text, String pattern);
 
 
+    /**
+     * Return the name of the aggregate function for group concatenation (string_agg in postgres, GROUP_CONCAT in MySQL)
+     * @return
+     */
+    public abstract String getGroupConcat(String value, String separator, boolean distinct);
 
 
     /**

http://git-wip-us.apache.org/repos/asf/marmotta/blob/60867dac/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/h2/H2Dialect.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/h2/H2Dialect.java b/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/h2/H2Dialect.java
index fdbbc63..05913b2 100644
--- a/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/h2/H2Dialect.java
+++ b/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/h2/H2Dialect.java
@@ -86,6 +86,24 @@ public class H2Dialect extends KiWiDialect {
         return "lower("+text+") LIKE lower("+pattern+")";
     }
 
+    /**
+     * Return the name of the aggregate function for group concatenation (string_agg in postgres, GROUP_CONCAT in MySQL)
+     *
+     * @param value
+     * @param separator
+     * @return
+     */
+    @Override
+    public String getGroupConcat(String value, String separator, boolean distinct) {
+        if(distinct) {
+            value = "DISTINCT " + value;
+        }
+        if(separator != null) {
+            return String.format("GROUP_CONCAT(%s SEPARATOR %s)", value, separator);
+        } else {
+            return String.format("GROUP_CONCAT(%s)", value);
+        }
+    }
 
 
     /**

http://git-wip-us.apache.org/repos/asf/marmotta/blob/60867dac/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/mysql/MySQLDialect.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/mysql/MySQLDialect.java b/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/mysql/MySQLDialect.java
index 0a24b75..82ed3f6 100644
--- a/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/mysql/MySQLDialect.java
+++ b/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/mysql/MySQLDialect.java
@@ -99,6 +99,24 @@ public class MySQLDialect extends KiWiDialect {
     }
 
 
+    /**
+     * Return the name of the aggregate function for group concatenation (string_agg in postgres, GROUP_CONCAT in MySQL)
+     *
+     * @param value
+     * @param separator
+     * @return
+     */
+    @Override
+    public String getGroupConcat(String value, String separator, boolean distinct) {
+        if(distinct) {
+            value = "DISTINCT " + value;
+        }
+        if(separator != null) {
+            return String.format("GROUP_CONCAT(%s SEPARATOR %s)", value, separator);
+        } else {
+            return String.format("GROUP_CONCAT(%s)", value);
+        }
+    }
 
     /**
      * Get the query string that can be used for validating that a JDBC connection to this database is still valid.

http://git-wip-us.apache.org/repos/asf/marmotta/blob/60867dac/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/pgsql/PostgreSQLDialect.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/pgsql/PostgreSQLDialect.java b/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/pgsql/PostgreSQLDialect.java
index bc369d1..28fa98e 100644
--- a/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/pgsql/PostgreSQLDialect.java
+++ b/libraries/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/pgsql/PostgreSQLDialect.java
@@ -90,6 +90,24 @@ public class PostgreSQLDialect extends KiWiDialect {
         return text + " ILIKE " + pattern;
     }
 
+    /**
+     * Return the name of the aggregate function for group concatenation (string_agg in postgres, GROUP_CONCAT in MySQL)
+     *
+     * @param value
+     * @param separator
+     * @return
+     */
+    @Override
+    public String getGroupConcat(String value, String separator, boolean distinct) {
+        if(distinct) {
+            value = "DISTINCT " + value;
+        }
+        if(separator != null) {
+            return String.format("string_agg(%s, %s)", value, separator);
+        } else {
+            return String.format("string_agg(%s, '')", value);
+        }
+    }
 
     /**
      * Get the query string that can be used for validating that a JDBC connection to this database is still valid.