OPENJPA-1195: Datastore function for in general usage as well as JPA 2.0. CriteriaQuery Parameter indexing during registration.

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@797257 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Pinaki Poddar 2009-07-23 23:01:55 +00:00
parent 001c9e4fd2
commit 7099c084fb
12 changed files with 259 additions and 29 deletions

View File

@ -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<Val> list = new ArrayList<Val>();
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,

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -32,24 +32,30 @@ class Args
extends Val
implements Arguments {
private final List _args = new ArrayList(3);
private final List<Value> _args = new ArrayList<Value>(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() {

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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();

View File

@ -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<Tuple> q = cb.createTupleQuery();
Root<Customer> 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<Tuple> q = cb.createTupleQuery();
Root<Customer> 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<Tuple> q = cb.createTupleQuery();
Root<Customer> 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<Tuple> q = cb.createTupleQuery();
Root<Customer> c = q.from(Customer.class);
Expression<Integer> 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);
}
}

View File

@ -246,7 +246,7 @@ public class CriteriaBuilder implements QueryBuilder, ExpressionParser {
public <T> Expression<T> function(String name, Class<T> type,
Expression<?>... args) {
throw new AbstractMethodError();
return new Expressions.DatabaseFunction(name, type, args);
}
public Predicate ge(Expression<? extends Number> x,

View File

@ -81,8 +81,7 @@ public class CriteriaQueryImpl<T> implements CriteriaQuery<T>, 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<Selection<?>,Value> _variables = new HashMap<Selection<?>, Value>();
private Map<Selection<?>,Value> _values = new HashMap<Selection<?>, Value>();
@ -194,9 +193,13 @@ public class CriteriaQueryImpl<T> implements CriteriaQuery<T>, AliasContext {
if (_paramTypes == null) {
_paramTypes = new LinkedHashMap<ParameterExpression<?>, 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<ParameterExpression<?>> getParameters() {

View File

@ -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 <X> the type of the resultant expression
*/
public abstract static class FunctionalExpression<X> extends ExpressionImpl<X>{
protected final ExpressionImpl<?>[] args;
public FunctionalExpression(Class<X> 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<T> extends FunctionalExpression<T> {
private final String functionName;
private final Class<T> resultType;
public DatabaseFunction(String name, Class<T> 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<X>
extends UnaryFunctionalExpression<Class<? extends X>> {
public Type(PathImpl<?, X> path) {
@ -1345,5 +1381,31 @@ public class Expressions {
return e;
}
}
/**
* An expression that is composed of one or more expressions.
*
* @param <T>
*/
public static class ListArgument<T> extends ExpressionImpl<T> {
private final ExpressionImpl<?>[] _args;
public ListArgument(Class<T> 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;
}
}
}

View File

@ -51,6 +51,7 @@ public class ParameterExpressionImpl<T> extends ExpressionImpl<T>
implements ParameterExpression<T>, QueryParameter<T> {
private String _autoName = null;
private int _index = 0; // index of the parameter as seen by the kernel
private final ParameterImpl<T> _delegate;
/**
@ -127,6 +128,14 @@ public class ParameterExpressionImpl<T> extends ExpressionImpl<T>
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<T> extends ExpressionImpl<T>
? factory.newCollectionValuedParameter(paramKey, clzz)
: factory.newParameter(paramKey, clzz);
param.setMetaData(meta);
param.setIndex(_index);
return param;
}