From d49fdb1ea206bc0a53c0afa9fa207c1a3ed1931d Mon Sep 17 00:00:00 2001 From: Catalina Wei Date: Fri, 23 Jan 2009 01:19:47 +0000 Subject: [PATCH] OPENJPA-856 JPQ2 JPQL add support for entity type expression git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@736881 13f79535-47bb-0310-9956-ffa450edef68 --- .../openjpa/jdbc/kernel/exps/AbstractVal.java | 5 + .../jdbc/kernel/exps/InExpression.java | 6 +- .../kernel/exps/JDBCExpressionFactory.java | 8 + .../openjpa/jdbc/kernel/exps/PCPath.java | 28 +++ .../openjpa/jdbc/kernel/exps/Param.java | 24 ++- .../apache/openjpa/jdbc/kernel/exps/Type.java | 84 ++++++++ .../openjpa/jdbc/kernel/exps/TypeLit.java | 127 ++++++++++++ .../apache/openjpa/jdbc/kernel/exps/Val.java | 6 + .../openjpa/jdbc/meta/FieldMapping.java | 4 + .../openjpa/jdbc/meta/FieldStrategy.java | 5 + .../meta/strats/AbstractFieldStrategy.java | 4 + .../kernel/exps/ExpressionFactory.java | 13 ++ .../exps/InMemoryExpressionFactory.java | 8 + .../apache/openjpa/kernel/exps/Literal.java | 1 + .../org/apache/openjpa/kernel/exps/Type.java | 63 ++++++ .../apache/openjpa/kernel/exps/TypeLit.java | 72 +++++++ .../kernel/jpql/JPQLExpressionBuilder.java | 102 +++++++++- .../org/apache/openjpa/kernel/jpql/JPQL.jjt | 76 ++++--- .../openjpa/kernel/jpql/localizer.properties | 3 +- .../expressions/TestEntityTypeExpression.java | 192 ++++++++++++++++++ .../TestJPQLScalarExpressions.java | 18 +- 21 files changed, 805 insertions(+), 44 deletions(-) create mode 100644 openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Type.java create mode 100644 openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/TypeLit.java create mode 100644 openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Type.java create mode 100644 openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/TypeLit.java create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/expressions/TestEntityTypeExpression.java diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/AbstractVal.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/AbstractVal.java index 2b74b79e4..0b1314830 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/AbstractVal.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/AbstractVal.java @@ -79,6 +79,11 @@ abstract class AbstractVal sql.append("1"); } + public void appendType(Select sel, ExpContext ctx, ExpState state, + SQLBuffer sql) { + sql.append("1"); + } + public void appendSize(Select sel, ExpContext ctx, ExpState state, SQLBuffer sql) { sql.append("1"); diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/InExpression.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/InExpression.java index 3f5a31bcf..d662df604 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/InExpression.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/InExpression.java @@ -91,7 +91,11 @@ class InExpression public void appendTo(Select sel, ExpContext ctx, ExpState state, SQLBuffer buf) { InExpState istate = (InExpState) state; - _const.calculateValue(sel, ctx, istate.constantState, null, null); + if (_val instanceof Type) + _const.calculateValue(sel, ctx, istate.constantState, _val, + istate.valueState); + else + _const.calculateValue(sel, ctx, istate.constantState, null, null); _val.calculateValue(sel, ctx, istate.valueState, null, null); List list = null; 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 0de777ff8..4eff9a987 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 @@ -247,6 +247,10 @@ public class JDBCExpressionFactory return new Lit(val, ptype); } + public Literal newTypeLiteral(Object val, int ptype) { + return new TypeLit(val, ptype); + } + public Value getThis() { return new PCPath(_type); } @@ -396,6 +400,10 @@ public class JDBCExpressionFactory return new Index((Val) val); } + public Value type(Value val) { + return new Type((Val) val); + } + public Value getObjectId(Value val) { if (val instanceof Const) return new ConstGetObjectId((Const) val); diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/PCPath.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/PCPath.java index c013141ea..a99f0ffa3 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/PCPath.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/PCPath.java @@ -27,6 +27,7 @@ import java.util.ListIterator; import org.apache.commons.lang.ObjectUtils; import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration; import org.apache.openjpa.jdbc.meta.ClassMapping; +import org.apache.openjpa.jdbc.meta.Discriminator; import org.apache.openjpa.jdbc.meta.FieldMapping; import org.apache.openjpa.jdbc.meta.ValueMapping; import org.apache.openjpa.jdbc.schema.Column; @@ -814,6 +815,33 @@ public class PCPath pstate.field.appendIndex(sql, sel, pstate.joins);; } + public void appendType(Select sel, ExpContext ctx, ExpState state, + SQLBuffer sql) { + Discriminator disc = null; + ClassMapping sup = _class; + while (sup.getMappedPCSuperclassMapping() != null) + sup = sup.getMappedPCSuperclassMapping(); + + disc = sup.getDiscriminator(); + + Column[] cols = null; + if (disc != null) + cols = disc.getColumns(); + else + cols = getColumns(state); + + if (cols == null) { + sql.append("1"); + return; + } + + for (int i = 0; i < cols.length; i++) { + if (i > 0) + sql.append(", "); + sql.append(sel.getColumnAlias(cols[i], state.joins)); + } + } + public void appendSize(Select sel, ExpContext ctx, ExpState state, SQLBuffer sql) { PathExpState pstate = (PathExpState) state; diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Param.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Param.java index 85d1d42d2..8f256d8db 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Param.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Param.java @@ -22,6 +22,7 @@ import java.util.Collection; import java.util.Map; import org.apache.openjpa.jdbc.meta.ClassMapping; +import org.apache.openjpa.jdbc.meta.Discriminator; import org.apache.openjpa.jdbc.sql.SQLBuffer; import org.apache.openjpa.jdbc.sql.Select; import org.apache.openjpa.kernel.Filters; @@ -82,6 +83,12 @@ public class Param return Filters.convert(params[_idx], getType()); } + public Object getValue(ExpContext ctx, ExpState state) { + ParamExpState pstate = (ParamExpState) state; + return (pstate.discValue != null) ? pstate.discValue : + getValue(ctx.params); + } + public Object getSQLValue(Select sel, ExpContext ctx, ExpState state) { return ((ParamExpState) state).sqlValue; } @@ -97,7 +104,10 @@ public class Param extends ConstExpState { public Object sqlValue = null; - public int otherLength = 1; + public int otherLength = 1; + public ClassMapping mapping = null; + public Discriminator disc = null; + public Object discValue = null; } public void calculateValue(Select sel, ExpContext ctx, ExpState state, @@ -108,6 +118,14 @@ public class Param if (other != null && !_container) { pstate.sqlValue = other.toDataStoreValue(sel, ctx, otherState, val); pstate.otherLength = other.length(sel, ctx, otherState); + if (other instanceof Type) { + pstate.mapping = ctx.store.getConfiguration(). + getMappingRepositoryInstance().getMapping((Class) val, + ctx.store.getContext().getClassLoader(), true); + pstate.disc = pstate.mapping.getDiscriminator(); + pstate.discValue = pstate.disc != null ? pstate.disc.getValue() + : null; + } } else if (ImplHelper.isManageable(val)) { ClassMapping mapping = ctx.store.getConfiguration(). getMappingRepositoryInstance().getMapping(val.getClass(), @@ -125,6 +143,10 @@ public class Param if (pstate.otherLength > 1) sql.appendValue(((Object[]) pstate.sqlValue)[index], pstate.getColumn(index)); + else if (pstate.cols != null) + sql.appendValue(pstate.sqlValue, pstate.getColumn(index)); + else if (pstate.discValue != null) + sql.appendValue(pstate.discValue); else sql.appendValue(pstate.sqlValue, pstate.getColumn(index)); } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Type.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Type.java new file mode 100644 index 000000000..e6d70b020 --- /dev/null +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Type.java @@ -0,0 +1,84 @@ +/* + * 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 java.sql.SQLException; + +import org.apache.openjpa.jdbc.meta.ClassMapping; +import org.apache.openjpa.jdbc.meta.Discriminator; +import org.apache.openjpa.jdbc.sql.Result; +import org.apache.openjpa.jdbc.sql.SQLBuffer; +import org.apache.openjpa.jdbc.sql.Select; +import org.apache.openjpa.util.InternalException; + +/** + * Entity Type expression. + * + * @author Catalina Wei + */ +class Type + extends UnaryOp { + + Discriminator _disc = null; + + public Type(Val val) { + super(val); + setMetaData(val.getMetaData()); + _disc = ((ClassMapping) getMetaData()).getDiscriminator(); + } + + public ExpState initialize(Select sel, ExpContext ctx, int flags) { + // initialize the value with a null test + return initializeValue(sel, ctx, NULL_CMP); + } + + public Object load(ExpContext ctx, ExpState state, Result res) + throws SQLException { + Object type = getValue().load(ctx, state, res); + return type.getClass(); + } + + public void calculateValue(Select sel, ExpContext ctx, ExpState state, + Val other, ExpState otherState) { + super.calculateValue(sel, ctx, state, null, null); + if (_disc != null) + _disc.select(sel, (ClassMapping) getMetaData()); + } + + public void select(Select sel, ExpContext ctx, ExpState state, + boolean pks) { + getValue().select(sel, ctx, state, pks); + } + + public void appendTo(Select sel, ExpContext ctx, ExpState state, + SQLBuffer sql, int index) { + getValue().calculateValue(sel, ctx, state, null, null); + getValue().appendType(sel, ctx, state, sql); + sel.append(sql, state.joins); + } + + protected Class getType(Class c) { + return Class.class; + } + + protected String getOperator() { + // since we override appendTo(), this method should never be called + throw new InternalException(); + } +} diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/TypeLit.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/TypeLit.java new file mode 100644 index 000000000..678ef3b59 --- /dev/null +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/TypeLit.java @@ -0,0 +1,127 @@ +/* + * 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.jdbc.meta.ClassMapping; +import org.apache.openjpa.jdbc.meta.Discriminator; +import org.apache.openjpa.jdbc.sql.SQLBuffer; +import org.apache.openjpa.jdbc.sql.Select; +import org.apache.openjpa.kernel.Filters; +import org.apache.openjpa.kernel.exps.Literal; +import org.apache.openjpa.meta.JavaTypes; + +/** + * A type literal value. + * + * @author Catalina Wei + */ +public class TypeLit + extends Const + implements Literal { + + private Object _val; + private int _ptype; + + /** + * Constructor. Supply literal value. + */ + public TypeLit(Object val, int ptype) { + _val = val; + _ptype = ptype; + } + + public Class getType() { + return (_val == null) ? Object.class : _val.getClass(); + } + + public void setImplicitType(Class type) { + _val = Filters.convert(_val, type); + } + + public int getParseType() { + return _ptype; + } + + public Object getValue() { + return _val; + } + + public void setValue(Object val) { + _val = val; + } + + public Object getValue(Object[] params) { + return getValue(); + } + + public ExpState initialize(Select sel, ExpContext ctx, int flags) { + return new LitExpState(); + } + + /** + * Expression state. + */ + private static class LitExpState + extends ConstExpState { + + public Object sqlValue; + public int otherLength; + public ClassMapping mapping = null; + public Discriminator disc = null; + public Object discValue = null; + } + + public void calculateValue(Select sel, ExpContext ctx, ExpState state, + Val other, ExpState otherState) { + super.calculateValue(sel, ctx, state, other, otherState); + LitExpState lstate = (LitExpState) state; + lstate.mapping = (ClassMapping) getMetaData(); + lstate.disc = lstate.mapping.getDiscriminator(); + lstate.discValue = lstate.disc != null ? lstate.disc.getValue() : + null; + sel.select(((ClassMapping) getMetaData()).getPrimaryKeyColumns(), + lstate.joins); + } + + public void appendTo(Select sel, ExpContext ctx, ExpState state, + SQLBuffer sql, int index) { + LitExpState lstate = (LitExpState) state; + if (lstate.otherLength > 1) + sql.appendValue(((Object[]) lstate.sqlValue)[index], + lstate.getColumn(index)); + else { + if (lstate.discValue != null) + sql.append(getDiscriminator(lstate)); + else + sql.append("1"); + } + } + + private String getDiscriminator(LitExpState lstate) { + StringBuffer disc = new StringBuffer(lstate.discValue.toString()); + switch(lstate.disc.getJavaType()) { + case JavaTypes.INT: + return disc.toString(); + case JavaTypes.CHAR: + case JavaTypes.STRING: + default: + return disc.insert(0, "'").append("'").toString(); + } + } +} diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Val.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Val.java index df0089422..e2b433c53 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Val.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Val.java @@ -152,6 +152,12 @@ public interface Val public void appendIndex(Select sel, ExpContext ctx, ExpState state, SQLBuffer sql); + /** + * Append the SQL checking the type of this value. + */ + public void appendType(Select sel, ExpContext ctx, ExpState state, + SQLBuffer sql); + /** * Append the SQL checking the size of this value. */ diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/FieldMapping.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/FieldMapping.java index 62c2353f8..81767ee87 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/FieldMapping.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/FieldMapping.java @@ -862,6 +862,10 @@ public class FieldMapping assertStrategy().appendIndex(sql, sel, joins); } + public void appendType(SQLBuffer sql, Select sel, Joins joins) { + assertStrategy().appendType(sql, sel, joins); + } + public Joins join(Joins joins, boolean forceOuter) { return assertStrategy().join(joins, forceOuter); } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/FieldStrategy.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/FieldStrategy.java index 6f395ae28..e62c790a6 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/FieldStrategy.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/FieldStrategy.java @@ -185,6 +185,11 @@ public interface FieldStrategy */ public void appendIndex(SQLBuffer sql, Select sel, Joins joins); + /** + * Append the entity discriminator value to the given statement. + */ + public void appendType(SQLBuffer sql, Select sel, Joins joins); + /** * Join this value to the class table. Does nothing by default. */ diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/AbstractFieldStrategy.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/AbstractFieldStrategy.java index d79543c87..1e97d6681 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/AbstractFieldStrategy.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/AbstractFieldStrategy.java @@ -139,6 +139,10 @@ public abstract class AbstractFieldStrategy sql.append("1"); } + public void appendType(SQLBuffer sql, Select sel, Joins joins) { + sql.append("1"); + } + public Joins join(Joins joins, boolean forceOuter) { return joins; } 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 0503f0c6d..ebb4410ab 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 @@ -402,6 +402,13 @@ public interface ExpressionFactory { */ public Value index(Value target); + /** + * Return the type/class of the given value. + * + * @since 2.0.0 + */ + public Value type(Value target); + /** * Return distinct values of the given value. This is typically used * within aggregates, for example: max(distinct(path)) @@ -445,4 +452,10 @@ public interface ExpressionFactory { * Return a nullif expression */ public Value nullIfExpression(Value val1, Value val2); + + /** + * Return a value representing the given constant, which will be + * a {@link Number}, {@link String}, or {@link Boolean} instance. + */ + public Literal newTypeLiteral(Object val, int parseType); } 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 724a00916..45226c349 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 @@ -480,6 +480,10 @@ public class InMemoryExpressionFactory return new Lit(val, parseType); } + public Literal newTypeLiteral(Object val, int parseType) { + return new TypeLit(val, parseType); + } + public Value getThis() { return new This(); } @@ -635,6 +639,10 @@ public class InMemoryExpressionFactory return new Index((Val) val); } + public Value type(Value val) { + return new Type((Val) val); + } + public Value getObjectId(Value val) { return new GetObjectId((Val) val); } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Literal.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Literal.java index 178f1e81a..217bd7777 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Literal.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Literal.java @@ -32,6 +32,7 @@ public interface Literal public static final int TYPE_BOOLEAN = 2; public static final int TYPE_STRING = 3; public static final int TYPE_SQ_STRING = 4; // single-quoted string + public static final int TYPE_CLASS = 5; /** * The value of this literal. diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Type.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Type.java new file mode 100644 index 000000000..a9e7f45cc --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Type.java @@ -0,0 +1,63 @@ +/* + * 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.kernel.exps; + +import java.util.Collection; +import java.util.Map; + +import org.apache.openjpa.kernel.StoreContext; + +import serp.util.Numbers; + +/** + * Returns the entity type. + * + * @author Catalina Wei + */ +class Type extends Val { + + private final Val _val; + + /** + * Constructor. Provide target string and the arguments to the + * indexOf method. + */ + public Type(Val val) { + _val = val; + } + + public Class getType() { + return Class.class; + } + + public void setImplicitType(Class type) { + } + + protected Object eval(Object candidate, Object orig, + StoreContext ctx, Object[] params) { + _val.eval(candidate, orig, ctx, params); + return _val.getType(); + } + + public void acceptVisit(ExpressionVisitor visitor) { + visitor.enter(this); + _val.acceptVisit(visitor); + visitor.exit(this); + } +} diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/TypeLit.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/TypeLit.java new file mode 100644 index 000000000..e38db03c6 --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/TypeLit.java @@ -0,0 +1,72 @@ +/* + * 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.kernel.exps; + +import org.apache.openjpa.kernel.Filters; +import org.apache.openjpa.kernel.StoreContext; + +/** + * Represents a type literal. + * + * @author Catalina Wei + */ +class TypeLit + extends Val + implements Literal { + + private Object _val; + private final int _ptype; + + /** + * Constructor. Provide constant value. + */ + public TypeLit(Object val, int ptype) { + _val = val; + _ptype = ptype; + } + + public Object getValue() { + return _val; + } + + public void setValue(Object val) { + _val = val; + } + + public int getParseType() { + return _ptype; + } + + public Object getValue(Object[] parameters) { + return _val; + } + + public Class getType() { + return (_val == null) ? Object.class : _val.getClass(); + } + + public void setImplicitType(Class type) { + _val = Filters.convert(_val, type); + } + + protected Object eval(Object candidate, Object orig, + StoreContext ctx, Object[] params) { + return _val; + } +} diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java index 35fe38ffc..fa9dfd736 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java @@ -330,14 +330,23 @@ public class JPQLExpressionBuilder if (aliasNode != null) proj.setAlias(alias); exps.projections[i] = proj; - exps.projectionClauses[i] = aliasNode == null ? - assemble(node.id == JJTSCALAREXPRESSION ? firstChild(node) - : node) : alias; + exps.projectionClauses[i] = aliasNode != null ? alias : + projectionClause(node.id == JJTSCALAREXPRESSION ? + firstChild(node) : node); exps.projectionAliases[i] = alias; } return exp; } + private String projectionClause(JPQLNode node) { + switch (node.id) { + case JJTTYPE: + return projectionClause(firstChild(node)); + default: + return assemble(node); + } + } + private void evalQueryOperation(QueryExpressions exps) { // determine whether we want to select, delete, or update if (root().id == JJTSELECT || root().id == JJTSUBSELECT) @@ -746,7 +755,10 @@ public class JPQLExpressionBuilder return eval(onlyChild(node)); case JJTTYPE: - return eval(onlyChild(node)); + return getType(onlyChild(node)); + + case JJTTYPELITERAL: + return getTypeLiteral(node); case JJTCLASSNAME: return getPathOrConstant(node); @@ -754,10 +766,10 @@ public class JPQLExpressionBuilder case JJTCASE: return eval(onlyChild(node)); - case JJTSCASE: + case JJTSIMPLECASE: return getSimpleCaseExpression(node); - case JJTGCASE: + case JJTGENERALCASE: return getGeneralCaseExpression(node); case JJTWHEN: @@ -814,6 +826,10 @@ public class JPQLExpressionBuilder case JJTPOSITIONALINPUTPARAMETER: return getParameter(node.text, true); + case JJTCOLLECTIONPARAMETER: + // TODO: support collection valued parameters + return getParameter(onlyChild(node).text, true); + case JJTOR: // x OR y return factory.or(getExpression(left(node)), getExpression(right(node))); @@ -892,14 +908,19 @@ public class JPQLExpressionBuilder factory.lessThanEqual(val1, val3))); case JJTIN: // x.field [NOT] IN ('a', 'b', 'c') - + // TYPE(x...) [NOT] IN (entityTypeLiteral1,...) Expression inExp = null; Iterator inIterator = node.iterator(); // the first child is the path - val1 = getValue((JPQLNode) inIterator.next()); + JPQLNode first = (JPQLNode) inIterator.next(); + val1 = getValue(first); while (inIterator.hasNext()) { - val2 = getValue((JPQLNode) inIterator.next()); + JPQLNode next = (JPQLNode) inIterator.next(); + if (first.id == JJTTYPE && next.id == JJTTYPELITERAL) + val2 = getTypeLiteral(next); + else + val2 = getValue(next); // special case for IN () or // IN () @@ -1360,6 +1381,21 @@ public class JPQLExpressionBuilder } else if (val instanceof Path) { return (Path) val; } else if (val instanceof Value) { + if (val.isVariable()) { + // can be an entity type literal + Class c = resolver.classForName(name, null); + if (c != null) { + Value lit = factory.newTypeLiteral(c, Literal.TYPE_CLASS); + Class candidate = getCandidateType(); + ClassMetaData can = getClassMetaData(candidate.getName(), false); + ClassMetaData meta = getClassMetaData(name, false); + if (candidate.isAssignableFrom(c)) + lit.setMetaData(meta); + else + lit.setMetaData(can); + return lit; + } + } return (Value) val; } @@ -1367,6 +1403,24 @@ public class JPQLExpressionBuilder new Object[]{ name }, null); } + private Value getTypeLiteral(JPQLNode node) { + JPQLNode type = onlyChild(node); + final String name = type.text; + final Value val = getVariable(name, false); + + if (val instanceof Value && val.isVariable()) { + Class c = resolver.classForName(name, null); + if (c != null) { + Value typeLit = factory.newTypeLiteral(c, Literal.TYPE_CLASS); + typeLit.setMetaData(getClassMetaData(name, false)); + return typeLit; + } + } + + throw parseException(EX_USER, "not-type-literal", + new Object[]{ name }, null); + } + private Value getPathOrConstant(JPQLNode node) { // first check to see if the path is an enum or static field, and // if so, load it @@ -1394,6 +1448,36 @@ public class JPQLExpressionBuilder } } + /** + * Process type_discriminator + * type_discriminator ::= + * TYPE(identification_variable | + * single_valued_object_path_expression | + * input_parameter ) + */ + private Value getType(JPQLNode node) { + switch (node.id) { + case JJTIDENTIFIER: + return factory.type(getValue(node)); + + case JJTNAMEDINPUTPARAMETER: + return factory.type(getParameter(node.text, false)); + + case JJTPOSITIONALINPUTPARAMETER: + return factory.type(getParameter(node.text, true)); + + default: + // TODO: enforce jpa2.0 spec rules. + // A single_valued_object_field is designated by the name of + // an association field in a one-to-one or many-to-one relationship + // or a field of embeddable class type. + // The type of a single_valued_object_field is the abstract schema + // type of the related entity or embeddable class + Value path = getPath(node, false, true); + return factory.type(path); + } + } + private Path getPath(JPQLNode node) { return getPath(node, false, true); } diff --git a/openjpa-kernel/src/main/jjtree/org/apache/openjpa/kernel/jpql/JPQL.jjt b/openjpa-kernel/src/main/jjtree/org/apache/openjpa/kernel/jpql/JPQL.jjt index 9c8ce2da5..e9884b6eb 100644 --- a/openjpa-kernel/src/main/jjtree/org/apache/openjpa/kernel/jpql/JPQL.jjt +++ b/openjpa-kernel/src/main/jjtree/org/apache/openjpa/kernel/jpql/JPQL.jjt @@ -745,19 +745,26 @@ void between_expression() #BETWEEN : { } void in_expression() #IN : { } { - (path() | scalar_function()) [ LOOKAHEAD(1) { jjtThis.not = true; }] - "(" (literal_or_param() + (path() | scalar_function() | type_discriminator()) [ LOOKAHEAD(1) { jjtThis.not = true; }] + ("(" (literal_or_param() ( (literal_or_param()))* | subquery()) - ")" + ")" + | collection_valued_input_parameter() + ) } +void entity_type_literal() #TYPELITERAL : { } +{ + identification_variable() +} void literal_or_param() : { } { (numeric_literal() | string_literal() | boolean_literal() - | input_parameter()) + | input_parameter() + | entity_type_literal()) } @@ -941,46 +948,51 @@ void arithmetic_factor() : { } LOOKAHEAD(2) "(" arithmetic_expression() ")" | functions_returning_numerics() | aggregate_select_expression() | + LOOKAHEAD(case_expression()) case_expression() | subquery() } -void qualified_path() #QPATH : { } +void qualified_path() #QUALIFIEDPATH : { } { general_identification_variable() (LOOKAHEAD(2) path_component())+ } -void qualified_identification_variable() #QIDENTIFIER : { } +void qualified_identification_variable() #QUALIFIEDIDENTIFIER : { } { - ( "(" identification_variable() ")" #KEY - | "(" identification_variable() ")" #VALUE - | "(" identification_variable() ")" #ENTRY + ( "(" identification_variable() ")" #KEY(1) + | "(" identification_variable() ")" #VALUE(1) + | "(" identification_variable() ")" #ENTRY(1) ) } -void general_identification_variable() #GIDENTIFIER : { } +void general_identification_variable() #GENERALIDENTIFIER : { } { - ( "(" identification_variable() ")" #KEY - | "(" identification_variable() ")" #VALUE + ( "(" identification_variable() ")" #KEY(1) + | "(" identification_variable() ")" #VALUE(1) ) } void entity_type_comp() : { } { - entity_type_expression() - ( entity_type_expression() #EQUALS(2) - | entity_type_expression() #NOTEQUALS(2) + (entity_type_expression() | entity_type_literal()) + ( (entity_type_expression() | entity_type_literal()) #EQUALS(2) + | (entity_type_expression() | entity_type_literal()) #NOTEQUALS(2) ) } -void entity_type_expression() #TYPE : { } +void type_discriminator() #TYPE : { } { - "(" (LOOKAHEAD(identification_variable()) identification_variable() - | LOOKAHEAD(path()) path() + "(" (LOOKAHEAD(path()) path() | LOOKAHEAD(general_identification_variable()) general_identification_variable() + | LOOKAHEAD(identification_variable()) identification_variable() | LOOKAHEAD(input_parameter()) input_parameter()) - ")" | - classname() #TYPE_LITERAL | + ")" +} + +void entity_type_expression(): { } +{ + type_discriminator() | input_parameter() } @@ -990,7 +1002,7 @@ void scalar_expression() #SCALAREXPRESSION : { } LOOKAHEAD(case_expression()) case_expression() | LOOKAHEAD(string_primary()) string_primary() | LOOKAHEAD(datetime_primary()) datetime_primary() | - LOOKAHEAD(enum_primary()) enum_primary() + LOOKAHEAD(enum_primary()) enum_primary() | LOOKAHEAD(entity_type_expression()) entity_type_expression() } @@ -1004,29 +1016,31 @@ void case_expression() #CASE : { } nullif_expression() } -void general_case_expression() #GCASE : { } +void general_case_expression() #GENERALCASE : { } { (when_clause())+ - scalar_expression() + (LOOKAHEAD(2) scalar_expression() | entity_type_literal()) } void when_clause() #WHEN : { } { - conditional_expression() scalar_expression() + conditional_expression() + (LOOKAHEAD(2) scalar_expression() | entity_type_literal()) } -void simple_case_expression() #SCASE : { } +void simple_case_expression() #SIMPLECASE : { } { - (LOOKAHEAD(2) path() | entity_type_expression()) + (LOOKAHEAD(type_discriminator()) type_discriminator() | LOOKAHEAD(path()) path()) (simple_when_clause())+ - scalar_expression() + (LOOKAHEAD(2) scalar_expression() | entity_type_literal()) } void simple_when_clause() #WHENSCALAR : { } { - scalar_expression() scalar_expression() + (LOOKAHEAD(2) scalar_expression() | entity_type_literal()) + (LOOKAHEAD(2) scalar_expression() | entity_type_literal()) } void coalesce_expression() #COALESCE : { } @@ -1409,6 +1423,12 @@ void input_parameter() : { } } +void collection_valued_input_parameter() #COLLECTIONPARAMETER: { } +{ + named_input_parameter() | positional_input_parameter() +} + + void named_input_parameter() #NAMEDINPUTPARAMETER : { Token t; } { diff --git a/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/jpql/localizer.properties b/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/jpql/localizer.properties index ca7321b7c..c53c64289 100644 --- a/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/jpql/localizer.properties +++ b/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/jpql/localizer.properties @@ -71,4 +71,5 @@ query-extensions-error: This JPQL query uses non-standard OpenJPA \ openjpa.Compatibility configuration setting is configured to disallow \ JPQL extensions. jpql-parse-error: "{1}" while parsing JPQL "{0}". See nested stack trace for \ - original parse error. \ No newline at end of file + original parse error. +not-type-literal: The specified node ("{0}") is not a valid entity type literal. diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/expressions/TestEntityTypeExpression.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/expressions/TestEntityTypeExpression.java new file mode 100644 index 000000000..21a16482a --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/expressions/TestEntityTypeExpression.java @@ -0,0 +1,192 @@ +/* + * 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.persistence.jpql.expressions; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import javax.persistence.EntityManager; + +import org.apache.openjpa.persistence.common.apps.*; +import org.apache.openjpa.persistence.common.utils.AbstractTestCase; + +public class TestEntityTypeExpression extends AbstractTestCase { + + private int userid1, userid2, userid3, userid4, userid5, userid6; + + public TestEntityTypeExpression(String name) { + super(name, "jpqlclausescactusapp"); + } + + public void setUp() { + deleteAll(CompUser.class); + EntityManager em = currentEntityManager(); + startTx(em); + + Address[] add = new Address[]{ + new Address("43 Sansome", "SF", "United-Kingdom", "94104"), + new Address("24 Mink", "ANTIOCH", "USA", "94513"), + new Address("23 Ogbete", "CoalCamp", "NIGERIA", "00000"), + new Address("10 Wilshire", "Worcester", "CANADA", "80080"), + new Address("23 Bellflower", "Ogui", null, "02000"), + new Address("22 Montgomery", "SF", null, "50054") }; + + CompUser user1 = createUser("Seetha", "MAC", add[0], 36, true); + CompUser user2 = createUser("Shannon ", "PC", add[1], 36, false); + CompUser user3 = createUser("Ugo", "PC", add[2], 19, true); + CompUser user4 = createUser("_Jacob", "LINUX", add[3], 10, true); + CompUser user5 = createUser("Famzy", "UNIX", add[4], 29, false); + CompUser user6 = createUser("Shade", "UNIX", add[5], 23, false); + + em.persist(user1); + userid1 = user1.getUserid(); + em.persist(user2); + userid2 = user2.getUserid(); + em.persist(user3); + userid3 = user3.getUserid(); + em.persist(user4); + userid4 = user4.getUserid(); + em.persist(user5); + userid5 = user5.getUserid(); + em.persist(user6); + userid6 = user6.getUserid(); + + endTx(em); + endEm(em); + } + + @SuppressWarnings("unchecked") + public void testTypeExpression() { + EntityManager em = currentEntityManager(); + + String query = null; + List rs = null; + CompUser user = null; + +// TODO: test when support for collection valued parameters is available +// Collection params = new ArrayList(); +// params.add(FemaleUser.class); +// params.add(MaleUser.class); +// query = "SELECT e FROM CompUser e where TYPE(e) = :params"; +// rs = em.createQuery(query). +// setParameter("params", params).getResultList(); +// user = rs.get(0); +// assertEquals("the name is not shannon", "Shannon ", user.getName()); + + query = "SELECT TYPE(e) FROM MaleUser e where TYPE(e) = MaleUser"; + rs = em.createQuery(query).getResultList(); + Object type = rs.get(0); + assertEquals(type, MaleUser.class); + + query = "SELECT TYPE(e) FROM CompUser e where TYPE(e) = ?1"; + rs = em.createQuery(query). + setParameter(1, FemaleUser.class).getResultList(); + type = rs.get(0); + assertEquals(type, FemaleUser.class); + + query = "SELECT TYPE(e) FROM MaleUser e where TYPE(e) = ?1"; + rs = em.createQuery(query). + setParameter(1, MaleUser.class).getResultList(); + type = rs.get(0); + assertEquals(type, MaleUser.class); + + query = "SELECT e, FemaleUser, a FROM Address a, FemaleUser e " + + " where e.address IS NOT NULL"; + List rs2 = em.createQuery(query).getResultList(); + type = ((Object[]) rs2.get(0))[1]; + assertEquals(type, FemaleUser.class); + + query = "SELECT e FROM CompUser e where TYPE(e) = :typeName"; + rs = em.createQuery(query). + setParameter("typeName", FemaleUser.class).getResultList(); + user = rs.get(0); + assertEquals("the name is not shannon", "Shannon ", user.getName()); + + query = "SELECT e FROM CompUser e where TYPE(e) = ?1"; + rs = em.createQuery(query). + setParameter(1, FemaleUser.class).getResultList(); + user = rs.get(0); + assertEquals("the name is not shannon", "Shannon ", user.getName()); + + query = "SELECT e FROM CompUser e where TYPE(e) in (?1)"; + rs = em.createQuery(query). + setParameter(1, MaleUser.class).getResultList(); + user = rs.get(0); + assertEquals(user.getName(), "Seetha"); + + query = "SELECT e FROM CompUser e where TYPE(e) in (?1, ?2)"; + rs = em.createQuery(query). + setParameter(1, FemaleUser.class).setParameter(2, MaleUser.class). + getResultList(); + user = rs.get(0); + assertEquals("the name is not shannon", "Shannon ", user.getName()); + + query = "select sum(e.age) FROM CompUser e GROUP BY e.age" + + " HAVING ABS(e.age) = :param"; + Long sum = (Long) em.createQuery(query). + setParameter("param", new Double(36)).getSingleResult(); + assertEquals(sum.intValue(), 72); + + String[] queries = { + "SELECT e FROM CompUser e where TYPE(e) = MaleUser", + "SELECT e from CompUser e where TYPE(e) in (FemaleUser)", + "SELECT e from CompUser e where TYPE(e) not in (FemaleUser)", + "SELECT e from CompUser e where TYPE(e) in (MaleUser, FemaleUser)", + "SELECT TYPE(e) FROM CompUser e where TYPE(e) = MaleUser", + "SELECT TYPE(e) FROM CompUser e", + "SELECT TYPE(a.user) FROM Address a", + "SELECT MaleUser FROM CompUser e", + "SELECT MaleUser FROM Address a", + "SELECT " + + " CASE TYPE(e) WHEN FemaleUser THEN 'Female' " + + " ELSE 'Male' " + + " END " + + " FROM CompUser e", + }; + + for (int i = 0; i < queries.length; i++) { + query = queries[i]; + List rs1 = em.createQuery(query).getResultList(); + Object obj = rs1.get(0); + obj.toString(); + System.err.println(obj+" rs size="+rs1.size()); + } + + endEm(em); + } + + public CompUser createUser(String name, String cName, Address add, int age, + boolean isMale) { + CompUser user = null; + if (isMale) { + user = new MaleUser(); + user.setName(name); + user.setComputerName(cName); + user.setAddress(add); + user.setAge(age); + } else { + user = new FemaleUser(); + user.setName(name); + user.setComputerName(cName); + user.setAddress(add); + user.setAge(age); + } + return user; + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/expressions/TestJPQLScalarExpressions.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/expressions/TestJPQLScalarExpressions.java index 4fb2283fe..29d7c7138 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/expressions/TestJPQLScalarExpressions.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/expressions/TestJPQLScalarExpressions.java @@ -69,11 +69,11 @@ public class TestJPQLScalarExpressions extends AbstractTestCase { endEm(em); } + @SuppressWarnings("unchecked") public void testCoalesceExpressions() { EntityManager em = currentEntityManager(); startTx(em); - String query = "SELECT e.name, " + "COALESCE (e.address.country, 'Unknown')" + " FROM CompUser e"; @@ -86,6 +86,7 @@ public class TestJPQLScalarExpressions extends AbstractTestCase { endEm(em); } + @SuppressWarnings("unchecked") public void testNullIfExpressions() { EntityManager em = currentEntityManager(); startTx(em); @@ -103,6 +104,7 @@ public class TestJPQLScalarExpressions extends AbstractTestCase { endEm(em); } + @SuppressWarnings("unchecked") public void testSimpleCaseExpressions() { EntityManager em = currentEntityManager(); @@ -129,15 +131,23 @@ public class TestJPQLScalarExpressions extends AbstractTestCase { Object[] result2 = (Object[]) rs2.get(rs2.size()-1); assertEquals("the name is not seetha", "Seetha", result2[0]); - // TODO: needs entity-type-expression String query3 = "SELECT e.name, " + " CASE TYPE(e) WHEN FemaleUser THEN 'Female' " + " ELSE 'Male' " + - " END " + + " END as result" + " FROM CompUser e"; + List rs3 = em.createQuery(query3).getResultList(); + Object[] result3 = (Object[]) rs3.get(rs3.size()-1); + assertEquals("the result is not female", "Female", result3[1]); + assertEquals("the name is not shade", "Shade", result3[0]); + result3 = (Object[]) rs3.get(0); + assertEquals("the result is not male", "Male", result3[1]); + assertEquals("the name is not seetha", "Seetha", result3[0]); + endEm(em); } + @SuppressWarnings("unchecked") public void testGeneralCaseExpressions() { EntityManager em = currentEntityManager(); startTx(em); @@ -154,7 +164,6 @@ public class TestJPQLScalarExpressions extends AbstractTestCase { " FROM CompUser e ORDER BY cage"; List rs = em.createQuery(query).getResultList(); - String update = "UPDATE CompUser e SET e.age = " + "CASE WHEN e.age > 30 THEN e.age - 1 " + @@ -169,6 +178,7 @@ public class TestJPQLScalarExpressions extends AbstractTestCase { endEm(em); } + @SuppressWarnings("unchecked") public void testMathFuncOrderByAlias() { EntityManager em = currentEntityManager();