From debe82b8d0afcf2dbcbd4fc05ddd5aba39c5c83e Mon Sep 17 00:00:00 2001 From: Catalina Wei Date: Fri, 12 Dec 2008 01:29:19 +0000 Subject: [PATCH] OPENJPA-805 JPQL iteration 1 - add support for COALESCE and NULLIF expressions git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@725881 13f79535-47bb-0310-9956-ffa450edef68 --- .../jdbc/kernel/exps/CoalesceExpression.java | 175 ++++++++++++++++++ .../kernel/exps/JDBCExpressionFactory.java | 33 ++++ .../jdbc/kernel/exps/NullIfExpression.java | 159 ++++++++++++++++ .../apache/openjpa/jdbc/kernel/exps/Val.java | 2 + .../kernel/exps/ExpressionFactory.java | 10 + .../exps/InMemoryExpressionFactory.java | 10 + .../kernel/jpql/JPQLExpressionBuilder.java | 23 +++ .../TestJPQLScalarExpressions.java | 34 ++++ 8 files changed, 446 insertions(+) create mode 100644 openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/CoalesceExpression.java create mode 100644 openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/NullIfExpression.java diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/CoalesceExpression.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/CoalesceExpression.java new file mode 100644 index 000000000..427f498c9 --- /dev/null +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/CoalesceExpression.java @@ -0,0 +1,175 @@ +/* + * 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.JavaSQLTypes; +import org.apache.openjpa.jdbc.sql.Joins; +import org.apache.openjpa.jdbc.sql.Raw; +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.kernel.Filters; +import org.apache.openjpa.kernel.exps.ExpressionVisitor; +import org.apache.openjpa.meta.ClassMetaData; + +/** + * Coalesce expression. + * + * @author Catalina Wei + */ +public class CoalesceExpression + extends AbstractVal { + + private final Val[] _vals; + private ClassMetaData _meta = null; + private Class _cast = null; + + /** + * Constructor. + */ + public CoalesceExpression(Val[] vals) { + _vals = vals; + } + + public Val[] getVal() { + return _vals; + } + + public Class getType() { + if (_cast != null) + return _cast; + Class type = _vals[0].getType(); + for (int i = 1; i < _vals.length; i++) + type = Filters.promote(type, _vals[i].getType()); + if (type == Raw.class) + return String.class; + return type; + } + + public ExpState initialize(Select sel, ExpContext ctx, int flags) { + ExpState[] states = new ExpState[_vals.length]; + Joins joins = null; + for (int i = 0; i < _vals.length; i++) { + states[i] = _vals[i].initialize(sel, ctx, flags); + if (joins == null) + joins = states[i].joins; + else + joins = sel.and(joins, states[i].joins); + } + return new CoalesceExpState(joins, states); + } + + private static class CoalesceExpState + extends ExpState { + + public ExpState[] states; + + public CoalesceExpState(Joins joins, ExpState[] states) { + super(joins); + this.states = states; + } + } + + public void appendTo(Select sel, ExpContext ctx, ExpState state, + SQLBuffer buf, int index) { + CoalesceExpState cstate = (CoalesceExpState) state; + + buf.append(" COALESCE "); + buf.append("("); + + for (int i = 0; i < _vals.length; i++) { + if (i > 0) + buf.append(","); + _vals[i].appendTo(sel, ctx, cstate.states[i], buf, 0); + } + + buf.append(")"); + } + + public void selectColumns(Select sel, ExpContext ctx, ExpState state, + boolean pks) { + CoalesceExpState cstate = (CoalesceExpState) state; + + for (int i = 0; i < _vals.length; i++) + _vals[i].selectColumns(sel, ctx, cstate.states[i], pks); + } + + public void acceptVisit(ExpressionVisitor visitor) { + visitor.enter(this); + for (int i = 0; i < _vals.length; i++) + _vals[i].acceptVisit(visitor); + visitor.exit(this); + } + + public int getId() { + return Val.COALESCE_VAL; + } + + public void calculateValue(Select sel, ExpContext ctx, ExpState state, + Val other, ExpState otherState) { + CoalesceExpState cstate = (CoalesceExpState) state; + for (int i = 0; i < _vals.length; i++) + _vals[i].calculateValue(sel, ctx, cstate.states[i], null, null); + } + + public void groupBy(Select sel, ExpContext ctx, ExpState state) { + sel.groupBy(newSQLBuffer(sel, ctx, state)); + } + + public int length(Select sel, ExpContext ctx, ExpState state) { + return 1; + } + + private SQLBuffer newSQLBuffer(Select sel, ExpContext ctx, ExpState state) { + calculateValue(sel, ctx, state, null, null); + SQLBuffer buf = new SQLBuffer(ctx.store.getDBDictionary()); + appendTo(sel, ctx, state, buf, 0); + return buf; + } + + public Object load(ExpContext ctx, ExpState state, Result res) + throws SQLException { + return Filters.convert(res.getObject(this, + JavaSQLTypes.JDBC_DEFAULT, null), getType()); + } + + public void orderBy(Select sel, ExpContext ctx, ExpState state, + boolean asc) { + sel.orderBy(newSQLBuffer(sel, ctx, state), asc, false, getSelectAs()); + } + + public void select(Select sel, ExpContext ctx, ExpState state, boolean pks){ + sel.select(newSQLBuffer(sel, ctx, state), this); + } + + public ClassMetaData getMetaData() { + return _meta; + } + + public void setImplicitType(Class type) { + _cast = type; + } + + public void setMetaData(ClassMetaData meta) { + _meta = meta; + } +} + 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 c7c32509b..3ddd2c932 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 @@ -447,4 +447,37 @@ public class JDBCExpressionFactory } return new WhenScalar((Val) val1, (Val) val2); } + + public Value coalesceExpression(Value[] vals) {; + Object[] values = new Val[vals.length]; + for (int i = 0; i < vals.length; i++) { + if (vals[i] instanceof Lit) { + Lit val = (Lit) vals[i]; + StringBuffer value = new StringBuffer(val.getValue().toString()); + if (val.getParseType() == Literal.TYPE_SQ_STRING) + value.insert(0, "'").append("'"); + val.setValue(new Raw(value.toString())); + } + values[i] = vals[i]; + } + return new CoalesceExpression((Val[]) values); + } + + public Value nullIfExpression(Value val1, Value val2) { + if (val1 instanceof Lit) { + Lit val = (Lit) val1; + StringBuffer value = new StringBuffer(val.getValue().toString()); + if (val.getParseType() == Literal.TYPE_SQ_STRING) + value.insert(0, "'").append("'"); + val.setValue(new Raw(value.toString())); + } + if (val2 instanceof Lit) { + Lit val = (Lit) val2; + StringBuffer value = new StringBuffer(val.getValue().toString()); + if (val.getParseType() == Literal.TYPE_SQ_STRING) + value.insert(0, "'").append("'"); + val.setValue(new Raw(value.toString())); + } + return new NullIfExpression((Val) val1, (Val) val2); + } } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/NullIfExpression.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/NullIfExpression.java new file mode 100644 index 000000000..aff99410b --- /dev/null +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/NullIfExpression.java @@ -0,0 +1,159 @@ +/* + * 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.JavaSQLTypes; +import org.apache.openjpa.jdbc.sql.Raw; +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.kernel.Filters; +import org.apache.openjpa.kernel.exps.ExpressionVisitor; +import org.apache.openjpa.meta.ClassMetaData; + +/** + * NullIf expression. + * + * @author Catalina Wei + */ +public class NullIfExpression + extends AbstractVal { + + private final Val _val1; + private final Val _val2; + private ClassMetaData _meta = null; + private Class _cast = null; + + /** + * Constructor. + */ + public NullIfExpression(Val val1, Val val2) { + _val1 = val1; + _val2 = val2; + } + + public Val getVal1() { + return _val1; + } + + public Val getVal2() { + return _val2; + } + + public Class getType() { + if (_cast != null) + return _cast; + Class c1 = _val1.getType(); + Class c2 = _val2.getType(); + Class type = Filters.promote(c1, c2); + if (type == Raw.class) + return String.class; + return type; + } + + public ExpState initialize(Select sel, ExpContext ctx, int flags) { + ExpState s1 = _val1.initialize(sel, ctx, 0); + ExpState s2 = _val2.initialize(sel, ctx, 0); + return new BinaryOpExpState(sel.and(s1.joins, s2.joins), s1, s2); + } + + public void appendTo(Select sel, ExpContext ctx, ExpState state, + SQLBuffer buf, int index) { + BinaryOpExpState bstate = (BinaryOpExpState) state; + + buf.append(" NULLIF "); + buf.append("("); + + _val1.appendTo(sel, ctx, bstate.state1, buf, 0); + buf.append(","); + _val2.appendTo(sel, ctx, bstate.state2, buf, 0); + + buf.append(")"); + } + + public void selectColumns(Select sel, ExpContext ctx, ExpState state, + boolean pks) { + BinaryOpExpState bstate = (BinaryOpExpState) state; + _val1.selectColumns(sel, ctx, bstate.state1, true); + _val2.selectColumns(sel, ctx, bstate.state2, true); + } + + public void acceptVisit(ExpressionVisitor visitor) { + visitor.enter(this); + _val1.acceptVisit(visitor); + _val2.acceptVisit(visitor); + visitor.exit(this); + } + + public int getId() { + return Val.NULLIF_VAL; + } + + public void calculateValue(Select sel, ExpContext ctx, ExpState state, + Val other, ExpState otherState) { + BinaryOpExpState bstate = (BinaryOpExpState) state; + _val1.calculateValue(sel, ctx, bstate.state1, _val2, bstate.state2); + _val2.calculateValue(sel, ctx, bstate.state2, _val1, bstate.state1); + } + + public void groupBy(Select sel, ExpContext ctx, ExpState state) { + sel.groupBy(newSQLBuffer(sel, ctx, state)); + } + + public int length(Select sel, ExpContext ctx, ExpState state) { + return 1; + } + + private SQLBuffer newSQLBuffer(Select sel, ExpContext ctx, ExpState state) { + calculateValue(sel, ctx, state, null, null); + SQLBuffer buf = new SQLBuffer(ctx.store.getDBDictionary()); + appendTo(sel, ctx, state, buf, 0); + return buf; + } + + public Object load(ExpContext ctx, ExpState state, Result res) + throws SQLException { + return Filters.convert(res.getObject(this, + JavaSQLTypes.JDBC_DEFAULT, null), getType()); + } + + public void orderBy(Select sel, ExpContext ctx, ExpState state, + boolean asc) { + sel.orderBy(newSQLBuffer(sel, ctx, state), asc, false, getSelectAs()); + } + + public void select(Select sel, ExpContext ctx, ExpState state, boolean pks){ + sel.select(newSQLBuffer(sel, ctx, state), this); + } + + public ClassMetaData getMetaData() { + return _meta; + } + + public void setImplicitType(Class type) { + _cast = type; + } + + public void setMetaData(ClassMetaData meta) { + _meta = meta; + } +} + 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 2b56c68c7..ce06e7e25 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 @@ -68,6 +68,8 @@ public interface Val public final int GENERALCASE_VAL = 14; public final int WHENCONDITION_VAL = 15; public final int WHENSCALAR_VAL = 16; + public final int COALESCE_VAL = 17; + public final int NULLIF_VAL = 18; /** * Initialize the value. This method should recursively initialize any 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 19e6c8de8..ed85ba14c 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 @@ -428,4 +428,14 @@ public interface ExpressionFactory { * Return a when scalar_expression clause */ public Expression whenScalar(Value val1, Value val2); + + /** + * Return a coalesce expression + */ + public Value coalesceExpression(Value[] val); + + /** + * Return a nullif expression + */ + public Value nullIfExpression(Value val1, Value val2); } 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 470a8fdf4..653fbd9fc 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 @@ -745,4 +745,14 @@ public class InMemoryExpressionFactory // TODO Auto-generated method stub return null; } + + public Value coalesceExpression(Value[] val) { + // TODO Auto-generated method stub + return null; + } + + public Value nullIfExpression(Value val1, Value val2) { + // TODO Auto-generated method stub + return null; + } } 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 00090bd76..c1ec4dd69 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 @@ -766,6 +766,12 @@ public class JPQLExpressionBuilder case JJTWHENSCALAR: return getWhenScalar(node); + case JJTCOALESCE: + return getCoalesceExpression(node); + + case JJTNULLIF: + return getNullIfExpression(node); + case JJTWHERE: // top-level WHERE clause return getExpression(onlyChild(node)); @@ -1502,6 +1508,23 @@ public class JPQLExpressionBuilder return factory.whenScalar((Value) val1, (Value) val2); } + private Value getCoalesceExpression(JPQLNode node) { + int nChild = node.getChildCount(); + + Object vals[] = new Value[nChild]; + for (int i = 0; i < nChild; i++) + vals[i] = eval(node.children[i]); + + return factory.coalesceExpression((Value[]) vals); + } + + private Value getNullIfExpression(JPQLNode node) { + Object val1 = eval(firstChild(node)); + Object val2 = eval(secondChild(node)); + + return factory.nullIfExpression((Value) val1, (Value) val2); + } + private Value getValue(JPQLNode node) { return getValue(node, VAR_PATH); } 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 2a088a78b..4fb2283fe 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,6 +69,40 @@ public class TestJPQLScalarExpressions extends AbstractTestCase { endEm(em); } + public void testCoalesceExpressions() { + EntityManager em = currentEntityManager(); + startTx(em); + + + String query = "SELECT e.name, " + + "COALESCE (e.address.country, 'Unknown')" + + " FROM CompUser e"; + List rs = em.createQuery(query).getResultList(); + Object[] result = (Object[]) rs.get(rs.size()-1); + assertEquals("the name is not shade", "Shade", result[0]); + assertEquals("Unknown", result[1]); + + endTx(em); + endEm(em); + } + + public void testNullIfExpressions() { + EntityManager em = currentEntityManager(); + startTx(em); + + String query = "SELECT e.name, " + + "NULLIF (e.address.country, 'USA')" + + " FROM CompUser e"; + + List rs = em.createQuery(query).getResultList(); + Object[] result = (Object[]) rs.get(1); + assertEquals("the name is not shannon ", "Shannon ", result[0]); + assertNull("is not null", result[1]); + + endTx(em); + endEm(em); + } + public void testSimpleCaseExpressions() { EntityManager em = currentEntityManager();