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.