diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Args.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Args.java index d6379b908..390282138 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Args.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Args.java @@ -18,6 +18,10 @@ */ package org.apache.openjpa.jdbc.kernel.exps; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + import org.apache.openjpa.jdbc.sql.Joins; import org.apache.openjpa.jdbc.sql.Result; import org.apache.openjpa.jdbc.sql.SQLBuffer; @@ -43,18 +47,21 @@ public class Args * Constructor. Supply values being combined. */ public Args(Val val1, Val val2) { - int len1 = (val1 instanceof Args) ? ((Args) val1)._args.length : 1; - int len2 = (val2 instanceof Args) ? ((Args) val2)._args.length : 1; - - _args = new Val[len1 + len2]; - if (val1 instanceof Args) - System.arraycopy(((Args) val1)._args, 0, _args, 0, len1); - else - _args[0] = val1; - if (val2 instanceof Args) - System.arraycopy(((Args) val2)._args, 0, _args, len1, len2); - else - _args[len1] = val2; + this(new Val[]{val1, val2}); + } + + public Args (Val... values) { + List list = new ArrayList(); + if (values != null) { + for (Val v : values) { + if (v instanceof Args) { + list.addAll(Arrays.asList(((Args)v)._args)); + } else { + list.add(v); + } + } + } + _args = list.toArray(new Val[list.size()]); } /** @@ -166,6 +173,12 @@ public class Args public void appendTo(Select sel, ExpContext ctx, ExpState state, SQLBuffer sql, int index) { + ArgsExpState astate = (ArgsExpState) state; + for (int i = 0; i < _args.length; i++) { + _args[i].appendTo(sel, ctx, astate.states[i], sql, index); + if (i < _args.length-1) + sql.append(", "); + } } public void appendIsEmpty(Select sel, ExpContext ctx, ExpState state, diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/DatastoreFunction.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/DatastoreFunction.java new file mode 100644 index 000000000..05a5853d3 --- /dev/null +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/DatastoreFunction.java @@ -0,0 +1,44 @@ +/* + * 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.openjpa.jdbc.kernel.exps; + +import org.apache.openjpa.kernel.exps.Arguments; + +/** + * A unary operator that executes a datastore specific function with zero or more arguments. + * + * @author Pinaki Poddar + * + */ +@SuppressWarnings("serial") +public class DatastoreFunction extends UnaryOp { + private final String _functionName; + + public DatastoreFunction(String name, Class resultType, Arguments args) { + super((Val)args); + _functionName = name; + setImplicitType(resultType); + } + + @Override + protected String getOperator() { + return _functionName; + } + +} diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/JDBCExpressionFactory.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/JDBCExpressionFactory.java index 954d0f1c8..9ff42702d 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/JDBCExpressionFactory.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/JDBCExpressionFactory.java @@ -297,6 +297,17 @@ public class JDBCExpressionFactory public Arguments newArgumentList(Value v1, Value v2) { return new Args((Val) v1, (Val) v2); } + + public Arguments newArgumentList(Value... vs) { + if (vs == null) + return new Args(null); + Val[] vals = new Val[vs.length]; + int i = 0; + for (Value v : vs) { + vals[i++] = (Val)v; + } + return new Args(vals); + } public Value newUnboundVariable(String name, Class type) { return new Variable(name, type); @@ -503,4 +514,8 @@ public class JDBCExpressionFactory val2 = getLiteralRawString(val2); return new NullIfExpression((Val) val1, (Val) val2); } + + public Value newFunction(String functionName, Class resultType, Value... args) { + return new DatastoreFunction(functionName, resultType, newArgumentList(args)); + } } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Args.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Args.java index 55758b405..378cb5063 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Args.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Args.java @@ -32,24 +32,30 @@ class Args extends Val implements Arguments { - private final List _args = new ArrayList(3); + private final List _args = new ArrayList(3); /** * Constructor. Supply values being combined. */ public Args(Value val1, Value val2) { - if (val1 instanceof Args) - _args.addAll(((Args) val1)._args); - else - _args.add(val1); - if (val2 instanceof Args) - _args.addAll(((Args) val2)._args); - else - _args.add(val2); + this(new Value[]{val1, val2}); + } + + public Args(Value...values) { + if (values == null) { + return; + } + for (Value v : values) { + if (v instanceof Args) { + _args.addAll(((Args)v)._args); + } else { + _args.add(v); + } + } } public Value[] getValues() { - return (Value[]) _args.toArray(new Value[_args.size()]); + return _args.toArray(new Value[_args.size()]); } public Class getType() { diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExpressionFactory.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExpressionFactory.java index 524e1f013..72f5a0af7 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExpressionFactory.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExpressionFactory.java @@ -269,6 +269,12 @@ public interface ExpressionFactory { * of which may itself be an argument list. */ public Arguments newArgumentList(Value arg1, Value arg2); + + /** + * Return a function argument list consisting of the given values, any + * of which may itself be an argument list. + */ + public Arguments newArgumentList(Value... values); /** * Return an unbound variable. This method will only be called once for @@ -481,4 +487,9 @@ public interface ExpressionFactory { * a {@link Number}, {@link String}, or {@link Boolean} instance. */ public Literal newTypeLiteral(Object val, int parseType); + + /** + * Return a value representing the given datastore function with the given arguments. + */ + public Value newFunction(String functionName, Class resultType, Value... args); } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/InMemoryExpressionFactory.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/InMemoryExpressionFactory.java index 95d67b324..1b7458600 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/InMemoryExpressionFactory.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/InMemoryExpressionFactory.java @@ -525,6 +525,10 @@ public class InMemoryExpressionFactory public Arguments newArgumentList(Value val1, Value val2) { return new Args(val1, val2); } + + public Arguments newArgumentList(Value... values) { + return new Args(values); + } public Value newUnboundVariable(String name, Class type) { UnboundVariable var = new UnboundVariable(type); @@ -788,4 +792,8 @@ public class InMemoryExpressionFactory public Value nullIfExpression(Value val1, Value val2) { return new NullIf((Val) val1, (Val) val2); } + + public Value newFunction(String functionName, Class resultType, Value... args) { + throw new AbstractMethodError(); + } } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/CriteriaTest.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/CriteriaTest.java index 26e811027..83a732961 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/CriteriaTest.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/CriteriaTest.java @@ -328,6 +328,10 @@ public abstract class CriteriaTest extends TestCase { assertFalse(auditor.getSQLs().isEmpty()); return auditor.getSQLs(); } + + void executeAndCompareSQL(CriteriaQuery q, String expectedSQL) { + executeAndCompareSQL(em.createQuery(q), expectedSQL); + } String extractSQL(Exception e) { Throwable t = e.getCause(); diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/TestTypesafeCriteria.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/TestTypesafeCriteria.java index fb35b59e3..34e4b961f 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/TestTypesafeCriteria.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/TestTypesafeCriteria.java @@ -802,4 +802,58 @@ public class TestTypesafeCriteria extends CriteriaTest { assertEquivalence(q, jpql); } + + /** + * 0-arg function works only if there is a other projection items to determine the table to select from. + */ + public void testFunctionWithNoArgument() { + String jpql = "SELECT c.balanceOwed FROM Customer c"; + String sql = "SELECT CURRENT_USER(), t0.balanceOwed FROM CR_CUST t0"; + + CriteriaQuery q = cb.createTupleQuery(); + Root c = q.from(Customer.class); + q.multiselect(cb.function("CURRENT_USER", String.class, (Expression[])null), c.get(Customer_.balanceOwed)); + + executeAndCompareSQL(q, sql); +// assertEquivalence(q, jpql); + } + + public void testFunctionWithOneArgument() { + String jpql = "SELECT MAX(c.balanceOwed) FROM Customer c"; + + CriteriaQuery q = cb.createTupleQuery(); + Root c = q.from(Customer.class); + q.multiselect(cb.function("MAX", Integer.class, c.get(Customer_.balanceOwed))); + + assertEquivalence(q, jpql); + + } + + public void testFunctionWithTwoArgument() { + String jpql = "SELECT MOD(c.balanceOwed,10) FROM Customer c"; + + CriteriaQuery q = cb.createTupleQuery(); + Root c = q.from(Customer.class); + q.multiselect(cb.function("MOD", Integer.class, c.get(Customer_.balanceOwed), cb.literal(10))); + + assertEquivalence(q, jpql); + + } + + public void testFunctionWithFunctionArgumentInOrderBy() { + String jpql = "SELECT MOD(c.balanceOwed,10) FROM Customer c WHERE LENGTH(c.name)>3 ORDER BY LENGTH(c.name)"; + String sql = "SELECT MOD(t0.balanceOwed, ?), LENGTH(t0.name) FROM CR_CUST t0 WHERE (LENGTH(t0.name) > ?) " + + "ORDER BY LENGTH(t0.name) ASC"; + + CriteriaQuery q = cb.createTupleQuery(); + Root c = q.from(Customer.class); + Expression nameLength = cb.function("LENGTH", Integer.class, c.get(Customer_.name)); + q.multiselect(cb.function("MOD", Integer.class, c.get(Customer_.balanceOwed), cb.literal(10))); + q.where(cb.greaterThan(nameLength, 3)); + q.orderBy(cb.asc(nameLength)); + + executeAndCompareSQL(q, sql); + + } + } diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaBuilder.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaBuilder.java index 646388ee3..8b647d978 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaBuilder.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaBuilder.java @@ -246,7 +246,7 @@ public class CriteriaBuilder implements QueryBuilder, ExpressionParser { public Expression function(String name, Class type, Expression... args) { - throw new AbstractMethodError(); + return new Expressions.DatabaseFunction(name, type, args); } public Predicate ge(Expression x, diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaQueryImpl.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaQueryImpl.java index b3f0ec18a..857258d57 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaQueryImpl.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaQueryImpl.java @@ -81,8 +81,7 @@ public class CriteriaQueryImpl implements CriteriaQuery, AliasContext { private static String ALIAS_BASE = "autoAlias"; // Auto-generated Parameter name - private int autoParameterCount = 0; - private static String PARAM_BASE = "autoParam"; + private static String PARAM_BASE = "*autoParam"; private Map,Value> _variables = new HashMap, Value>(); private Map,Value> _values = new HashMap, Value>(); @@ -194,9 +193,13 @@ public class CriteriaQueryImpl implements CriteriaQuery, AliasContext { if (_paramTypes == null) { _paramTypes = new LinkedHashMap, Class>(); } + if (_paramTypes.containsKey(p)) { + return; + } + p.setIndex(_paramTypes.size()); _paramTypes.put(p, p.getJavaType()); if (p.getName() == null) - p.assignAutoName(PARAM_BASE + (++autoParameterCount)); + p.assignAutoName(PARAM_BASE + p.getIndex()); } public Set> getParameters() { diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/Expressions.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/Expressions.java index 33539ac5f..79c49ce39 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/Expressions.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/Expressions.java @@ -47,12 +47,12 @@ public class Expressions { * Handles null expression. */ static Value toValue(ExpressionImpl e, ExpressionFactory factory, - MetamodelImpl model, CriteriaQueryImpl q) { + MetamodelImpl model, CriteriaQueryImpl q) { return (e == null) ? factory.getNull() : e.toValue(factory, model, q); } static void setImplicitTypes(Value v1, Value v2, Class expected, - CriteriaQueryImpl q) { + CriteriaQueryImpl q) { JPQLExpressionBuilder.setImplicitTypes(v1, v2, expected, q.getMetamodel(), q.getParameterTypes(), q.toString()); } @@ -96,8 +96,26 @@ public class Expressions { e2 = (ExpressionImpl)y; } } - + /** + * Functional Expression applies a function on a list of input Expressions. + * + * + * @param the type of the resultant expression + */ + public abstract static class FunctionalExpression extends ExpressionImpl{ + protected final ExpressionImpl[] args; + + public FunctionalExpression(Class t, Expression... args) { + super(t); + int len = args == null ? 0 : args.length; + this.args = new ExpressionImpl[len]; + for (int i = 0; args != null && i < args.length; i++) { + this.args[i] = (ExpressionImpl)args[i]; + } + } + } + /** * Binary Logical Expression applies a binary function on a pair of * input Expression to generate a Predicate. @@ -216,6 +234,24 @@ public class Expressions { } } + public static class DatabaseFunction extends FunctionalExpression { + private final String functionName; + private final Class resultType; + public DatabaseFunction(String name, Class resultType, Expression... exps) { + super(resultType, exps); + functionName = name; + this.resultType = resultType; + } + + @Override + public Value toValue(ExpressionFactory factory, MetamodelImpl model, + CriteriaQueryImpl q) { + return factory.newFunction(functionName, getJavaType(), + new Expressions.ListArgument(resultType, args).toValue(factory, model, q)); + } + } + + public static class Type extends UnaryFunctionalExpression> { public Type(PathImpl path) { @@ -1345,5 +1381,31 @@ public class Expressions { return e; } } + + /** + * An expression that is composed of one or more expressions. + * + * @param + */ + public static class ListArgument extends ExpressionImpl { + private final ExpressionImpl[] _args; + public ListArgument(Class cls, ExpressionImpl... args) { + super(cls); + _args = args; + } + + @Override + public org.apache.openjpa.kernel.exps.Arguments toValue( + ExpressionFactory factory, MetamodelImpl model, CriteriaQueryImpl q) { + org.apache.openjpa.kernel.exps.Value[] kvs = new org.apache.openjpa.kernel.exps.Value[_args.length]; + int i = 0; + for (ExpressionImpl arg : _args) { + kvs[i++] = arg.toValue(factory, model, q); + } + org.apache.openjpa.kernel.exps.Arguments e = factory.newArgumentList(kvs); + e.setImplicitType(getJavaType()); + return e; + } + } } diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/ParameterExpressionImpl.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/ParameterExpressionImpl.java index a95dcb731..98e298e79 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/ParameterExpressionImpl.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/ParameterExpressionImpl.java @@ -51,6 +51,7 @@ public class ParameterExpressionImpl extends ExpressionImpl implements ParameterExpression, QueryParameter { private String _autoName = null; + private int _index = 0; // index of the parameter as seen by the kernel private final ParameterImpl _delegate; /** @@ -127,6 +128,14 @@ public class ParameterExpressionImpl extends ExpressionImpl public final boolean isPositional() { return false; } + + void setIndex(int index) { + _index = index; + } + + public int getIndex() { + return _index; + } public final boolean isValueAssignable(Object v) { return _delegate.isValueAssignable(v); @@ -155,6 +164,7 @@ public class ParameterExpressionImpl extends ExpressionImpl ? factory.newCollectionValuedParameter(paramKey, clzz) : factory.newParameter(paramKey, clzz); param.setMetaData(meta); + param.setIndex(_index); return param; }