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 12c937166..c803a983a 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 @@ -1091,43 +1091,7 @@ public class JPQLExpressionBuilder if (node.children.length == 3) setImplicitType(val3, Integer.TYPE); - // the semantics of the JPQL substring() function - // are that arg2 is the 1-based start index, and arg3 is - // the length of the string to be return; this is different - // than the semantics of the ExpressionFactory's substring, - // which matches the Java language (0-based start index, - // arg2 is the end index): we perform the translation by - // adding one to the first argument, and then adding the - // first argument to the second argument to get the endIndex - Value start = null; - Value end = null; - if (val2 instanceof Literal && - (val3 == null || val3 instanceof Literal)) { - // optimize SQL for the common case of two literals - long jpqlStart = ((Number) ((Literal) val2).getValue()) - .longValue(); - start = factory.newLiteral(new Long(jpqlStart - 1), - Literal.TYPE_NUMBER); - if (val3 != null) { - long length = ((Number) ((Literal) val3).getValue()) - .longValue(); - long endIndex = length + (jpqlStart - 1); - end = factory.newLiteral(new Long(endIndex), - Literal.TYPE_NUMBER); - } - } else { - start = factory.subtract(val2, factory.newLiteral - (Numbers.valueOf(1), Literal.TYPE_NUMBER)); - if (val3 != null) - end = factory.add(val3, - (factory.subtract(val2, factory.newLiteral - (Numbers.valueOf(1), Literal.TYPE_NUMBER)))); - } - if (val3 != null) - return factory.substring(val1, factory.newArgumentList( - start, end)); - else - return factory.substring(val1, start); + return convertSubstringArguments(factory, val1, val2, val3); case JJTLOCATE: // as with SUBSTRING (above), the semantics for LOCATE differ @@ -1220,7 +1184,56 @@ public class JPQLExpressionBuilder new Object[]{ node }, null); } } - + + /** + * Converts JPQL substring() function to OpenJPA ExpressionFactory + * substring() arguments. + * + * The semantics of the JPQL substring() function are that arg2 is the + * 1-based start index, and arg3 is the length of the string to be return. + * This is different than the semantics of the ExpressionFactory's + * substring(), which matches the Java language (0-based start index, + * arg2 is the end index): we perform the translation by adding one to the + * first argument, and then adding the first argument to the second + * argument to get the endIndex. + * + * @param val1 the original String + * @param val2 the 1-based start index as per JPQL substring() semantics + * @param val3 the length of the returned string as per JPQL semantics + * + */ + public static Value convertSubstringArguments(ExpressionFactory factory, + Value val1, Value val2, Value val3) { + Value start = null; + Value end = null; + if (val2 instanceof Literal && + (val3 == null || val3 instanceof Literal)) { + // optimize SQL for the common case of two literals + long jpqlStart = ((Number) ((Literal) val2).getValue()) + .longValue(); + start = factory.newLiteral(new Long(jpqlStart - 1), + Literal.TYPE_NUMBER); + if (val3 != null) { + long length = ((Number) ((Literal) val3).getValue()) + .longValue(); + long endIndex = length + (jpqlStart - 1); + end = factory.newLiteral(new Long(endIndex), + Literal.TYPE_NUMBER); + } + } else { + start = factory.subtract(val2, factory.newLiteral + (Numbers.valueOf(1), Literal.TYPE_NUMBER)); + if (val3 != null) + end = factory.add(val3, + (factory.subtract(val2, factory.newLiteral + (Numbers.valueOf(1), Literal.TYPE_NUMBER)))); + } + if (val3 != null) + return factory.substring(val1, factory.newArgumentList(start, end)); + else + return factory.substring(val1, start); + + } private void assertQueryExtensions(String clause) { OpenJPAConfiguration conf = resolver.getConfiguration(); switch(conf.getCompatibilityInstance().getJPQL()) { diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Account.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Account.java index 782fa5b8e..92851704a 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Account.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Account.java @@ -37,4 +37,7 @@ public class Account { private long id; private int balance; + private Integer loan; + private String owner; + private String name; } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Account_.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Account_.java new file mode 100644 index 000000000..352fca083 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Account_.java @@ -0,0 +1,37 @@ +/* + * 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.criteria; + +import javax.persistence.metamodel.Attribute; + +/** + * This source is supposed to be automatically generated from Account.java + * during compilation. + * Hand coded to avoid runtime compiler invocation. Used for testing. + * + * @author Pinaki Poddar + * + */ +public class Account_ { + public static volatile Attribute id; + public static volatile Attribute balance; + public static volatile Attribute loan; + public static volatile Attribute owner; + public static volatile Attribute name; +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/TestTypesafeCriteria.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/TestTypesafeCriteria.java new file mode 100644 index 000000000..ff0c0bdec --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/TestTypesafeCriteria.java @@ -0,0 +1,149 @@ +package org.apache.openjpa.persistence.criteria; + +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.apache.openjpa.jdbc.conf.JDBCConfiguration; +import org.apache.openjpa.jdbc.sql.DBDictionary; +import org.apache.openjpa.persistence.test.SQLListenerTestCase; + +/** + * Tests type-strict version of Criteria API. + * + * Most of the tests build Criteria Query and then execute the query as well + * as a reference JPQL query supplied as a string. The test is validated by + * asserting that the resultant SQL queries for these two alternative form + * of executing a query are the same. + * + * + * + * @author Pinaki Poddar + * + */ +public class TestTypesafeCriteria extends SQLListenerTestCase { + CriteriaBuilder cb; + EntityManager em; + + public void setUp() { + super.setUp(Account.class); + setDictionary(); + cb = (CriteriaBuilder)emf.getQueryBuilder(); + em = emf.createEntityManager(); + } + + void setDictionary() { + JDBCConfiguration conf = (JDBCConfiguration)emf.getConfiguration(); + DBDictionary dict = conf.getDBDictionaryInstance(); + dict.requiresCastForComparisons = false; + dict.requiresCastForMathFunctions = false; + } + + public void testRoot() { + String jpql = "select a from Account a"; + CriteriaQuery c = cb.create(); + Root account = c.from(Account.class); + c.select(account); + + assertEquivalence(c, jpql); + } + + public void testEqual() { + String jpql = "select a from Account a where a.balance=100"; + + CriteriaQuery c = cb.create(); + Root account = c.from(Account.class); + c.select(account).where(cb.equal(account.get(Account_.balance), 100)); + + assertEquivalence(c, jpql); + } + + public void testEqual2() { + String jpql = "select a from Account a where a.balance=a.loan"; + + CriteriaQuery c = cb.create(); + Root account = c.from(Account.class); + c.select(account).where(cb.equal(account.get(Account_.balance), account.get(Account_.loan))); + + assertEquivalence(c, jpql); + } + + public void testProj() { + String jpql = "select a.balance,a.loan from Account a"; + CriteriaQuery c = cb.create(); + Root account = c.from(Account.class); + c.select(account.get(Account_.balance), account.get(Account_.loan)); + assertEquivalence(c, jpql); + + } + + + public void testAbs() { + CriteriaQuery c = cb.create(); + Root account = c.from(Account.class); + + String jpql = "select a from Account a where abs(a.balance)=100"; + c.select(account).where(cb.equal(cb.abs(account.get(Account_.balance)), 100)); + assertEquivalence(c, jpql); + } + + public void testAvg() { + CriteriaQuery c = cb.create(); + Root account = c.from(Account.class); + + String jpql = "select avg(a.balance) from Account a"; + c.select(cb.avg(account.get(Account_.balance))); + assertEquivalence(c, jpql); + } + + + public void testBinaryPredicate() { + String jpql = "select a from Account a where a.balance>100 and a.balance<200"; + + CriteriaQuery c = cb.create(); + Root account = c.from(Account.class); + c.select(account).where(cb.and( + cb.greaterThan(account.get(Account_.balance), 100), + cb.lessThan(account.get(Account_.balance), 200))); + + assertEquivalence(c, jpql); + } + + public void testUnaryExpression() { + String jpql = "select a from Account a where a.balance=abs(a.balance)"; + + CriteriaQuery c = cb.create(); + Root account = c.from(Account.class); + c.select(account).where(cb.equal(account.get(Account_.balance), cb.abs(account.get(Account_.balance)))); + + assertEquivalence(c, jpql); + } + + public void testBetweenExpression() { + String jpql = "select a from Account a where a.balance between 100 and 200"; + + CriteriaQuery c = cb.create(); + Root account = c.from(Account.class); + c.select(account).where(cb.between(account.get(Account_.balance), 100, 200)); + + assertEquivalence(c, jpql); + } + + + + void assertEquivalence(CriteriaQuery c, String jpql) { + sql.clear(); + List cList = em.createQuery(c).getResultList(); + assertEquals(1, sql.size()); + String cSQL = sql.get(0); + + sql.clear(); + List jList = em.createQuery(jpql).getResultList(); + assertEquals(1, sql.size()); + String jSQL = sql.get(0); + + assertEquals(jSQL, cSQL); + } +} diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerFactoryImpl.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerFactoryImpl.java index c5308ddaa..a41b25c42 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerFactoryImpl.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerFactoryImpl.java @@ -47,6 +47,7 @@ import org.apache.openjpa.lib.conf.Value; import org.apache.openjpa.lib.util.Localizer; import org.apache.openjpa.lib.util.Closeable; import org.apache.openjpa.meta.MetaDataRepository; +import org.apache.openjpa.persistence.criteria.CriteriaBuilder; import org.apache.openjpa.persistence.meta.MetamodelImpl; import org.apache.openjpa.persistence.query.OpenJPAQueryBuilder; import org.apache.openjpa.persistence.query.QueryBuilderImpl; @@ -357,7 +358,7 @@ public class EntityManagerFactoryImpl } public QueryBuilder getQueryBuilder() { - throw new UnsupportedOperationException(); + return new CriteriaBuilder().setMetaModel(getMetamodel()); } public OpenJPAQueryBuilder getDynamicQueryBuilder() { diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java index a4beb1680..2e53cff4c 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java @@ -70,6 +70,7 @@ import org.apache.openjpa.meta.ClassMetaData; import org.apache.openjpa.meta.FieldMetaData; import org.apache.openjpa.meta.QueryMetaData; import org.apache.openjpa.meta.SequenceMetaData; +import org.apache.openjpa.persistence.criteria.CriteriaBuilder; import org.apache.openjpa.util.Exceptions; import org.apache.openjpa.util.ImplHelper; import org.apache.openjpa.util.RuntimeExceptionTranslator; @@ -1509,8 +1510,8 @@ public class EntityManagerImpl } public Query createQuery(CriteriaQuery criteriaQuery) { - throw new UnsupportedOperationException( - "JPA 2.0 - Method not yet implemented"); + return new QueryImpl(this, _ret, + _broker.newQuery(CriteriaBuilder.LANG_CRITERIA, criteriaQuery)); } public OpenJPAQuery createDynamicQuery( diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/ResultItemImpl.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/ResultItemImpl.java new file mode 100644 index 000000000..7886be113 --- /dev/null +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/ResultItemImpl.java @@ -0,0 +1,49 @@ +/* + * 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; + +import javax.persistence.ResultItem; + +/** + * A single dimension of projection in query result. + * + * @author Pinaki Poddar + * + * @param type of the result + */ +public class ResultItemImpl implements ResultItem { + protected String _alias; + protected Class _cls; + + protected ResultItemImpl(Class cls) { + _cls = cls; + } + + public String getAlias() { + return _alias; + } + + public void setAlias(String alias) { + _alias = alias; + } + + public Class getJavaType() { + return _cls; + } +} diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaBuilder.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaBuilder.java new file mode 100644 index 000000000..f67152f76 --- /dev/null +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaBuilder.java @@ -0,0 +1,675 @@ +/* + * 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.criteria; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import javax.persistence.Parameter; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Order; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.QueryBuilder; +import javax.persistence.criteria.Selection; +import javax.persistence.criteria.Subquery; + +import org.apache.openjpa.kernel.ExpressionStoreQuery; +import org.apache.openjpa.kernel.exps.ExpressionFactory; +import org.apache.openjpa.kernel.exps.ExpressionParser; +import org.apache.openjpa.kernel.exps.QueryExpressions; +import org.apache.openjpa.kernel.exps.Value; +import org.apache.openjpa.meta.ClassMetaData; +import org.apache.openjpa.persistence.meta.MetamodelImpl; + +/** + * Factory for Criteria query expressions. + * + * Acts as an adapter to OpenJPA ExpressionFactory. + * + * @author Pinaki Poddar + * + * @since 2.0.0 + * + */ +@SuppressWarnings("serial") +public class CriteriaBuilder implements QueryBuilder, ExpressionParser { + public static final String LANG_CRITERIA = "javax.persistence.criteria"; + + private MetamodelImpl _model; + + public CriteriaBuilder setMetaModel(MetamodelImpl model) { + _model = model; + return this; + } + + public QueryExpressions eval(Object parsed, ExpressionStoreQuery query, + ExpressionFactory factory, ClassMetaData candidate) { + CriteriaQueryImpl c = (CriteriaQueryImpl) parsed; + return c.getQueryExpressions(factory); + } + + public Value[] eval(String[] vals, ExpressionStoreQuery query, + ExpressionFactory factory, ClassMetaData candidate) { + throw new AbstractMethodError(); + } + + public String getLanguage() { + return LANG_CRITERIA; + } + + public Object parse(String ql, ExpressionStoreQuery query) { + throw new AbstractMethodError(); + } + + public void populate(Object parsed, ExpressionStoreQuery query) { + CriteriaQueryImpl c = (CriteriaQueryImpl) parsed; + query.getContext().setCandidateType(c.getRoot().getJavaType(), true); + query.setQuery(parsed); + } + + public Expression abs(Expression x) { + return new Expressions.Abs(x); + } + + public Expression all(Subquery subquery) { + throw new AbstractMethodError(); + } + + public Predicate and(Predicate... restrictions) { + return new PredicateImpl.And(restrictions); + } + + public Predicate and(Expression x, Expression y) { + return new PredicateImpl.And(x,y); + } + + public Expression any(Subquery subquery) { + throw new AbstractMethodError(); + } + + public Order asc(Expression x) { + return new OrderImpl(x, true); + } + + public Expression avg(Expression x) { + return new Expressions.Avg(x); + } + + public > Predicate between( + Expression v, Expression x, + Expression y) { + return new Expressions.Between(v, x, y); + } + + public > Predicate between( + Expression v, Y x, Y y) { + return new Expressions.Between(v,x,y); + } + + public Coalesce coalesce() { + return new Expressions.Coalesce(); + } + + public Expression coalesce(Expression x, + Expression y) { + return new Expressions.Coalesce().value(x).value(y); + } + + public Expression coalesce(Expression x, Y y) { + return new Expressions.Coalesce().value(x).value(y); + } + + public Expression concat(Expression x, + Expression y) { + return new Expressions.Concat(x, y); + } + + public Expression concat(Expression x, String y) { + return new Expressions.Concat(x, y); + } + + public Expression concat(String x, Expression y) { + return new Expressions.Concat(x, y); + } + + public Predicate conjunction() { + return new PredicateImpl.And(); + } + + public Expression count(Expression x) { + return new Expressions.Count(x); + } + + public Expression countDistinct(Expression x) { + return new Expressions.Count(x, true); + } + + public CriteriaQuery create() { + return new CriteriaQueryImpl(_model); + } + + public Expression currentDate() { + return new Expressions.CurrentDate(); + } + + public Expression