From 4427455757f1ce8114826c935739e44980d0f62c Mon Sep 17 00:00:00 2001 From: Catalina Wei Date: Fri, 9 Jan 2009 20:13:20 +0000 Subject: [PATCH] OPENJPA-855 JPA2 Query updates add support for Index function git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@733142 13f79535-47bb-0310-9956-ffa450edef68 --- .../openjpa/jdbc/kernel/exps/AbstractVal.java | 5 ++ .../openjpa/jdbc/kernel/exps/Index.java | 57 ++++++++++++ .../kernel/exps/JDBCExpressionFactory.java | 5 ++ .../openjpa/jdbc/kernel/exps/PCPath.java | 16 ++++ .../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 + .../meta/strats/ContainerFieldStrategy.java | 5 ++ .../apache/openjpa/jdbc/sql/LogicalUnion.java | 4 + .../org/apache/openjpa/jdbc/sql/Select.java | 5 ++ .../apache/openjpa/jdbc/sql/SelectImpl.java | 27 ++++++ .../jdbc/kernel/exps/localizer.properties | 3 +- .../kernel/exps/ExpressionFactory.java | 7 ++ .../exps/InMemoryExpressionFactory.java | 4 + .../org/apache/openjpa/kernel/exps/Index.java | 47 ++++++++++ .../kernel/jpql/JPQLExpressionBuilder.java | 3 + .../openjpa/kernel/exps/localizer.properties | 3 + .../jpql/expressions/TestIndex.java | 89 +++++++++++++++++++ 19 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Index.java create mode 100644 openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Index.java create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/expressions/TestIndex.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 32f448ea4..2b74b79e4 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 @@ -74,6 +74,11 @@ abstract class AbstractVal sql.append(" IS NOT ").appendValue(null); } + public void appendIndex(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/Index.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Index.java new file mode 100644 index 000000000..ddea63eb1 --- /dev/null +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Index.java @@ -0,0 +1,57 @@ +/* + * 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.sql.SQLBuffer; +import org.apache.openjpa.jdbc.sql.Select; +import org.apache.openjpa.util.InternalException; + +/** + * Index. + * + * @author Catalina Wei + */ +class Index + extends UnaryOp { + + public Index(Val val) { + super(val); + } + + public ExpState initialize(Select sel, ExpContext ctx, int flags) { + // initialize the value with a null test + return initializeValue(sel, ctx, NULL_CMP); + } + + public void appendTo(Select sel, ExpContext ctx, ExpState state, + SQLBuffer sql, int index) { + getValue().calculateValue(sel, ctx, state, null, null); + getValue().appendIndex(sel, ctx, state, sql); + sel.append(sql, state.joins); + } + + protected Class getType(Class c) { + return long.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/JDBCExpressionFactory.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/JDBCExpressionFactory.java index 3ddd2c932..0de777ff8 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 @@ -391,6 +391,11 @@ public class JDBCExpressionFactory return new Size((Val) val); } + public Value index(Value val) { + ((PCPath) val).verifyIndexedField(); + return new Index((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 238d90c0c..c013141ea 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 @@ -761,6 +761,13 @@ public class PCPath // we eventually call appendIsEmpty or appendIsNull rather than appendTo } + public void verifyIndexedField() { + Action lastAction = (Action) lastFieldAction(); + FieldMapping fm = (FieldMapping) lastAction.data; + if (fm.getOrderColumn() == null) + throw new UserException(_loc.get("no-order-column", fm.getName())); + } + public int length(Select sel, ExpContext ctx, ExpState state) { return getColumns(state).length; } @@ -798,6 +805,15 @@ public class PCPath pstate.field.appendIsNotEmpty(sql, sel, pstate.joins); } + public void appendIndex(Select sel, ExpContext ctx, ExpState state, + SQLBuffer sql) { + PathExpState pstate = (PathExpState) state; + if (pstate.field == null) + sql.append("1"); + else + pstate.field.appendIndex(sql, sel, pstate.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/Val.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Val.java index ce06e7e25..df0089422 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 @@ -146,6 +146,12 @@ public interface Val public void appendIsNotEmpty(Select sel, ExpContext ctx, ExpState state, SQLBuffer sql); + /** + * Append the SQL checking the index of this value. + */ + public void appendIndex(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 3ba6009a8..6e00d1225 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 @@ -849,6 +849,10 @@ public class FieldMapping assertStrategy().appendSize(sql, sel, joins); } + public void appendIndex(SQLBuffer sql, Select sel, Joins joins) { + assertStrategy().appendIndex(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 083a9de95..6f395ae28 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 @@ -180,6 +180,11 @@ public interface FieldStrategy */ public void appendSize(SQLBuffer sql, Select sel, Joins joins); + /** + * Append the ordered column alias to the given statement. + */ + public void appendIndex(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 49e168d0e..d79543c87 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 @@ -135,6 +135,10 @@ public abstract class AbstractFieldStrategy sql.append("1"); } + public void appendIndex(SQLBuffer sql, Select sel, Joins joins) { + sql.append("1"); + } + public Joins join(Joins joins, boolean forceOuter) { return joins; } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ContainerFieldStrategy.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ContainerFieldStrategy.java index 617ac6c06..6eb9baba6 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ContainerFieldStrategy.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ContainerFieldStrategy.java @@ -91,6 +91,11 @@ public abstract class ContainerFieldStrategy appendJoinCount(sql, sel, joins, dict, field, fk); } + public void appendIndex(SQLBuffer sql, Select sel, Joins joins) { + sql.append(sel.getOrderedColumnAlias(field.getOrderColumn(), + field.getName())); + } + protected static void appendJoinCount(SQLBuffer sql, Select sel, Joins joins, DBDictionary dict, FieldMapping field, ForeignKey fk) { String fullTable = dict.getFullName(fk.getTable(), false); diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/LogicalUnion.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/LogicalUnion.java index 2fe9b219f..8405c43aa 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/LogicalUnion.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/LogicalUnion.java @@ -540,6 +540,10 @@ public class LogicalUnion return sel.getColumnAlias(col, joins); } + public String getOrderedColumnAlias(Column col, Object alias) { + return sel.getOrderedColumnAlias(col, alias); + } + public String getColumnAlias(String col, Table table) { return sel.getColumnAlias(col, table); } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Select.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Select.java index 1f362b8b2..f76d41cf5 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Select.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Select.java @@ -721,4 +721,9 @@ public interface Select * Implement toString to generate SQL string for profiling/debuggging. */ public String toString(); + + /** + * Return the alias for the given ordered column. + */ + public String getOrderedColumnAlias(Column col, Object path); } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SelectImpl.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SelectImpl.java index b26f108f0..a6b38a30c 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SelectImpl.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SelectImpl.java @@ -784,6 +784,29 @@ public class SelectImpl return getColumnAlias(col, table, getJoins(joins, false)); } + public String getOrderedColumnAlias(Column col, Object path) { + String columnName = col.getName(); + String tableName = col.getTable().getFullName(); + Set entries = _aliases.entrySet(); + Integer tableAlias = null; + for (Map.Entry entry : entries) { + Object obj = entry.getKey(); + Key key = null; + if (obj instanceof Key) + key = (Key) obj; + String str = key != null ? key.getKey().toString() : obj.toString(); + if (str.equals(tableName)) { + tableAlias = (Integer) entry.getValue(); + break; + } + } + if (tableAlias != null) + return new StringBuffer("t").append(tableAlias.toString()).append("."). + append(columnName).toString(); + else + throw new InternalException(path.toString()); + } + /** * Return the alias for the given column. */ @@ -2327,6 +2350,10 @@ public class SelectImpl public String toString() { return _path + "|" + _key; } + + Object getKey() { + return _key; + } } /** diff --git a/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/kernel/exps/localizer.properties b/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/kernel/exps/localizer.properties index 90d866d95..d6fc33826 100644 --- a/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/kernel/exps/localizer.properties +++ b/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/kernel/exps/localizer.properties @@ -26,4 +26,5 @@ path-only: The target for filter listener "{0}" must be "this" or some \ no-col: The column "{0}" given to filter "{1}" does not exist in the table \ of the specified target. cant-convert: Attempt to compare incompatible types "{0}" and "{1}". -invalid-unbound-var: Invalid unbound variable "{0}" in query. \ No newline at end of file +invalid-unbound-var: Invalid unbound variable "{0}" in query. +no-order-column: Field "{0}" does not have order column defined". 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 ed85ba14c..0503f0c6d 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 @@ -395,6 +395,13 @@ public interface ExpressionFactory { */ public Value size(Value target); + /** + * Return an index/position of the given value within a collection/map. + * + * @since 2.0.0 + */ + public Value index(Value target); + /** * Return distinct values of the given value. This is typically used * within aggregates, for example: max(distinct(path)) 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 1ccfea0e8..724a00916 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 @@ -631,6 +631,10 @@ public class InMemoryExpressionFactory return new Size((Val) val); } + public Value index(Value val) { + return new Index((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/Index.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Index.java new file mode 100644 index 000000000..4fa7af0f3 --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Index.java @@ -0,0 +1,47 @@ +/* + * 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.lib.util.Localizer; +import org.apache.openjpa.util.UnsupportedException; + +/** + * Returns the index of a value within a collection/map. + * + * @author Catalina Wei + */ +class Index extends UnaryMathVal { + + private static final Localizer _loc = Localizer.forPackage(Index.class); + + /** + * Constructor. + */ + public Index(Val val) { + super(val); + } + + public Class getType(Class c) { + return int.class; + } + + protected Object operate(Object o, Class c) { + throw new UnsupportedException(_loc.get("in-mem-index")); + } +} 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 c1ec4dd69..35fe38ffc 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 @@ -965,6 +965,9 @@ public class JPQLExpressionBuilder case JJTSIZE: return factory.size(getValue(onlyChild(node))); + case JJTINDEX: + return factory.index(getValue(onlyChild(node))); + case JJTUPPER: val1 = getValue(onlyChild(node)); setImplicitType(val1, TYPE_STRING); diff --git a/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/exps/localizer.properties b/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/exps/localizer.properties index b540f10ef..ad3e82730 100644 --- a/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/exps/localizer.properties +++ b/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/exps/localizer.properties @@ -31,3 +31,6 @@ non-numeric-comparison: Filter invalid. Cannot compare {0} to {1}. Numeric \ comparisons must be between numeric types only. To enable such comparisons \ for backwards-compatibility, add "QuotedNumbersInQueries=true" to the \ org.apache.openjpa.Compatibility setting in your configuration. +in-mem-index: Index function is not supported for queries that execute \ + in-memory. If you do not intend for this query to execute in-memory, \ + consider setting IgnoreCache to true for the query. diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/expressions/TestIndex.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/expressions/TestIndex.java new file mode 100644 index 000000000..64d23590f --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/expressions/TestIndex.java @@ -0,0 +1,89 @@ +/* + * 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.List; + +import javax.persistence.EntityManager; + +import org.apache.openjpa.persistence.proxy.TreeNode; +import org.apache.openjpa.persistence.test.SingleEMFTestCase; + +/** + * Tests index function + * + * @author Catalina Wei + */ +public class TestIndex extends SingleEMFTestCase { + public void setUp() { + super.setUp(CLEAR_TABLES, TreeNode.class); + } + + public void testQueryIndex() { + persistTree(); + EntityManager em = emf.createEntityManager(); + String query = "SELECT index(c) from TreeNode t, in (t.childern) c" + + " WHERE index(c) = 2"; + + List rs = em.createQuery(query).getResultList(); + for (Object t: rs) + assertEquals(2, Integer.parseInt(t.toString())); + + em.close(); + } + + public void createTree() { + TreeNode root = new TreeNode(); + root.setName("0"); + int[] fanOuts = {1,2,3}; + root.createTree(fanOuts); + assertArrayEquals(fanOuts, root.getFanOuts()); + } + + public void persistTree() { + int[] fanOuts = {2,3,4}; + create(fanOuts); + } + + /** + * Create a uniform tree with given fan out. + * Persist. + */ + TreeNode create(int[] original) { + TreeNode root = new TreeNode(); + root.createTree(original); + + EntityManager em = emf.createEntityManager(); + em.getTransaction().begin(); + em.persist(root); + em.getTransaction().commit(); + em.clear(); + + return root; + } + + /** + * Asserts the given arrays have exactly same elements at the same index. + */ + void assertArrayEquals(int[] a, int[] b) { + assertEquals(a.length, b.length); + for (int i = 0; i