| /* |
| * 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.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.persistence.EntityManager; |
| import javax.persistence.Persistence; |
| import javax.persistence.Query; |
| import javax.persistence.criteria.CriteriaQuery; |
| |
| import org.apache.openjpa.jdbc.conf.JDBCConfiguration; |
| import org.apache.openjpa.jdbc.sql.DBDictionary; |
| import org.apache.openjpa.jdbc.sql.DerbyDictionary; |
| import org.apache.openjpa.jdbc.sql.HSQLDictionary; |
| import org.apache.openjpa.jdbc.sql.MariaDBDictionary; |
| import org.apache.openjpa.jdbc.sql.MySQLDictionary; |
| import org.apache.openjpa.jdbc.sql.OracleDictionary; |
| import org.apache.openjpa.lib.jdbc.JDBCListener; |
| import org.apache.openjpa.lib.jdbc.ReportingSQLException; |
| import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI; |
| import org.apache.openjpa.persistence.test.AllowFailure; |
| import org.apache.openjpa.persistence.test.FilteringJDBCListener; |
| |
| import junit.framework.TestCase; |
| |
| /** |
| * Extends junit.framework.TestCase |
| * |
| * @version $Rev$ $Date$ |
| */ |
| public abstract class AbstractCriteriaTestCase extends TestCase { |
| |
| protected abstract SQLAuditor getAuditor(); |
| |
| protected abstract OpenJPAEntityManagerFactorySPI getEntityManagerFactory(); |
| |
| protected abstract EntityManager getEntityManager(); |
| private DBDictionary dict = null; |
| |
| public DBDictionary getDictionary() { |
| return dict; |
| } |
| |
| @Override |
| public void tearDown() throws Exception { |
| dict = null; |
| super.tearDown(); |
| } |
| |
| /** |
| * Create an entity manager factory for persistence unit <code>pu</code>. Put {@link #CLEAR_TABLES} in this list to |
| * tell the test framework to delete all table contents before running the tests. |
| * NOTE: Caller must close the returned EMF. |
| * |
| * @param props |
| * list of persistent types used in testing and/or configuration values in the form |
| * key,value,key,value... |
| */ |
| protected OpenJPAEntityManagerFactorySPI createNamedEMF(Class<?>... types) { |
| Map<Object, Object> map = new HashMap<>(); |
| map.put("openjpa.jdbc.SynchronizeMappings", "buildSchema(ForeignKeys=true," + "SchemaAction='add')"); |
| map.put("openjpa.jdbc.QuerySQLCache", "false"); |
| map.put("openjpa.DynamicEnhancementAgent", "false"); |
| map.put("openjpa.RuntimeUnenhancedClasses", "unsupported"); |
| map.put("openjpa.Compatibility", "QuotedNumbersInQueries=true"); |
| map.put("openjpa.jdbc.JDBCListeners", new JDBCListener[] { getAuditor() }); |
| |
| StringBuffer buf = new StringBuffer(); |
| for (Class<?> c : types) { |
| if (buf.length() > 0) |
| buf.append(";"); |
| buf.append(c.getName()); |
| } |
| |
| map.put("openjpa.MetaDataFactory", "jpa(Types=" + buf.toString() + ")"); |
| |
| Map<Object, Object> config = new HashMap<>(System.getProperties()); |
| config.putAll(map); |
| return (OpenJPAEntityManagerFactorySPI) Persistence.createEntityManagerFactory("test", config); |
| } |
| |
| void setDictionary() { |
| JDBCConfiguration conf = (JDBCConfiguration) getEntityManagerFactory().getConfiguration(); |
| dict = conf.getDBDictionaryInstance(); |
| if (dict instanceof DerbyDictionary || dict instanceof HSQLDictionary) { |
| dict.requiresCastForComparisons = false; |
| dict.requiresCastForMathFunctions = false; |
| } else if (dict instanceof OracleDictionary) { |
| dict.setJoinSyntax("sql92"); |
| } |
| } |
| |
| /** |
| * Executes the given CriteriaQuery and JPQL string and compare their respective SQLs for equality. |
| */ |
| void assertEquivalence(CriteriaQuery<?> c, String jpql) { |
| assertEquivalence(null, c, jpql, null); |
| } |
| |
| /** |
| * Executes the given CriteriaQuery and JPQL string and compare their respective SQLs for equality |
| * with the expected SQL. |
| */ |
| void assertEquivalence(CriteriaQuery<?> c, String jpql, String expectedSQL) { |
| assertEquivalence(null, c, jpql, expectedSQL); |
| } |
| |
| /** |
| * Executes the given CriteriaQuery and JPQL string after decorating with the given decorator, |
| * and then compare their respective SQLs for equality. |
| */ |
| void assertEquivalence(QueryDecorator decorator, CriteriaQuery<?> c, String jpql) { |
| assertEquivalence(decorator, c, jpql, null); |
| } |
| |
| /** |
| * Executes the given CriteriaQuery and JPQL string and compare their respective SQLs for equality. |
| * Decorates the query with the given decorator before execution. |
| * supplied parameters, if any. |
| */ |
| void assertEquivalence(QueryDecorator decorator, CriteriaQuery<?> c, String jpql, String expectedSQL) { |
| System.err.println("JPQL:[" + jpql + "]"); |
| System.err.println("CQL :[" + ((OpenJPACriteriaQuery<?>)c).toCQL()); |
| Query cQ = getEntityManager().createQuery(c); |
| Query jQ = getEntityManager().createQuery(jpql); |
| if (decorator != null) { |
| decorator.decorate(cQ); |
| decorator.decorate(jQ); |
| } |
| executeAndCompareSQL(jpql, cQ, jQ, expectedSQL); |
| } |
| |
| /** |
| * Execute the two given queries. The first query originated from a JPQL string must be well-formed. The second |
| * query originated from a Criteria is being tested. |
| * |
| * @param sqls |
| * The target SQL for the queries will be filled-in the given array. |
| * @return true if both queries execute successfully. |
| */ |
| void executeAndCompareSQL(String jpql, Query cQ, Query jQ, String expectedSQL) { |
| List<String> jSQL = null; |
| List<String> cSQL = null; |
| try { |
| jSQL = executeQueryAndCollectSQL(jQ); |
| } catch (Exception e) { |
| StringWriter w = new StringWriter(); |
| e.printStackTrace(new PrintWriter(w)); |
| fail("JPQL " + jpql + " failed to execute\r\n" + w); |
| } |
| getEntityManager().clear(); |
| try { |
| cSQL = executeQueryAndCollectSQL(cQ); |
| } catch (Exception e) { |
| StringWriter w = new StringWriter(); |
| e.printStackTrace(new PrintWriter(w)); |
| fail("CriteriaQuery corresponding to " + jpql + " failed to execute\r\n" + w); |
| } |
| |
| if (jSQL.size() != cSQL.size()) { |
| printSQL("Target SQL for JPQL", jSQL); |
| printSQL("Target SQL for CriteriaQuery", cSQL); |
| assertEquals("No. of SQL generated for JPQL and CriteriaQuery for " + jpql + " is different", jSQL.size(), |
| cSQL.size()); |
| } |
| |
| if (!(dict instanceof DerbyDictionary || dict instanceof MySQLDictionary || dict instanceof MariaDBDictionary)) |
| return; |
| |
| for (int i = 0; i < jSQL.size(); i++) { |
| if (!jSQL.get(i).equalsIgnoreCase(cSQL.get(i))) { |
| printSQL("Target SQL for JPQL", jSQL); |
| printSQL("Target SQL for CriteriaQuery", cSQL); |
| assertTrue(i + "-th SQL for JPQL and CriteriaQuery for " + jpql + " is different\r\n" + |
| "JPQL = [" + jSQL.get(i) + "]\r\n" + |
| "CSQL = [" + cSQL.get(i) + "]\r\n", |
| jSQL.get(i).equalsIgnoreCase(cSQL.get(i))); |
| } |
| } |
| |
| if (expectedSQL != null) { |
| assertTrue("SQL for JPQL " + jpql + " is different than expecetd " + expectedSQL, |
| jSQL.get(0).equalsIgnoreCase(expectedSQL)); |
| |
| } |
| } |
| |
| void executeAndCompareSQL(String jpql, String expectedSQL) { |
| Query jQ = getEntityManager().createQuery(jpql); |
| |
| List<String> jSQL = null; |
| try { |
| jSQL = executeQueryAndCollectSQL(jQ); |
| } catch (Exception e) { |
| StringWriter w = new StringWriter(); |
| e.printStackTrace(new PrintWriter(w)); |
| fail("JPQL " + jpql + " failed to execute\r\n" + w); |
| } |
| |
| if (!(dict instanceof DerbyDictionary || dict instanceof MySQLDictionary || dict instanceof MariaDBDictionary)) |
| return; |
| |
| for (int i = 0; i < jSQL.size(); i++) { |
| if (!jSQL.get(i).equalsIgnoreCase(expectedSQL)) { |
| printSQL("SQL for JPQL", jSQL.get(i)); |
| printSQL("Expected SQL", expectedSQL); |
| assertTrue(i + "-th SQL for JPQL: " + jSQL.get(i) + " are different than Expected SQL " + expectedSQL, |
| expectedSQL.equalsIgnoreCase(jSQL.get(i))); |
| } |
| } |
| } |
| |
| void executeAndCompareSQL(Query jQ, String expectedSQL) { |
| List<String> jSQL = null; |
| try { |
| jSQL = executeQueryAndCollectSQL(jQ); |
| } catch (Exception e) { |
| StringWriter w = new StringWriter(); |
| e.printStackTrace(new PrintWriter(w)); |
| fail(w.toString()); |
| } |
| |
| if (!(dict instanceof DerbyDictionary || dict instanceof MySQLDictionary || dict instanceof MariaDBDictionary)) |
| return; |
| |
| String jSql = jSQL.get(0).trim(); |
| if (jSql.indexOf("optimize for 1 row") != -1) |
| jSql = jSql.substring(0, jSql.indexOf("optimize for 1 row")).trim(); |
| |
| if (!jSql.equalsIgnoreCase(expectedSQL)) { |
| printSQL("SQL for JPQL", jSql); |
| assertTrue("SQL for JPQL " + jSql + " is different than expecetd " + expectedSQL, |
| expectedSQL.equalsIgnoreCase(jSql)); |
| } |
| } |
| |
| void executeExpectFail(CriteriaQuery<?> c, String jpql) { |
| try { |
| Query cQ = getEntityManager().createQuery(c); |
| executeQueryAndCollectSQL(cQ); |
| fail("CriteriaQuery corresponding to " + jpql + " is expected to fail\r\n"); |
| } catch (Exception e) { |
| // expected |
| } |
| } |
| |
| void executeExpectFail(CriteriaQuery<?> c, String jpql, String[] paramNames, Object[] params) { |
| try { |
| Query cQ = getEntityManager().createQuery(c); |
| for (int i = 0; i < params.length; i++) |
| cQ.setParameter(paramNames[i], params[i]); |
| executeQueryAndCollectSQL(cQ); |
| fail("CriteriaQuery corresponding to " + jpql + " is expected to fail\r\n"); |
| } catch (Exception e) { |
| // expected |
| } |
| } |
| |
| void executeExpectFail(String jpql) { |
| try { |
| Query jQ = getEntityManager().createQuery(jpql); |
| executeQueryAndCollectSQL(jQ); |
| fail("JPQL " + jpql + " is expected to Failed to execute\r\n"); |
| } catch (Exception e) { |
| // expected |
| } |
| } |
| |
| void executeExpectFail(String jpql, String[] paramNames, Object[] params) { |
| try { |
| Query jQ = getEntityManager().createQuery(jpql); |
| for (int i = 0; i < params.length; i++) |
| jQ.setParameter(paramNames[i], params[i]); |
| executeQueryAndCollectSQL(jQ); |
| fail("JPQL " + jpql + " is expected to Failed to execute\r\n"); |
| } catch (Exception e) { |
| // expected |
| } |
| } |
| |
| void printSQL(String header, String sql) { |
| System.err.println(header); |
| System.err.println(sql); |
| } |
| |
| void printSQL(String header, List<String> sqls) { |
| System.err.println(header); |
| for (int i = 0; sqls != null && i < sqls.size(); i++) { |
| System.err.println(i + ":" + sqls.get(i)); |
| } |
| } |
| |
| |
| /** |
| * Execute the given query and return the generated SQL. If the query execution fail because the generated SQL is |
| * ill-formed, then raised exception should carry the ill-formed SQL for diagnosis. |
| */ |
| List<String> executeQueryAndCollectSQL(Query q) { |
| getAuditor().clear(); |
| try { |
| q.getResultList(); |
| } catch (Exception e) { |
| throw new RuntimeException(extractSQL(e), e); |
| } |
| assertFalse(getAuditor().getSQLs().isEmpty()); |
| return getAuditor().getSQLs(); |
| } |
| |
| void executeAndCompareSQL(CriteriaQuery<?> q, String expectedSQL) { |
| executeAndCompareSQL(getEntityManager().createQuery(q), expectedSQL); |
| } |
| |
| String extractSQL(Exception e) { |
| Throwable t = e.getCause(); |
| if (t instanceof ReportingSQLException) { |
| return ((ReportingSQLException) t).getSQL(); |
| } |
| return "Can not extract SQL from exception " + e; |
| } |
| |
| @Override |
| public void runBare() throws Throwable { |
| try { |
| super.runBare(); |
| } catch (Throwable t) { |
| AllowFailure allowFailure = getAllowFailure(); |
| if (allowFailure != null && allowFailure.value()) { |
| System.err.println("*** FAILED (but ignored): " + this); |
| System.err.println("*** Reason : " + allowFailure.message()); |
| System.err.println("Stacktrace of failure"); |
| t.printStackTrace(); |
| } else { |
| throw t; |
| } |
| } |
| } |
| |
| /** |
| * Affirms if the test case or the test method is annotated with |
| * |
| * @AllowFailure. Method level annotation has higher precedence than Class level annotation. |
| * |
| * Set -DIgnoreAllowFailure=true to ignore this directive altogether. |
| */ |
| protected AllowFailure getAllowFailure() { |
| if (Boolean.getBoolean("IgnoreAllowFailure")) { |
| return null; |
| } |
| try { |
| Method runMethod = getClass().getMethod(getName(), (Class[]) null); |
| AllowFailure anno = runMethod.getAnnotation(AllowFailure.class); |
| if (anno != null) { |
| return anno; |
| } |
| } catch (SecurityException e) { |
| // ignore |
| } catch (NoSuchMethodException e) { |
| // ignore |
| } |
| return getClass().getAnnotation(AllowFailure.class); |
| } |
| |
| public class SQLAuditor extends FilteringJDBCListener { |
| public SQLAuditor() { |
| super(new ArrayList<String>()); |
| } |
| |
| List<String> getSQLs() { |
| return getCopy(); |
| } |
| } |
| |
| /** |
| * Interface to decorate a query such as set parameter or range before execution. |
| * |
| */ |
| public interface QueryDecorator { |
| void decorate(Query q); |
| } |
| } |