marmotta-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sschaff...@apache.org
Subject git commit: first working UNION (MARMOTTA-538)
Date Wed, 17 Sep 2014 16:16:23 GMT
Repository: marmotta
Updated Branches:
  refs/heads/develop 25568a16d -> 4a33f414d


first working UNION (MARMOTTA-538)


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

Branch: refs/heads/develop
Commit: 4a33f414dc1e3663bbd1cf02f6e59fa41eb15e09
Parents: 25568a1
Author: Sebastian Schaffert <sschaffert@apache.org>
Authored: Wed Sep 17 18:16:42 2014 +0200
Committer: Sebastian Schaffert <sschaffert@apache.org>
Committed: Wed Sep 17 18:16:42 2014 +0200

----------------------------------------------------------------------
 .../kiwi/sparql/builder/PatternCollector.java   |  30 +++-
 .../sparql/builder/SQLAbstractSubquery.java     |  53 ++++++
 .../kiwi/sparql/builder/SQLBuilder.java         | 157 +++++++++++------
 .../marmotta/kiwi/sparql/builder/SQLClause.java |  80 +++++++++
 .../kiwi/sparql/builder/SQLFragment.java        |  27 +--
 .../kiwi/sparql/builder/SQLPattern.java         |  47 ++---
 .../sparql/builder/SQLProjectionFinder.java     |  10 +-
 .../kiwi/sparql/builder/SQLSubQuery.java        |  91 ++++++++++
 .../marmotta/kiwi/sparql/builder/SQLUnion.java  | 173 +++++++++++++++++++
 .../kiwi/sparql/builder/SQLVariable.java        |  50 +++++-
 .../evaluation/KiWiEvaluationStrategyImpl.java  |  30 ++++
 .../exception/UnsatisfiableQueryException.java  |   2 +-
 .../persistence/KiWiSparqlConnection.java       |  12 +-
 .../marmotta/kiwi/sparql/test/query26.sparql    |   4 +-
 14 files changed, 644 insertions(+), 122 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a33f414/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/PatternCollector.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/PatternCollector.java b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/PatternCollector.java
index 3dba067..bb8f254 100644
--- a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/PatternCollector.java
+++ b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/PatternCollector.java
@@ -17,10 +17,10 @@
 
 package org.apache.marmotta.kiwi.sparql.builder;
 
-import org.openrdf.query.algebra.Filter;
-import org.openrdf.query.algebra.LeftJoin;
-import org.openrdf.query.algebra.StatementPattern;
-import org.openrdf.query.algebra.TupleExpr;
+import org.apache.marmotta.kiwi.persistence.KiWiDialect;
+import org.openrdf.query.BindingSet;
+import org.openrdf.query.Dataset;
+import org.openrdf.query.algebra.*;
 import org.openrdf.query.algebra.helpers.QueryModelVisitorBase;
 
 import java.util.LinkedList;
@@ -36,7 +36,19 @@ public class PatternCollector extends QueryModelVisitorBase<RuntimeException> {
 
     int counter = 0;
 
-    public PatternCollector(TupleExpr expr) {
+
+    private BindingSet bindings;
+    private Dataset dataset;
+    private ValueConverter converter;
+    private KiWiDialect dialect;
+
+
+    public PatternCollector(TupleExpr expr, BindingSet bindings, Dataset dataset, ValueConverter converter, KiWiDialect dialect) {
+        this.bindings = bindings;
+        this.dataset = dataset;
+        this.converter = converter;
+        this.dialect = dialect;
+
         parts.push(new SQLFragment());
         expr.visit(this);
     }
@@ -66,4 +78,12 @@ public class PatternCollector extends QueryModelVisitorBase<RuntimeException> {
 
         super.meet(node);
     }
+
+    @Override
+    public void meet(Union node) throws RuntimeException {
+        // unions are treated as subqueries, don't continue collection, but add the Union to the last part
+
+        parts.getLast().getSubqueries().add(new SQLUnion("U" + (++counter),node, bindings, dataset, converter, dialect));
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a33f414/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLAbstractSubquery.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLAbstractSubquery.java b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLAbstractSubquery.java
new file mode 100644
index 0000000..e0c6e49
--- /dev/null
+++ b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLAbstractSubquery.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import org.openrdf.query.algebra.ValueExpr;
+
+import java.util.Set;
+
+/**
+ * Common fields and methods for all subqueries.
+ *
+ * @author Sebastian Schaffert (sschaffert@apache.org)
+ */
+public abstract class SQLAbstractSubquery extends SQLClause {
+
+    protected String alias;
+
+    public SQLAbstractSubquery(String alias) {
+        this.alias = alias;
+    }
+
+    public String getAlias() {
+        return alias;
+    }
+
+    /**
+     * Return the SQL variables used by the subquery; we need this to do proper mapping in the parent query.
+     * @return
+     */
+    public abstract Set<SQLVariable> getQueryVariables();
+
+    /**
+     * Return the projection type of an expression in this subquery. Needed for propagation up to the parent.
+     * @param expr
+     * @return
+     */
+    protected abstract ProjectionType getProjectionType(ValueExpr expr);
+}

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a33f414/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 f2dc767..804fda9 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
@@ -145,8 +145,8 @@ public class SQLBuilder {
      * and what internal aliases to use.
      */
     private Map<String,SQLVariable> variables = new HashMap<>();
-    private void addVariable(SQLVariable v) {
-        variables.put(v.getSparqlVariable().getName(),v);
+    protected void addVariable(SQLVariable v) {
+        variables.put(v.getSparqlName(),v);
     }
 
 
@@ -204,11 +204,11 @@ public class SQLBuilder {
     }
 
     private void prepareBuilder()  throws UnsatisfiableQueryException {
-        Preconditions.checkArgument(query instanceof Extension || query instanceof Order || query instanceof Group || query instanceof LeftJoin ||query instanceof Join || query instanceof Filter || query instanceof StatementPattern || query instanceof Distinct || query instanceof Slice || query instanceof Reduced);
+        Preconditions.checkArgument(query instanceof Union || query instanceof Extension || query instanceof Order || query instanceof Group || query instanceof LeftJoin ||query instanceof Join || query instanceof Filter || query instanceof StatementPattern || query instanceof Distinct || query instanceof Slice || query instanceof Reduced);
 
 
         // collect all patterns in a list, using depth-first search over the join
-        PatternCollector pc = new PatternCollector(query);
+        PatternCollector pc = new PatternCollector(query, bindings, dataset, converter, dialect);
 
         fragments = pc.parts;
 
@@ -243,11 +243,11 @@ public class SQLBuilder {
                         Var v = fields[i];
 
                         SQLVariable sv = variables.get(v.getName());
-                        if(!variables.containsKey(v.getName())) {
-                            sv = new SQLVariable("V" + (++variableCount), v);
+                        if(sv == null) {
+                            sv = new SQLVariable("V" + (++variableCount), v.getName());
 
                             // select those variables that are really projected and not only needed in a grouping construct
-                            if(new SQLProjectionFinder(query,v).found) {
+                            if(new SQLProjectionFinder(query,v.getName()).found) {
                                 sv.setProjectionType(ProjectionType.NODE);
                             }
 
@@ -257,7 +257,7 @@ public class SQLBuilder {
                         String pName = p.getName();
                         String vName = sv.getName();
 
-                        if (hasNodeCondition(fields[i], query)) {
+                        if (hasNodeCondition(v.getName(), query)) {
                             sv.getAliases().add(pName + "_" + positions[i] + "_" + vName);
                         }
 
@@ -270,6 +270,39 @@ public class SQLBuilder {
                     }
                 }
             }
+
+            // subqueries: look up which variables are bound in the subqueries and add proper aliases
+            for(SQLAbstractSubquery sq : f.getSubqueries()) {
+                for(SQLVariable sq_v : sq.getQueryVariables()) {
+                    SQLVariable sv = variables.get(sq_v.getSparqlName());
+
+                    if(sv == null) {
+                        sv = new SQLVariable("V" + (++variableCount), sq_v.getSparqlName());
+
+                        // select those variables that are really projected and not only needed in a grouping construct
+                        if(new SQLProjectionFinder(query,sq_v.getSparqlName()).found) {
+                            sv.setProjectionType(ProjectionType.NODE);   // TODO: might need other types as well in case a value is created in children
+                        }
+
+                        addVariable(sv);
+                    }
+
+                    String sqName = sq.getAlias();
+                    String vName = sv.getName();
+
+                    if (hasNodeCondition(sq_v.getSparqlName(), query)) {
+                        sv.getAliases().add(sqName + "_" + vName);
+                    }
+
+                    // if the variable has been used before, add a join condition to the first occurrence
+                    if(sv.getExpressions().size() > 0) {
+                        sq.getConditions().add(sv.getExpressions().get(0) + " = " + sqName + "." + sq_v.getName());
+                    }
+
+                    sv.getExpressions().add(sqName + "." + sq_v.getName());
+
+                }
+            }
         }
 
 
@@ -280,17 +313,17 @@ public class SQLBuilder {
 
             SQLVariable sv = variables.get(v.getName());
             if(!variables.containsKey(v.getName())) {
-                sv = new SQLVariable("V" + (++variableCount), v);
+                sv = new SQLVariable("V" + (++variableCount), v.getName());
 
                 // select those variables that are really projected and not only needed in a grouping construct
-                if(new SQLProjectionFinder(query,v).found) {
+                if(new SQLProjectionFinder(query,v.getName()).found) {
                     sv.setProjectionType(getProjectionType(ext.getExpr()));
                 }
 
                 addVariable(sv);
             }
 
-            if (hasNodeCondition(v, query)) {
+            if (hasNodeCondition(v.getName(), query)) {
                 sv.getAliases().add(evaluateExpression(ext.getExpr(), OPTypes.ANY));
             }
             sv.getExpressions().add(evaluateExpression(ext.getExpr(), OPTypes.ANY));
@@ -460,19 +493,31 @@ public class SQLBuilder {
 
             for (SQLPattern p : f.getPatterns()) {
                 for(Map.Entry<SQLPattern.TripleColumns, Var> fieldEntry : p.getTripleFields().entrySet()) {
-                    if(fieldEntry.getValue() != null && !fieldEntry.getValue().hasValue() && hasNodeCondition(fieldEntry.getValue(),query)) {
+                    if(fieldEntry.getValue() != null && !fieldEntry.getValue().hasValue() && hasNodeCondition(fieldEntry.getValue().getName(),query)) {
                         p.setJoinField(fieldEntry.getKey(), variables.get(fieldEntry.getValue().getName()).getName());
                     }
                 }
             }
-        }
 
+            for(SQLAbstractSubquery sq : f.getSubqueries()) {
+                for(SQLVariable sq_v : sq.getQueryVariables()) {
+                    if(hasNodeCondition(sq_v.getSparqlName(),query)) {
+                        // TODO: this is needed in case we need to JOIN with the NODES table to retrieve values
+                    }
+                }
+            }
+        }
     }
 
 
     private String buildSelectClause() {
         List<String> projections = new ArrayList<>();
-        for(SQLVariable v : variables.values()) {
+
+        // enforce order in SELECT part, we need this for merging UNION subqueries
+        List<SQLVariable> vars = new ArrayList<>(variables.values());
+        Collections.sort(vars, SQLVariable.sparqlNameComparator);
+
+        for(SQLVariable v : vars) {
             if(v.getProjectionType() != ProjectionType.NONE) {
                 String projectedName = v.getName();
                 String fromName = v.getExpressions().get(0);
@@ -515,7 +560,7 @@ public class SQLBuilder {
     }
 
 
-    private String buildWhereClause() throws UnsatisfiableQueryException {
+    private String buildWhereClause()  {
         // 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
@@ -547,7 +592,7 @@ public class SQLBuilder {
                     if(binding instanceof KiWiNode) {
                         whereConditions.add(vName+" = "+((KiWiNode)binding).getId());
                     } else {
-                        throw new UnsatisfiableQueryException("the values in this binding have not been created by the KiWi value factory");
+                        throw new IllegalStateException("the values in this binding have not been created by the KiWi value factory");
                     }
 
                 }
@@ -557,10 +602,13 @@ public class SQLBuilder {
         // 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 ");
+            String condition = it.next();
+            if(condition.length() > 0) {
+                whereClause.append(condition);
+                whereClause.append("\n ");
+                if (it.hasNext()) {
+                    whereClause.append("AND ");
+                }
             }
         }
         return whereClause.toString();
@@ -569,7 +617,6 @@ public class SQLBuilder {
     private String buildOrderClause() {
         StringBuilder orderClause = new StringBuilder();
         if(orderby.size() > 0) {
-            orderClause.append("ORDER BY ");
             for(Iterator<OrderElem> it = orderby.iterator(); it.hasNext(); ) {
                 OrderElem elem = it.next();
                 orderClause.append(evaluateExpression(elem.getExpr(), OPTypes.VALUE));
@@ -591,7 +638,6 @@ public class SQLBuilder {
     private String buildGroupClause() {
         StringBuilder groupClause = new StringBuilder();
         if(groupLabels.size() > 0) {
-            groupClause.append("GROUP BY ");
             for(Iterator<String> it = groupLabels.iterator(); it.hasNext(); ) {
                 SQLVariable sv = variables.get(it.next());
 
@@ -876,60 +922,60 @@ public class SQLBuilder {
     /**
      * Check if a variable selecting a node actually has any attached condition; if not return false. This is used to
      * decide whether joining with the node itself is necessary.
-     * @param v
+     * @param varName
      * @param expr
      * @return
      */
-    private boolean hasNodeCondition(Var v, TupleExpr expr) {
+    private boolean hasNodeCondition(String varName, TupleExpr expr) {
         if(expr instanceof Filter) {
-            return hasNodeCondition(v, ((UnaryTupleOperator) expr).getArg()) || hasNodeCondition(v,  ((Filter) expr).getCondition());
+            return hasNodeCondition(varName, ((UnaryTupleOperator) expr).getArg()) || hasNodeCondition(varName,  ((Filter) expr).getCondition());
         } else if(expr instanceof Extension) {
             for(ExtensionElem elem : ((Extension) expr).getElements()) {
-                if(hasNodeCondition(v, elem.getExpr())) {
+                if(hasNodeCondition(varName, elem.getExpr())) {
                     return true;
                 }
             }
-            return hasNodeCondition(v,((Extension) expr).getArg());
+            return hasNodeCondition(varName,((Extension) expr).getArg());
         } else if(expr instanceof Order) {
             for(OrderElem elem : ((Order) expr).getElements()) {
-                if(hasNodeCondition(v, elem.getExpr())) {
+                if(hasNodeCondition(varName, elem.getExpr())) {
                     return true;
                 }
             }
-            return hasNodeCondition(v,((Order) expr).getArg());
+            return hasNodeCondition(varName,((Order) expr).getArg());
         } else if(expr instanceof Group) {
             for(GroupElem elem : ((Group) expr).getGroupElements()) {
-                if(hasNodeCondition(v, elem.getOperator())) {
+                if(hasNodeCondition(varName, elem.getOperator())) {
                     return true;
                 }
             }
-            return hasNodeCondition(v,((Group) expr).getArg());
+            return hasNodeCondition(varName,((Group) expr).getArg());
         } else if(expr instanceof UnaryTupleOperator) {
-            return hasNodeCondition(v, ((UnaryTupleOperator) expr).getArg());
+            return hasNodeCondition(varName, ((UnaryTupleOperator) expr).getArg());
         } else if(expr instanceof BinaryTupleOperator) {
-            return hasNodeCondition(v, ((BinaryTupleOperator) expr).getLeftArg()) || hasNodeCondition(v, ((BinaryTupleOperator) expr).getRightArg());
+            return hasNodeCondition(varName, ((BinaryTupleOperator) expr).getLeftArg()) || hasNodeCondition(varName, ((BinaryTupleOperator) expr).getRightArg());
         } else {
             return false;
         }
 
     }
 
-    private boolean hasNodeCondition(Var v, ValueExpr expr) {
+    private boolean hasNodeCondition(String varName, ValueExpr expr) {
         if(expr instanceof Var) {
-            return v.getName().equals(((Var) expr).getName());
+            return varName.equals(((Var) expr).getName());
         } else if(expr instanceof UnaryValueOperator) {
-            return hasNodeCondition(v, ((UnaryValueOperator) expr).getArg());
+            return hasNodeCondition(varName, ((UnaryValueOperator) expr).getArg());
         } else if(expr instanceof BinaryValueOperator) {
-            return hasNodeCondition(v, ((BinaryValueOperator) expr).getLeftArg()) || hasNodeCondition(v, ((BinaryValueOperator) expr).getRightArg());
+            return hasNodeCondition(varName, ((BinaryValueOperator) expr).getLeftArg()) || hasNodeCondition(varName, ((BinaryValueOperator) expr).getRightArg());
         } else if(expr instanceof NAryValueOperator) {
             for(ValueExpr e : ((NAryValueOperator) expr).getArguments()) {
-                if(hasNodeCondition(v,e)) {
+                if(hasNodeCondition(varName,e)) {
                     return true;
                 }
             }
         } else if(expr instanceof FunctionCall) {
             for(ValueExpr e : ((FunctionCall) expr).getArgs()) {
-                if(hasNodeCondition(v,e)) {
+                if(hasNodeCondition(varName,e)) {
                     return true;
                 }
             }
@@ -1033,7 +1079,7 @@ public class SQLBuilder {
     }
 
 
-    private ProjectionType getProjectionType(ValueExpr expr) {
+    protected ProjectionType getProjectionType(ValueExpr expr) {
         if(expr instanceof FunctionCall) {
             return opTypeToProjection(functions.get(FunctionUtil.getFunctionUri(((FunctionCall) expr).getURI())).getReturnType());
         } else if(expr instanceof NAryValueOperator) {
@@ -1091,7 +1137,7 @@ public class SQLBuilder {
      *
      * @return
      */
-    public String build() throws UnsatisfiableQueryException {
+    public String build()  {
         String selectClause = buildSelectClause();
         String fromClause   = buildFromClause();
         String whereClause  = buildWhereClause();
@@ -1100,20 +1146,31 @@ public class SQLBuilder {
         String limitClause  = buildLimitClause();
 
 
-        // build the query string
-        String queryString =
-                "SELECT " + selectClause + "\n " +
-                        "FROM " + fromClause + "\n " +
-                        "WHERE " + whereClause + "\n " +
-                        groupClause +
-                        orderClause +
-                        limitClause;
+        StringBuilder queryString = new StringBuilder();
+        queryString
+                .append("SELECT ").append(selectClause).append("\n ")
+                .append("FROM ").append(fromClause).append("\n ");
+
+        if(whereClause.length() > 0) {
+            queryString.append("WHERE ").append(whereClause).append("\n ");
+        }
+
+        if(groupClause.length() > 0) {
+            queryString.append("GROUP BY ").append(groupClause).append("\n ");
+        }
+
+        if(orderClause.length() > 0) {
+            queryString.append("ORDER BY ").append(orderClause).append("\n ");
+        }
+
+        queryString.append(limitClause);
+
 
         log.debug("original SPARQL syntax tree:\n {}", query);
         log.debug("constructed SQL query string:\n {}",queryString);
         log.debug("SPARQL -> SQL node variable mappings:\n {}", variables);
 
-        return queryString;
+        return queryString.toString();
     }
 
 }

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a33f414/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLClause.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLClause.java b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLClause.java
new file mode 100644
index 0000000..15164ba
--- /dev/null
+++ b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLClause.java
@@ -0,0 +1,80 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Used to mark fragments that can be used to construct table-like FROM clauses in SQL queries (triple patterns,
+ * subqueries, unions, ...)
+ *
+ * @author Sebastian Schaffert (sschaffert@apache.org)
+ */
+public abstract class SQLClause {
+
+    /**
+     * SQL conditions defined on this pattern; may only refer to previous or the current statement.
+     */
+    protected List<String> conditions;
+
+    public SQLClause() {
+        this.conditions = new ArrayList<>();
+    }
+
+    /**
+     * Build the query fragment that can be used in the FROM clause of a SQL query for representing this SPARQL construct.
+     * The fragment will be joined appropriately by the enclosing construct using CROSS JOIN, LEFT JOIN or normal JOIN.
+     *
+     * @return
+     */
+    public abstract String buildFromClause();
+
+    /**
+     * Return true if the FROM clause requires parenthesis before
+     * @return
+     */
+    public boolean needsParentheses() {
+        return false;
+    }
+
+    /**
+     * Build the condition clause for this statement to be used in the WHERE part or the ON part of a JOIN.
+     * @return
+     */
+    public String buildConditionClause() {
+        // the onClause consists of the filter conditions from the statement for joining/left joining with
+        // previous statements
+        StringBuilder onClause = new StringBuilder();
+
+        for(Iterator<String> cit = conditions.iterator(); cit.hasNext(); ) {
+            if(onClause.length() > 0) {
+                onClause.append("\n      AND ");
+            }
+            onClause.append(cit.next());
+        }
+
+        return onClause.toString();
+    }
+
+
+    public List<String> getConditions() {
+        return conditions;
+    }
+}

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a33f414/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLFragment.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLFragment.java b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLFragment.java
index 55920c4..11689e8 100644
--- a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLFragment.java
+++ b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLFragment.java
@@ -17,6 +17,7 @@
 
 package org.apache.marmotta.kiwi.sparql.builder;
 
+import com.google.common.collect.Iterators;
 import org.openrdf.query.algebra.ValueExpr;
 
 import java.util.ArrayList;
@@ -29,7 +30,7 @@ import java.util.List;
  *
  * @author Sebastian Schaffert (sschaffert@apache.org)
  */
-public class SQLFragment {
+public class SQLFragment extends SQLClause {
 
     /**
      * Indicate where the fragment's conditions should be placed (ON part of the JOIN clause or WHERE part of the query).
@@ -46,30 +47,30 @@ public class SQLFragment {
      */
     private List<SQLPattern> patterns;
 
-    private List<String> conditions;
+    private List<SQLAbstractSubquery> subqueries;
 
     private List<ValueExpr> filters;
 
     private ConditionPosition conditionPosition = ConditionPosition.JOIN;
 
     public SQLFragment() {
+        super();
         this.patterns   = new ArrayList<>();
-        this.conditions = new ArrayList<>();
         this.filters    = new ArrayList<>();
+        this.subqueries = new ArrayList<>();
     }
 
     public List<SQLPattern> getPatterns() {
         return patterns;
     }
 
-    public List<String> getConditions() {
-        return conditions;
-    }
-
     public List<ValueExpr> getFilters() {
         return filters;
     }
 
+    public List<SQLAbstractSubquery> getSubqueries() {
+        return subqueries;
+    }
 
     /**
      * Indicate where the fragment's conditions should be placed (ON part of the JOIN clause or WHERE part of the query).
@@ -91,9 +92,9 @@ public class SQLFragment {
     public String buildFromClause() {
         StringBuilder fromClause = new StringBuilder();
 
-        for (Iterator<SQLPattern> it = patterns.iterator(); it.hasNext(); ) {
+        for (Iterator<SQLClause> it = Iterators.concat(patterns.iterator(), subqueries.iterator()); it.hasNext(); ) {
 
-            SQLPattern p = it.next();
+            SQLClause p = it.next();
 
 
             StringBuilder conditionClause = new StringBuilder();
@@ -120,10 +121,10 @@ public class SQLFragment {
             // when the pattern builds a join with the nodes table and we have fragment-wide conditions, we need to
             // wrap the pattern's from clause in parentheses
             if(conditionClause.length() > 0) {
-                if(p.hasJoinFields())
+                if(p.needsParentheses())
                     fromClause.append("(");
                 fromClause.append(p.buildFromClause());
-                if(p.hasJoinFields())
+                if(p.needsParentheses())
                     fromClause.append(")");
                 fromClause.append(" ON (");
                 fromClause.append(conditionClause);
@@ -154,8 +155,8 @@ public class SQLFragment {
         StringBuilder conditionClause = new StringBuilder();
 
         if(conditionPosition == ConditionPosition.WHERE) {
-            for (Iterator<SQLPattern> it = patterns.iterator(); it.hasNext(); ) {
-                SQLPattern p = it.next();
+            for (Iterator<SQLClause> it = Iterators.concat(patterns.iterator(), subqueries.iterator()); it.hasNext(); ) {
+                SQLClause p = it.next();
 
                 // in case we add the condition to the JOIN, build first the conditions for the pattern; otherwise, the
                 // conditions for the pattern will be added to the WHERE clause

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a33f414/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLPattern.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLPattern.java b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLPattern.java
index 3508e46..c88f1bd 100644
--- a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLPattern.java
+++ b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLPattern.java
@@ -21,7 +21,9 @@ import org.openrdf.model.Resource;
 import org.openrdf.query.algebra.StatementPattern;
 import org.openrdf.query.algebra.Var;
 
-import java.util.*;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * A statement pattern translated to SQL consists of a named reference to the triple table, an indicator giving the
@@ -29,7 +31,7 @@ import java.util.*;
  *
  * @author Sebastian Schaffert (sschaffert@apache.org)
  */
-public class SQLPattern {
+public class SQLPattern extends SQLClause {
 
 
     /**
@@ -66,11 +68,6 @@ public class SQLPattern {
     private EnumMap<TripleColumns, Var> tripleFields = new EnumMap<>(TripleColumns.class);
 
     /**
-     * SQL conditions defined on this pattern; may only refer to previous or the current statement.
-     */
-    private List<String> conditions;
-
-    /**
      * Maps triple patterns from SPARQL WHERE to SQL aliases for the TRIPLES table in the FROM part. Used
      * to join one instance of the triples table for each triple pattern occurring in the query.
      */
@@ -87,8 +84,8 @@ public class SQLPattern {
     private List<Resource> variableContexts;
 
     public SQLPattern(String name, StatementPattern sparqlPattern) {
+        super();
         this.name = name;
-        this.conditions = new ArrayList<>();
         this.conditions.add(name + ".deleted = false");
         this.sparqlPattern = sparqlPattern;
 
@@ -116,6 +113,17 @@ public class SQLPattern {
         return joinFields.size() > 0;
     }
 
+
+    /**
+     * Return true if the FROM clause requires parenthesis before
+     *
+     * @return
+     */
+    @Override
+    public boolean needsParentheses() {
+        return hasJoinFields();
+    }
+
     public Var[] getFields() {
         return new Var[] {
                 getSparqlPattern().getSubjectVar(),
@@ -165,29 +173,6 @@ public class SQLPattern {
         return fromClause.toString();
     }
 
-    /**
-     * Build the condition clause for this statement to be used in the WHERE part or the ON part of a JOIN.
-     * @return
-     */
-    public String buildConditionClause() {
-        // the onClause consists of the filter conditions from the statement for joining/left joining with
-        // previous statements
-        StringBuilder onClause = new StringBuilder();
-
-        for(Iterator<String> cit = conditions.iterator(); cit.hasNext(); ) {
-            if(onClause.length() > 0) {
-                onClause.append("\n      AND ");
-            }
-            onClause.append(cit.next());
-        }
-
-        return onClause.toString();
-    }
-
-
-    public List<String> getConditions() {
-        return conditions;
-    }
 
     public String getName() {
         return name;

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a33f414/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLProjectionFinder.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLProjectionFinder.java b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLProjectionFinder.java
index 46bece0..f3c2d1b 100644
--- a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLProjectionFinder.java
+++ b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLProjectionFinder.java
@@ -39,18 +39,18 @@ public class SQLProjectionFinder extends QueryModelVisitorBase<RuntimeException>
 
     List<ExtensionElem> elements = new ArrayList<>();
 
-    Var needle;
+    String needle;
 
     boolean found = false;
 
-    public SQLProjectionFinder(TupleExpr expr, Var needle) {
+    public SQLProjectionFinder(TupleExpr expr, String needle) {
         this.needle = needle;
         expr.visit(this);
     }
 
     @Override
     public void meet(ExtensionElem node) throws RuntimeException {
-        if(node.getName().equals(needle.getName())) {
+        if(node.getName().equals(needle)) {
             found = true;
         }
         // don't recurse to the children, as this would project non-grouped elements
@@ -59,7 +59,7 @@ public class SQLProjectionFinder extends QueryModelVisitorBase<RuntimeException>
     @Override
     public void meet(Group node) throws RuntimeException {
         for(String g : node.getGroupBindingNames()) {
-            if(g.equals(needle.getName())) {
+            if(g.equals(needle)) {
                 found = true;
             }
         }
@@ -68,7 +68,7 @@ public class SQLProjectionFinder extends QueryModelVisitorBase<RuntimeException>
 
     @Override
     public void meet(Var node) throws RuntimeException {
-        if(node.equals(needle)) {
+        if(node.getName().equals(needle)) {
             found = true;
         }
     }

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a33f414/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLSubQuery.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLSubQuery.java b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLSubQuery.java
new file mode 100644
index 0000000..1881122
--- /dev/null
+++ b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLSubQuery.java
@@ -0,0 +1,91 @@
+/*
+ * 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;
+
+import org.apache.marmotta.kiwi.persistence.KiWiDialect;
+import org.apache.marmotta.kiwi.sparql.exception.UnsatisfiableQueryException;
+import org.openrdf.query.BindingSet;
+import org.openrdf.query.Dataset;
+import org.openrdf.query.algebra.SubQueryValueOperator;
+import org.openrdf.query.algebra.ValueExpr;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Representation of a simple SPARQL->SQL subquery (no union). We can use a new SQLBuilder to construct the subquery
+ * and add appropriate variable mappings.
+ *
+ * @author Sebastian Schaffert (sschaffert@apache.org)
+ */
+public class SQLSubQuery extends SQLAbstractSubquery {
+
+    private SQLBuilder builder;
+
+    private Set<SQLVariable> variables = new HashSet<>();
+
+    public SQLSubQuery(String alias, SubQueryValueOperator query, BindingSet bindings, Dataset dataset, ValueConverter converter, KiWiDialect dialect) throws UnsatisfiableQueryException {
+        super(alias);
+
+        // we build a full subquery for each of the UNION's arguments
+        builder = new SQLBuilder(query.getSubQuery(), bindings, dataset, converter, dialect);
+
+        for(SQLVariable svl : builder.getVariables().values()) {
+            variables.add(svl);
+        }
+    }
+
+
+    /**
+     * Return the SQL variables used by the subquery; we need this to do proper mapping in the parent query.
+     *
+     * @return
+     */
+    @Override
+    public Set<SQLVariable> getQueryVariables() {
+        return variables;
+    }
+
+    /**
+     * Build the query fragment that can be used in the FROM clause of a SQL query for representing this SPARQL construct.
+     * The fragment will be joined appropriately by the enclosing construct using CROSS JOIN, LEFT JOIN or normal JOIN.
+     *
+     * @return
+     */
+    @Override
+    public String buildFromClause() {
+        StringBuilder fromClause = new StringBuilder();
+        fromClause
+                .append("(")
+                .append(builder.build())
+                .append(") AS ")
+                .append(alias);
+        return fromClause.toString();
+    }
+
+    /**
+     * Return the projection type of an expression in this subquery. Needed for propagation up to the parent.
+     *
+     * @param expr
+     * @return
+     */
+    @Override
+    protected ProjectionType getProjectionType(ValueExpr expr) {
+        return builder.getProjectionType(expr);
+    }
+}

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a33f414/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLUnion.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLUnion.java b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLUnion.java
new file mode 100644
index 0000000..c681216
--- /dev/null
+++ b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLUnion.java
@@ -0,0 +1,173 @@
+/*
+ * 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;
+
+import org.apache.marmotta.kiwi.persistence.KiWiDialect;
+import org.apache.marmotta.kiwi.sparql.exception.UnsatisfiableQueryException;
+import org.openrdf.query.BindingSet;
+import org.openrdf.query.Dataset;
+import org.openrdf.query.algebra.Union;
+import org.openrdf.query.algebra.ValueExpr;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Represents a SPARQL UNION in SQL. Essentially, we translate a SPARQL UNION into a SQL subquery using UNION to
+ * merge the different parts. Mostly, we can use a new SQLBuilder to construct the subquery. However, what needs to be
+ * taken care of is that SQL UNIONS only work when all subqueries have the same columns, but in SPARQL this is not
+ * always the case. Furthermore, we need to map variables from the subquery to variables in the enclosing query
+ * (using the expressions mapping of our SQLVariable).
+ *
+ * TODO: proper variable mapping and conditions (in SQLBuilder)
+ *
+ * @author Sebastian Schaffert (sschaffert@apache.org)
+ */
+public class SQLUnion extends SQLAbstractSubquery {
+
+    private static Logger log = LoggerFactory.getLogger(SQLUnion.class);
+
+    private SQLBuilder left, right;
+
+    private Set<SQLVariable> variables = new HashSet<>();
+
+    public SQLUnion(String alias, Union query, BindingSet bindings, Dataset dataset, ValueConverter converter, KiWiDialect dialect) throws UnsatisfiableQueryException {
+        super(alias);
+
+        // we build a full subquery for each of the UNION's arguments
+        left  = new SQLBuilder(query.getLeftArg(), bindings, dataset, converter, dialect);
+        right = new SQLBuilder(query.getRightArg(), bindings, dataset, converter, dialect);
+
+        // next we make sure that both subqueries share the same SQL variables so the SQL UNION succeeds by
+        // adding NULL aliases for all variables present in one but not the other
+        int c = 0;
+        Map<String,SQLVariable> leftVars = new HashMap<>();
+        for(SQLVariable svl : left.getVariables().values()) {
+            leftVars.put(svl.getName(), svl);
+        }
+
+        Map<String,SQLVariable> rightVars = new HashMap<>();
+        for(SQLVariable svr : right.getVariables().values()) {
+            rightVars.put(svr.getName(), svr);
+        }
+
+        for(SQLVariable svl : leftVars.values()) {
+            if(!rightVars.containsKey(svl.getName())) {
+                String vname = "_u_"+alias+"_" + (++c);
+                SQLVariable svr = new SQLVariable(svl.getName(), vname);
+                svr.getExpressions().add("NULL");
+                svr.setProjectionType(ProjectionType.NODE);
+                right.getVariables().put(vname,svr);
+            }
+            variables.add(svl);
+        }
+
+        for(SQLVariable svr : rightVars.values()) {
+            if(!leftVars.containsKey(svr.getName())) {
+                String vname = "_u_"+alias+"_" + (++c);
+                SQLVariable svl = new SQLVariable(svr.getName(), vname);
+                svl.getExpressions().add("NULL");
+                svl.setProjectionType(ProjectionType.NODE);
+                left.getVariables().put(vname,svl);
+            }
+            variables.add(svr);
+        }
+
+        log.debug("UNION variables: {}", variables);
+    }
+
+
+    /**
+     * Return the SQL variables used by the subquery; we need this to do proper mapping in the parent query.
+     *
+     * @return
+     */
+    @Override
+    public Set<SQLVariable> getQueryVariables() {
+        return variables;
+    }
+
+    /**
+     * Build the query fragment that can be used in the FROM clause of a SQL query for representing this SPARQL construct.
+     * The fragment will be joined appropriately by the enclosing construct using CROSS JOIN, LEFT JOIN or normal JOIN.
+     *
+     * @return
+     */
+    @Override
+    public String buildFromClause() {
+        StringBuilder fromClause = new StringBuilder();
+        fromClause
+                .append("((")
+                .append(left.build())
+                .append(") UNION (")
+                .append(right.build())
+                .append(")) AS ")
+                .append(alias);
+        return fromClause.toString();
+    }
+
+
+    /**
+     * Return the projection type of an expression in this subquery. Needed for propagation up to the parent.
+     *
+     * @param expr
+     * @return
+     */
+    @Override
+    protected ProjectionType getProjectionType(ValueExpr expr) {
+        ProjectionType r = left.getProjectionType(expr);
+        if(r == ProjectionType.STRING) {
+            r = right.getProjectionType(expr);
+        }
+        return r;
+    }
+}
+
+
+/*
+Example:
+
+PREFIX foaf: <http://xmlns.com/foaf/0.1/>
+
+SELECT ?p ?name ?likes WHERE {
+    ?p foaf:name ?name .
+    { ?p foaf:knows ?likes }
+    UNION
+    { ?p foaf:interest ?likes }
+
+}
+
+translated to:
+
+SELECT SUB1.V3 AS V3, P1.object AS V2, P1.subject AS V1
+ FROM triples P1
+ JOIN
+ (
+  (SELECT P2.subject AS V1, P2.object AS V3, P2.predicate AS V2 FROM triples P2 WHERE P2.deleted=false AND P2.predicate = 512217739590426624)
+ UNION
+  (SELECT P3.subject AS V1, P3.object AS V3, NULL AS V2 FROM triples P3 WHERE P3.deleted=false AND P3.predicate = 512217739326185472)
+ ) AS SUB1 ON (P1.subject = SUB1.V1)
+ WHERE P1.deleted = false
+      AND P1.predicate = 512217739124858880
+
+
+ */

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a33f414/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLVariable.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLVariable.java b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLVariable.java
index 0950796..a5dfb76 100644
--- a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLVariable.java
+++ b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/builder/SQLVariable.java
@@ -17,9 +17,9 @@
 
 package org.apache.marmotta.kiwi.sparql.builder;
 
-import org.openrdf.query.algebra.Var;
-
+import java.text.Collator;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.List;
 
 /**
@@ -28,7 +28,7 @@ import java.util.List;
  *
  * @author Sebastian Schaffert (sschaffert@apache.org)
  */
-public class SQLVariable {
+public class SQLVariable  {
 
     /**
      * A map for mapping the SPARQL variable names to internal names used for constructing SQL aliases.
@@ -36,7 +36,7 @@ public class SQLVariable {
      */
     private String name;
 
-    private Var sparqlVariable;
+    private String sparqlName;
 
     /**
      * A map for mapping SPARQL variables to field names; each variable might have one or more field names,
@@ -59,9 +59,9 @@ public class SQLVariable {
      */
     private ProjectionType projectionType = ProjectionType.NONE;
 
-    public SQLVariable(String name, Var sparqlVariable) {
+    public SQLVariable(String name, String sparqlName) {
         this.name = name;
-        this.sparqlVariable = sparqlVariable;
+        this.sparqlName = sparqlName;
 
         this.aliases = new ArrayList<>();
         this.expressions = new ArrayList<>();
@@ -71,8 +71,8 @@ public class SQLVariable {
         return name;
     }
 
-    public Var getSparqlVariable() {
-        return sparqlVariable;
+    public String getSparqlName() {
+        return sparqlName;
     }
 
     public List<String> getAliases() {
@@ -100,13 +100,45 @@ public class SQLVariable {
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        SQLVariable that = (SQLVariable) o;
+
+        if (!sparqlName.equals(that.sparqlName)) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return sparqlName.hashCode();
+    }
+
+    @Override
     public String toString() {
         return "Variable{" +
                 "SQL name='" + name + '\'' +
-                ", SPARQL name=" + sparqlVariable.getName() +
+                ", SPARQL name=" + sparqlName +
                 ", aliases=" + aliases +
                 ", expressions=" + expressions +
                 ", projectionType=" + projectionType +
                 '}';
     }
+
+
+    public static final Comparator<SQLVariable> sparqlNameComparator = new Comparator<SQLVariable>() {
+        @Override
+        public int compare(SQLVariable l, SQLVariable r) {
+            return Collator.getInstance().compare(l.getSparqlName(), r.getSparqlName());
+        }
+    };
+
+    public static final Comparator<SQLVariable> sqlNameComparator = new Comparator<SQLVariable>() {
+        @Override
+        public int compare(SQLVariable l, SQLVariable r) {
+            return Collator.getInstance().compare(l.getName(), r.getName());
+        }
+    };
 }

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a33f414/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 67f3d84..89da038 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
@@ -68,6 +68,34 @@ public class KiWiEvaluationStrategyImpl extends EvaluationStrategyImpl{
     }
 
     @Override
+    public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(Union union, BindingSet bindings) throws QueryEvaluationException {
+        if(Thread.currentThread().isInterrupted()) {
+            throw new QueryEvaluationException("SPARQL evaluation has already been cancelled");
+        }
+
+        if(isSupported(union)) {
+            log.debug("applying KiWi UNION optimizations on SPARQL query ...");
+
+            try {
+                return new ExceptionConvertingIteration<BindingSet, QueryEvaluationException>(connection.evaluateNative(union, bindings, dataset)) {
+                    @Override
+                    protected QueryEvaluationException convert(Exception e) {
+                        return new QueryEvaluationException(e);
+                    }
+                };
+            } catch (SQLException e) {
+                throw new QueryEvaluationException(e.getMessage(),e);
+            } catch (IllegalArgumentException e) {
+                throw new QueryEvaluationException(e.getMessage(),e);
+            } catch (InterruptedException e) {
+                throw new QueryInterruptedException(e.getMessage());
+            }
+        } else {
+            return super.evaluate(union, bindings);
+        }
+    }
+
+    @Override
     public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(Extension order, BindingSet bindings) throws QueryEvaluationException {
         if(Thread.currentThread().isInterrupted()) {
             throw new QueryEvaluationException("SPARQL evaluation has already been cancelled");
@@ -306,6 +334,8 @@ public class KiWiEvaluationStrategyImpl extends EvaluationStrategyImpl{
             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 Order) {
             for(OrderElem elem : ((Order) expr).getElements()) {
                 if(!isSupported(elem.getExpr())) {

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a33f414/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/exception/UnsatisfiableQueryException.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/exception/UnsatisfiableQueryException.java b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/exception/UnsatisfiableQueryException.java
index 308209a..5022bec 100644
--- a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/exception/UnsatisfiableQueryException.java
+++ b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/exception/UnsatisfiableQueryException.java
@@ -22,7 +22,7 @@ package org.apache.marmotta.kiwi.sparql.exception;
  *
  * @author Sebastian Schaffert (sschaffert@apache.org)
  */
-public class UnsatisfiableQueryException extends Exception {
+public class UnsatisfiableQueryException extends RuntimeException {
 
     public UnsatisfiableQueryException() {
     }

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a33f414/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/persistence/KiWiSparqlConnection.java
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/persistence/KiWiSparqlConnection.java b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/persistence/KiWiSparqlConnection.java
index 1bd008c..0ad6a90 100644
--- a/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/persistence/KiWiSparqlConnection.java
+++ b/libraries/kiwi/kiwi-sparql/src/main/java/org/apache/marmotta/kiwi/sparql/persistence/KiWiSparqlConnection.java
@@ -133,26 +133,26 @@ public class KiWiSparqlConnection {
                             SQLVariable sv = vars.get(i);
                             if(nodes[i] != null) {
                                 // resolved node
-                                resultRow.addBinding(sv.getSparqlVariable().getName(), nodes[i]);
+                                resultRow.addBinding(sv.getSparqlName(), nodes[i]);
                             } else if(sv.getProjectionType() != ProjectionType.NONE) {
                                 // literal value
                                 String value = row.getString(sv.getName());
                                 if(value != null) {
                                     switch (sv.getProjectionType()) {
                                         case URI:
-                                            resultRow.addBinding(sv.getSparqlVariable().getName(), new URIImpl(value));
+                                            resultRow.addBinding(sv.getSparqlName(), new URIImpl(value));
                                             break;
                                         case INT:
-                                            resultRow.addBinding(sv.getSparqlVariable().getName(), new LiteralImpl(value, XSD.Integer));
+                                            resultRow.addBinding(sv.getSparqlName(), new LiteralImpl(value, XSD.Integer));
                                             break;
                                         case DOUBLE:
-                                            resultRow.addBinding(sv.getSparqlVariable().getName(), new LiteralImpl(value, XSD.Double));
+                                            resultRow.addBinding(sv.getSparqlName(), new LiteralImpl(value, XSD.Double));
                                             break;
                                         case STRING:
-                                            resultRow.addBinding(sv.getSparqlVariable().getName(), new LiteralImpl(value));
+                                            resultRow.addBinding(sv.getSparqlName(), new LiteralImpl(value));
                                             break;
                                         default:
-                                            resultRow.addBinding(sv.getSparqlVariable().getName(), new LiteralImpl(value));
+                                            resultRow.addBinding(sv.getSparqlName(), new LiteralImpl(value));
                                             break;
                                     }
                                 }

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a33f414/libraries/kiwi/kiwi-sparql/src/test/resources/org/apache/marmotta/kiwi/sparql/test/query26.sparql
----------------------------------------------------------------------
diff --git a/libraries/kiwi/kiwi-sparql/src/test/resources/org/apache/marmotta/kiwi/sparql/test/query26.sparql b/libraries/kiwi/kiwi-sparql/src/test/resources/org/apache/marmotta/kiwi/sparql/test/query26.sparql
index 9bf0a06..4dfd877 100644
--- a/libraries/kiwi/kiwi-sparql/src/test/resources/org/apache/marmotta/kiwi/sparql/test/query26.sparql
+++ b/libraries/kiwi/kiwi-sparql/src/test/resources/org/apache/marmotta/kiwi/sparql/test/query26.sparql
@@ -17,9 +17,9 @@
 #
 PREFIX foaf: <http://xmlns.com/foaf/0.1/>
 
-SELECT ?p ?name ?likes WHERE {
+SELECT ?p ?name ?likes ?fname WHERE {
     ?p foaf:name ?name .
-    { ?p foaf:knows ?likes }
+    { ?p foaf:knows ?likes . ?likes foaf:knows ?fname }
     UNION
     { ?p foaf:interest ?likes }
 


Mime
View raw message