| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation from Oracle TopLink |
| // Zoltan NAGY & tware - updated support for MaxRows |
| // 11/01/2010-2.2 Guy Pelletier |
| // - 322916: getParameter on Query throws NPE |
| // 11/09/2010-2.1 Michael O'Brien |
| // - 329089: PERF: EJBQueryImpl.setParamenterInternal() move indexOf check inside non-native block |
| // 02/08/2012-2.4 Guy Pelletier |
| // - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls |
| // 08/11/2012-2.5 Guy Pelletier |
| // - 393867: Named queries do not work when using EM level Table Per Tenant Multitenancy. |
| package org.eclipse.persistence.internal.jpa; |
| |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import jakarta.persistence.FlushModeType; |
| import jakarta.persistence.LockModeType; |
| import jakarta.persistence.LockTimeoutException; |
| import jakarta.persistence.Parameter; |
| import jakarta.persistence.PersistenceException; |
| import jakarta.persistence.TemporalType; |
| import jakarta.persistence.TypedQuery; |
| |
| import org.eclipse.persistence.exceptions.QueryException; |
| import org.eclipse.persistence.expressions.Expression; |
| import org.eclipse.persistence.internal.databaseaccess.DatasourcePlatform; |
| import org.eclipse.persistence.internal.helper.ClassConstants; |
| import org.eclipse.persistence.internal.helper.Helper; |
| import org.eclipse.persistence.internal.jpa.querydef.ParameterExpressionImpl; |
| import org.eclipse.persistence.internal.localization.ExceptionLocalization; |
| import org.eclipse.persistence.internal.queries.ContainerPolicy; |
| import org.eclipse.persistence.internal.queries.JPQLCallQueryMechanism; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.jpa.JpaQuery; |
| import org.eclipse.persistence.queries.Cursor; |
| import org.eclipse.persistence.queries.DataReadQuery; |
| import org.eclipse.persistence.queries.DatabaseQuery; |
| import org.eclipse.persistence.queries.JPAQueryBuilder; |
| import org.eclipse.persistence.queries.ModifyQuery; |
| import org.eclipse.persistence.queries.ObjectLevelReadQuery; |
| import org.eclipse.persistence.queries.ReadAllQuery; |
| import org.eclipse.persistence.queries.ReadObjectQuery; |
| import org.eclipse.persistence.queries.ReadQuery; |
| import org.eclipse.persistence.queries.ResultSetMappingQuery; |
| import org.eclipse.persistence.sessions.DatabaseRecord; |
| |
| /** |
| * Concrete JPA query class. The JPA query wraps a DatabaseQuery which is |
| * executed. |
| */ |
| public class EJBQueryImpl<X> extends QueryImpl implements JpaQuery<X> { |
| /** |
| * Base constructor for EJBQueryImpl. Initializes basic variables. |
| */ |
| protected EJBQueryImpl(EntityManagerImpl entityManager) { |
| super(entityManager); |
| } |
| |
| /** |
| * Create an EJBQueryImpl with a DatabaseQuery. |
| */ |
| public EJBQueryImpl(DatabaseQuery query, EntityManagerImpl entityManager) { |
| super(query, entityManager); |
| } |
| |
| /** |
| * Build an EJBQueryImpl based on the given jpql string. |
| */ |
| public EJBQueryImpl(String jpql, EntityManagerImpl entityManager) { |
| this(jpql, entityManager, false); |
| } |
| |
| /** |
| * Create an EJBQueryImpl with either a query name or an jpql string. |
| * |
| * @param isNamedQuery |
| * determines whether to treat the queryDescription as jpql or a |
| * query name. |
| */ |
| public EJBQueryImpl(String queryDescription, EntityManagerImpl entityManager, boolean isNamedQuery) { |
| super(entityManager); |
| if (isNamedQuery) { |
| this.queryName = queryDescription; |
| } else { |
| if (databaseQuery == null) { |
| databaseQuery = buildEJBQLDatabaseQuery(queryDescription, entityManager.getActiveSessionIfExists()); |
| } |
| } |
| } |
| |
| /** |
| * Build a DatabaseQuery from an jpql string. |
| * |
| * @param jpql |
| * @param session |
| * the session to get the descriptors for this query for. |
| * @return a DatabaseQuery representing the given jpql. |
| */ |
| public static DatabaseQuery buildEJBQLDatabaseQuery(String jpql, AbstractSession session) { |
| return buildEJBQLDatabaseQuery(null, jpql, session, null, null, session.getDatasourcePlatform().getConversionManager().getLoader()); |
| } |
| |
| /** |
| * Build a DatabaseQuery from an JPQL string. |
| * |
| * @param jpqlQuery |
| * the JPQL string. |
| * @param session |
| * the session to get the descriptors for this query for. |
| * @param hints |
| * a list of hints to be applied to the query. |
| * @return a DatabaseQuery representing the given jpql. |
| */ |
| public static DatabaseQuery buildEJBQLDatabaseQuery(String queryName, String jpqlQuery, AbstractSession session, Enum lockMode, Map<String, Object> hints, ClassLoader classLoader) { |
| // PERF: Check if the JPQL has already been parsed. |
| // Only allow queries with default properties to be parse cached. |
| boolean isCacheable = (queryName == null) && (hints == null); |
| DatabaseQuery databaseQuery = null; |
| if (isCacheable) { |
| databaseQuery = (DatabaseQuery) session.getProject().getJPQLParseCache().get(jpqlQuery); |
| } |
| if ((databaseQuery == null) || (!databaseQuery.isPrepared())) { |
| JPAQueryBuilder queryBuilder = session.getQueryBuilder(); |
| databaseQuery = queryBuilder.buildQuery(jpqlQuery, session); |
| |
| // If the query uses fetch joins, need to use JPA default of not |
| // filtering duplicates. |
| if (databaseQuery.isReadAllQuery()) { |
| ReadAllQuery readAllQuery = (ReadAllQuery) databaseQuery; |
| if (readAllQuery.hasJoining() && (readAllQuery.getDistinctState() == ReadAllQuery.DONT_USE_DISTINCT)) { |
| readAllQuery.setShouldFilterDuplicates(false); |
| } |
| } else if (databaseQuery.isModifyQuery()) { |
| // By default, do not batch modify queries, as row count must be returned. |
| ((ModifyQuery)databaseQuery).setIsBatchExecutionSupported(false); |
| } |
| |
| ((JPQLCallQueryMechanism) databaseQuery.getQueryMechanism()).getJPQLCall().setIsParsed(true); |
| |
| // Apply the lock mode. |
| if (lockMode != null && !lockMode.name().equals(ObjectLevelReadQuery.NONE)) { |
| if (databaseQuery.isObjectLevelReadQuery()) { |
| // If setting the lock mode returns true, we were unable to |
| // set the lock mode, throw an exception. |
| if (((ObjectLevelReadQuery) databaseQuery).setLockModeType(lockMode.name(), session)) { |
| throw new PersistenceException(ExceptionLocalization.buildMessage("ejb30-wrong-lock_called_without_version_locking-index", null)); |
| } |
| } else { |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("invalid_lock_query", null)); |
| } |
| } |
| |
| // Apply any query hints. |
| databaseQuery = applyHints(hints, databaseQuery, classLoader, session); |
| |
| // If a primary key query, switch to read-object to allow cache hit. |
| if (databaseQuery.isReadAllQuery() && !databaseQuery.isReportQuery() && ((ReadAllQuery)databaseQuery).shouldCheckCache()) { |
| ReadAllQuery readQuery = (ReadAllQuery)databaseQuery; |
| if ((readQuery.getContainerPolicy().getContainerClass() == ContainerPolicy.getDefaultContainerClass()) |
| && (!readQuery.hasHierarchicalExpressions())) { |
| databaseQuery.checkDescriptor(session); |
| Expression selectionCriteria = databaseQuery.getSelectionCriteria(); |
| if ((selectionCriteria != null) |
| && (databaseQuery.getDescriptor().getObjectBuilder().isPrimaryKeyExpression(true, selectionCriteria, session) |
| || (databaseQuery.getDescriptor().getCachePolicy().isIndexableExpression(selectionCriteria, databaseQuery.getDescriptor(), session)))) { |
| ReadObjectQuery newQuery = new ReadObjectQuery(); |
| newQuery.copyFromQuery(databaseQuery); |
| databaseQuery = newQuery; |
| } |
| } |
| } |
| |
| if (isCacheable) { |
| // Prepare query as hint may cause cloning (but not un-prepare |
| // as in read-only). |
| databaseQuery.checkPrepare(session, new DatabaseRecord()); |
| session.getProject().getJPQLParseCache().put(jpqlQuery, databaseQuery); |
| } |
| } |
| |
| return databaseQuery; |
| } |
| |
| /** |
| * Build a ReadAllQuery from a class and sql string. |
| */ |
| public static DatabaseQuery buildSQLDatabaseQuery(Class resultClass, String sqlString, ClassLoader classLoader, AbstractSession session) { |
| return buildSQLDatabaseQuery(resultClass, sqlString, null, classLoader, session); |
| } |
| |
| /** |
| * Build a ReadAllQuery for class and sql string. |
| * |
| * @param hints |
| * a list of hints to be applied to the query. |
| */ |
| public static DatabaseQuery buildSQLDatabaseQuery(Class resultClass, String sqlString, Map<String, Object> hints, ClassLoader classLoader, AbstractSession session) { |
| ReadAllQuery query = new ReadAllQuery(resultClass); |
| query.setCall(((DatasourcePlatform)session.getPlatform(resultClass)).buildNativeCall(sqlString)); |
| query.setIsUserDefined(true); |
| |
| // apply any query hints |
| return applyHints(hints, query, classLoader, session); |
| } |
| |
| /** |
| * Build a DataReadQuery from a sql string. |
| */ |
| public static DatabaseQuery buildSQLDatabaseQuery(String sqlString, ClassLoader classLoader, AbstractSession session) { |
| return buildSQLDatabaseQuery(sqlString, new HashMap<String, Object>(), classLoader, session); |
| } |
| |
| /** |
| * Build a DataReadQuery from a sql string. |
| */ |
| public static DatabaseQuery buildSQLDatabaseQuery(String sqlString, Map<String, Object> hints, ClassLoader classLoader, AbstractSession session) { |
| DataReadQuery query = new DataReadQuery(); |
| query.setResultType(DataReadQuery.AUTO); |
| query.setSQLString(sqlString); |
| query.setIsUserDefined(true); |
| |
| // apply any query hints |
| return applyHints(hints, query, classLoader, session); |
| } |
| |
| /** |
| * Build a ResultSetMappingQuery from a sql result set mapping name and sql |
| * string. |
| */ |
| public static DatabaseQuery buildSQLDatabaseQuery(String sqlResultSetMappingName, String sqlString, ClassLoader classLoader, AbstractSession session) { |
| return buildSQLDatabaseQuery(sqlResultSetMappingName, sqlString, null, classLoader, session); |
| } |
| |
| /** |
| * Build a ResultSetMappingQuery from a sql result set mapping name and sql |
| * string. |
| * |
| * @param hints |
| * a list of hints to be applied to the query. |
| */ |
| public static DatabaseQuery buildSQLDatabaseQuery(String sqlResultSetMappingName, String sqlString, Map<String, Object> hints, ClassLoader classLoader, AbstractSession session) { |
| ResultSetMappingQuery query = new ResultSetMappingQuery(); |
| query.setSQLResultSetMappingName(sqlResultSetMappingName); |
| query.setCall(((DatasourcePlatform)session.getDatasourcePlatform()).buildNativeCall(sqlString)); |
| query.setIsUserDefined(true); |
| |
| // apply any query hints |
| return applyHints(hints, query, classLoader, session); |
| } |
| |
| /** |
| * Set an implementation-specific hint. If the hint name is not recognized, |
| * it is silently ignored. |
| * |
| * @param hintName |
| * @param value |
| * @return the same query instance |
| * @throws IllegalArgumentException |
| * if the second argument is not valid for the implementation |
| */ |
| @Override |
| public TypedQuery<X> setHint(String hintName, Object value) { |
| try { |
| entityManager.verifyOpen(); |
| setHintInternal(hintName, value); |
| return this; |
| } catch (RuntimeException e) { |
| setRollbackOnly(); |
| throw e; |
| } |
| } |
| |
| /** |
| * Set the lock mode type to be used for the query execution. |
| * |
| * @param lockMode |
| * @throws IllegalStateException |
| * if not a Java Persistence query language SELECT query |
| */ |
| @Override |
| public EJBQueryImpl setLockMode(LockModeType lockMode) { |
| return (EJBQueryImpl) super.setLockMode(lockMode); |
| } |
| |
| /** |
| * Non-standard method to return results of a ReadQuery that has a |
| * containerPolicy that returns objects as a collection rather than a List |
| * |
| * @return Collection of results |
| */ |
| @Override |
| public Collection getResultCollection() { |
| // bug51411440: need to throw IllegalStateException if query |
| // executed on closed em |
| this.entityManager.verifyOpenWithSetRollbackOnly(); |
| setAsSQLReadQuery(); |
| propagateResultProperties(); |
| // bug:4297903, check container policy class and throw exception if its |
| // not the right type |
| DatabaseQuery query = getDatabaseQueryInternal(); |
| try { |
| if (query.isReadAllQuery()) { |
| Class containerClass = ((ReadAllQuery) getDatabaseQueryInternal()).getContainerPolicy().getContainerClass(); |
| if (!Helper.classImplementsInterface(containerClass, ClassConstants.Collection_Class)) { |
| throw QueryException.invalidContainerClass(containerClass, ClassConstants.Collection_Class); |
| } |
| } else if (query.isReadObjectQuery()) { |
| List<Object> resultList = new ArrayList<>(); |
| Object result = executeReadQuery(); |
| if (result != null) { |
| resultList.add(executeReadQuery()); |
| } |
| return resultList; |
| } else if (!query.isReadQuery()) { |
| throw new IllegalStateException(ExceptionLocalization.buildMessage("incorrect_query_for_get_result_collection")); |
| } |
| |
| return (Collection) executeReadQuery(); |
| } catch (LockTimeoutException exception) { |
| throw exception; |
| } catch (PersistenceException exception) { |
| setRollbackOnly(); |
| throw exception; |
| } catch (IllegalStateException exception) { |
| setRollbackOnly(); |
| throw exception; |
| } catch (RuntimeException exception) { |
| setRollbackOnly(); |
| throw new PersistenceException(exception); |
| } |
| } |
| |
| /** |
| * Non-standard method to return results of a ReadQuery that uses a Cursor. |
| * |
| * @return Cursor on results, either a CursoredStream, or ScrollableCursor |
| */ |
| @Override |
| public Cursor getResultCursor() { |
| // bug51411440: need to throw IllegalStateException if query executed on closed em |
| this.entityManager.verifyOpenWithSetRollbackOnly(); |
| try { |
| setAsSQLReadQuery(); |
| propagateResultProperties(); |
| // bug:4297903, check container policy class and throw exception if its |
| // not the right type |
| if (getDatabaseQueryInternal() instanceof ReadAllQuery) { |
| if (!((ReadAllQuery) getDatabaseQueryInternal()).getContainerPolicy().isCursorPolicy()) { |
| Class containerClass = ((ReadAllQuery) getDatabaseQueryInternal()).getContainerPolicy().getContainerClass(); |
| throw QueryException.invalidContainerClass(containerClass, Cursor.class); |
| } |
| } else if (getDatabaseQueryInternal() instanceof ReadObjectQuery) { |
| // bug:4300879, no support for ReadObjectQuery if a collection is required |
| throw QueryException.incorrectQueryObjectFound(getDatabaseQueryInternal(), ReadAllQuery.class); |
| } else if (!(getDatabaseQueryInternal() instanceof ReadQuery)) { |
| throw new IllegalStateException(ExceptionLocalization.buildMessage("incorrect_query_for_get_result_collection")); |
| } |
| |
| Object result = executeReadQuery(); |
| return (Cursor) result; |
| } catch (LockTimeoutException e) { |
| throw e; |
| } catch (PersistenceException exception) { |
| setRollbackOnly(); |
| throw exception; |
| } catch (IllegalStateException exception) { |
| setRollbackOnly(); |
| throw exception; |
| } catch (RuntimeException exception) { |
| setRollbackOnly(); |
| throw new PersistenceException(exception); |
| } |
| } |
| |
| /** |
| * Execute a query that returns a single result. |
| * |
| * @return the result |
| * @throws jakarta.persistence.EntityNotFoundException |
| * if there is no result |
| * @throws jakarta.persistence.NonUniqueResultException |
| * if more than one result |
| */ |
| @Override |
| public X getSingleResult() { |
| return (X) super.getSingleResult(); |
| } |
| |
| /** |
| * Set the position of the first result to retrieve. |
| * |
| * @param startPosition |
| * position of the first result, numbered from 0 |
| * @return the same query instance |
| */ |
| @Override |
| public EJBQueryImpl setFirstResult(int startPosition) { |
| return (EJBQueryImpl) super.setFirstResult(startPosition); |
| } |
| |
| /** |
| * Set the flush mode type to be used for the query execution. |
| * |
| * @param flushMode |
| */ |
| @Override |
| public EJBQueryImpl setFlushMode(FlushModeType flushMode) { |
| return (EJBQueryImpl) super.setFlushMode(flushMode); |
| } |
| |
| /** |
| * Set the maximum number of results to retrieve. |
| * |
| * @param maxResult |
| * @return the same query instance |
| */ |
| @Override |
| public EJBQueryImpl setMaxResults(int maxResult) { |
| return (EJBQueryImpl) super.setMaxResults(maxResult); |
| } |
| |
| /** |
| * Bind an instance of java.util.Calendar to a positional parameter. |
| * |
| * @param position |
| * @param value |
| * @param temporalType |
| * @return the same query instance |
| */ |
| @Override |
| public TypedQuery setParameter(int position, Calendar value, TemporalType temporalType) { |
| entityManager.verifyOpenWithSetRollbackOnly(); |
| return setParameter(position, convertTemporalType(value, temporalType)); |
| } |
| |
| /** |
| * Bind an instance of java.util.Date to a positional parameter. |
| * |
| * @param position |
| * @param value |
| * @param temporalType |
| * @return the same query instance |
| */ |
| @Override |
| public TypedQuery setParameter(int position, Date value, TemporalType temporalType) { |
| entityManager.verifyOpenWithSetRollbackOnly(); |
| return setParameter(position, convertTemporalType(value, temporalType)); |
| } |
| |
| /** |
| * Bind an argument to a positional parameter. |
| * |
| * @param position |
| * @param value |
| * @return the same query instance |
| */ |
| @Override |
| public TypedQuery setParameter(int position, Object value) { |
| try { |
| entityManager.verifyOpen(); |
| setParameterInternal(position, value); |
| return this; |
| } catch (RuntimeException e) { |
| setRollbackOnly(); |
| throw e; |
| } |
| } |
| |
| /** |
| * Bind an instance of java.util.Calendar to a Parameter object. |
| * |
| * @param param |
| * @param value |
| * @param temporalType |
| * @return the same query instance |
| * @throws IllegalArgumentException |
| * if position does not correspond to a parameter of the query |
| */ |
| @Override |
| public TypedQuery setParameter(Parameter<Calendar> param, Calendar value, TemporalType temporalType) { |
| entityManager.verifyOpenWithSetRollbackOnly(); |
| if (param == null) |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("NULL_PARAMETER_PASSED_TO_SET_PARAMETER")); |
| //bug 402686: type validation |
| String position = getParameterId(param); |
| ParameterExpressionImpl parameter = (ParameterExpressionImpl) this.getInternalParameters().get(position); |
| if (parameter == null ) { |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("NO_PARAMETER_WITH_NAME", new Object[] { param.toString(), this.databaseQuery })); |
| } |
| if (!parameter.getParameterType().equals(param.getParameterType())) { |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("INCORRECT_PARAMETER_TYPE", new Object[] { position, param.getParameterType() })); |
| } |
| return this.setParameter(position, value, temporalType); |
| } |
| |
| /** |
| * Bind an instance of java.util.Date to a Parameter object. |
| * |
| * @param param |
| * object |
| * @param value |
| * @param temporalType |
| * @return the same query instance |
| * @throws IllegalArgumentException |
| * if position does not correspond to a parameter of the query |
| */ |
| @Override |
| public TypedQuery setParameter(Parameter<Date> param, Date value, TemporalType temporalType) { |
| if (param == null) |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("NULL_PARAMETER_PASSED_TO_SET_PARAMETER")); |
| //bug 402686: type validation |
| String position = getParameterId(param); |
| ParameterExpressionImpl parameter = (ParameterExpressionImpl) this.getInternalParameters().get(position); |
| if (parameter == null ) { |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("NO_PARAMETER_WITH_NAME", new Object[] { param.toString(), this.databaseQuery })); |
| } |
| if (!parameter.getParameterType().equals(param.getParameterType())) { |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("INCORRECT_PARAMETER_TYPE", new Object[] { position, param.getParameterType() })); |
| } |
| return this.setParameter(position, value, temporalType); |
| } |
| |
| /** |
| * Set the value of a Parameter object. |
| * |
| * @param param |
| * parameter to be set |
| * @param value |
| * parameter value |
| * @return query instance |
| * @throws IllegalArgumentException |
| * if parameter does not correspond to a parameter of the query |
| */ |
| @Override |
| public <T> TypedQuery setParameter(Parameter<T> param, T value) { |
| if (param == null) { |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("NULL_PARAMETER_PASSED_TO_SET_PARAMETER")); |
| } |
| //bug 402686: type validation |
| String position = getParameterId(param); |
| ParameterExpressionImpl parameter = (ParameterExpressionImpl) this.getInternalParameters().get(position); |
| if (parameter == null ) { |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("NO_PARAMETER_WITH_NAME", new Object[] { param.toString(), this.databaseQuery })); |
| } |
| if (!parameter.getParameterType().equals(param.getParameterType())) { |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("INCORRECT_PARAMETER_TYPE", new Object[] { position, param.getParameterType() })); |
| } |
| return this.setParameter(position, value); |
| } |
| |
| /** |
| * Bind an instance of java.util.Calendar to a named parameter. |
| * |
| * @param name |
| * @param value |
| * @param temporalType |
| * @return the same query instance |
| */ |
| @Override |
| public TypedQuery setParameter(String name, Calendar value, TemporalType temporalType) { |
| entityManager.verifyOpenWithSetRollbackOnly(); |
| return setParameter(name, convertTemporalType(value, temporalType)); |
| } |
| |
| /** |
| * Bind an instance of java.util.Date to a named parameter. |
| * |
| * @param name |
| * @param value |
| * @param temporalType |
| * @return the same query instance |
| */ |
| @Override |
| public TypedQuery setParameter(String name, Date value, TemporalType temporalType) { |
| entityManager.verifyOpenWithSetRollbackOnly(); |
| return setParameter(name, convertTemporalType(value, temporalType)); |
| } |
| |
| /** |
| * Bind an argument to a named parameter. |
| * |
| * @param name |
| * the parameter name |
| * @param value |
| * @return the same query instance |
| */ |
| @Override |
| public TypedQuery setParameter(String name, Object value) { |
| try { |
| entityManager.verifyOpen(); |
| setParameterInternal(name, value, false); |
| return this; |
| } catch (RuntimeException e) { |
| setRollbackOnly(); |
| throw e; |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return getClass().getSimpleName() + "(" + this.databaseQuery + ")"; |
| } |
| } |