/******************************************************************************* * Copyright (c) 1998, 2015 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 v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.queries; import java.util.*; import org.eclipse.persistence.internal.helper.*; import org.eclipse.persistence.config.ResultType; import org.eclipse.persistence.exceptions.*; import org.eclipse.persistence.internal.queries.*; import org.eclipse.persistence.internal.sessions.AbstractRecord; import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.mappings.converters.Converter; /** * <p><b>Purpose</b>: * Concrete class to perform read using raw SQL. * * <p><b>Responsibilities</b>: * Execute a selecting raw SQL string. * This returns a Collection of the Records representing the result set. * * @author Yvon Lavoie * @since TOPLink/Java 1.0 */ public class DataReadQuery extends ReadQuery { protected ContainerPolicy containerPolicy; /** * Allow return type to be configured, MAP, ARRAY, VALUE, ATTRIBUTE (MAP is the default, i.e. DatabaseRecord). */ protected int resultType; /** A Map (DatabaseRecord) is returned for each row. */ public static final int MAP = 0; /** An Object[] of values is returned for each row. */ public static final int ARRAY = 1; /** A single value is returned. */ public static final int VALUE = 2; /** A single value is returned for each row. */ public static final int ATTRIBUTE = 3; /** Auto, a single value if a single field is selected, otherwise an Object[] (JPA default). */ public static final int AUTO = 4; /** * PUBLIC: * Initialize the state of the query. */ public DataReadQuery() { super(); this.shouldMaintainCache = false; this.resultType = MAP; setContainerPolicy(ContainerPolicy.buildDefaultPolicy()); } /** * PUBLIC: * Initialize the query to use the specified SQL string. * Warning: Allowing an unverified SQL string to be passed into this * method makes your application vulnerable to SQL injection attacks. */ public DataReadQuery(String sqlString) { this(); setSQLString(sqlString); } /** * PUBLIC: * Initialize the query to use the specified call. */ public DataReadQuery(Call call) { this(); setCall(call); } /** * INTERNAL: * <P> This method is called by the object builder when building an original. * It will cause the original to be cached in the query results if the query * is set to do so. */ @Override public void cacheResult(Object results) { setTemporaryCachedQueryResults(results); } /** * INTERNAL: * Clone the query. */ @Override public Object clone() { DataReadQuery cloneQuery = (DataReadQuery)super.clone(); cloneQuery.containerPolicy = this.containerPolicy.clone(cloneQuery); return cloneQuery; } /** * INTERNAL: * Execute the query. If there are cached results return those. * This must override the super to support result caching. * * @param session - the session in which the receiver will be executed. * @return An object or collection, the result of executing the query. * @exception DatabaseException - an error has occurred on the database */ @Override public Object execute(AbstractSession session, AbstractRecord row) throws DatabaseException { if (shouldCacheQueryResults()) { if (this.containerPolicy.overridesRead()) { throw QueryException.cannotCacheCursorResultsOnQuery(this); } if (this.isPrepared) {// only prepared queries can have cached results. Object results = getQueryResults(session, row, true); // Bug6138532 - if results are "cached no results", return null or an empty collection. if (results == InvalidObject.instance) { if (this.resultType == VALUE) { return null; } else { return this.containerPolicy.containerInstance(0); } } if (results != null) { return results; } } } return super.execute(session, row); } /** * INTERNAL: * Execute the query. * Perform the work to execute the SQL string. * @exception DatabaseException an error has occurred on the database * @return a collection or cursor of Records representing the result set */ @Override public Object executeDatabaseQuery() throws DatabaseException { if (getContainerPolicy().overridesRead()) { return getContainerPolicy().execute(); } return executeNonCursor(); } /** * INTERNAL: * Conversion not supported. */ public Converter getValueConverter() { return null; } /** * INTERNAL: * Build the result value for the row. */ @Override public Object buildObject(AbstractRecord row) { if (this.resultType == AUTO) { List values = row.getValues(); if (values.size() == 1) { return row.getValues().get(0); } else { return row.getValues().toArray(); } } else if (this.resultType == ARRAY) { return row.getValues().toArray(); } else if (this.resultType == ATTRIBUTE) { // Use get with field for XML records. Object value = row.get(row.getFields().get(0)); if (getValueConverter() != null) { value = getValueConverter().convertDataValueToObjectValue(value, this.session); } return value; } return row; } /** * INTERNAL: * The results are *not* in a cursor, build the collection. * Cache the results in temporaryCachedQueryResults. */ protected Object executeNonCursor() throws DatabaseException { Vector rows = getQueryMechanism().executeSelect(); Object results = null; if (this.resultType == VALUE) { if (!rows.isEmpty()) { AbstractRecord record = (AbstractRecord)rows.get(0); // Use get with field for XML records. results = record.get(record.getFields().get(0)); if (getValueConverter() != null) { results = getValueConverter().convertDataValueToObjectValue(results, this.session); } } } else { int size = rows.size(); ContainerPolicy containerPolicy = getContainerPolicy(); results = containerPolicy.containerInstance(size); if(containerPolicy.shouldAddAll()) { if(size > 0) { List values = new ArrayList(size); for (int index = 0; index < size; index++) { AbstractRecord row = (AbstractRecord)rows.get(index); Object value = buildObject(row); values.add(value); } containerPolicy.addAll(values, results, this.session, rows, this, null, true); } } else { for (int index = 0; index < size; index++) { AbstractRecord row = (AbstractRecord)rows.get(index); Object value = buildObject(row); containerPolicy.addInto(value, results, this.session, row, this, null, true); } } } // Bug 6135563 - cache DataReadQuery results verbatim, as ObjectBuilder is not invoked cacheResult(results); return results; } /** * PUBLIC: * Return the query's ContainerPolicy. */ public ContainerPolicy getContainerPolicy() { return containerPolicy; } /** * PUBLIC: * Return if this is a data read query. */ @Override public boolean isDataReadQuery() { return true; } /** * INTERNAL: * Prepare the receiver for execution in a session. */ @Override protected void prepare() { super.prepare(); this.containerPolicy.prepare(this, this.session); if (this.containerPolicy.overridesRead()) { return; } getQueryMechanism().prepareExecuteSelect(); } /** * INTERNAL: * Prepare the receiver for execution in a session. */ @Override public void prepareForExecution() throws QueryException { super.prepareForExecution(); this.containerPolicy.prepareForExecution(); } /** * INTERNAL: * Used by RemoteSession. */ @Override public Object remoteExecute() { if (getContainerPolicy().overridesRead()) { return getContainerPolicy().remoteExecute(); } return super.remoteExecute(); } /** * PUBLIC: * Set the container policy. */ public void setContainerPolicy(ContainerPolicy containerPolicy) { // Fix for BUG 3337003 - TopLink OX will try to set this to null if // it is not set in the deployment XML. So don't allow it to do that. if (containerPolicy == null) { return; } this.containerPolicy = containerPolicy; } /** * PUBLIC: * Configure the query to use an instance of the specified container class * to hold the target objects. * The container class must implement (directly or indirectly) the Collection interface. */ public void useCollectionClass(Class concreteClass) { setContainerPolicy(ContainerPolicy.buildPolicyFor(concreteClass)); } /** * PUBLIC: * Use a CursoredStream as the result collection. * The initial read size is 10 and page size is 5. */ public void useCursoredStream() { useCursoredStream(10, 5); } /** * Return the result type to be configured, MAP, ARRAY, VALUE, ATTRIBUTE (MAP is the default, DatabaseRecord). * @see ResultType */ public int getResultType() { return resultType; } /** * Set the result type to be configured, MAP, ARRAY, VALUE, ATTRIBUTE (MAP is the default, DatabaseRecord). */ public void setResultType(int resultType) { this.resultType = resultType; } /** * Set the result type to be configured, Map, Array, Value, Attribute (Map is the default, DatabaseRecord). * @see ResultType */ public void setResultType(String resultType) { if (ResultType.Map.equals(resultType)) { this.resultType = MAP; } else if (ResultType.Array.equals(resultType)) { this.resultType = ARRAY; } else if (ResultType.Value.equals(resultType)) { this.resultType = VALUE; } else if (ResultType.Attribute.equals(resultType)) { this.resultType = ATTRIBUTE; } } /** * PUBLIC: * Use a CursoredStream as the result collection. * @param initialReadSize the initial number of objects to read * @param pageSize the number of objects to read when more objects * are needed from the database */ public void useCursoredStream(int initialReadSize, int pageSize) { setContainerPolicy(new CursoredStreamPolicy(this, initialReadSize, pageSize)); } /** * PUBLIC: * Use a CursoredStream as the result collection. * @param initialReadSize the initial number of objects to read * @param pageSize the number of objects to read when more objects * are needed from the database * @param sizeQuery a query that will return the size of the result set; * this must be set if an expression is not used (i.e. custom SQL) */ public void useCursoredStream(int initialReadSize, int pageSize, ValueReadQuery sizeQuery) { setContainerPolicy(new CursoredStreamPolicy(this, initialReadSize, pageSize, sizeQuery)); } /** * PUBLIC: * Use a ScrollableCursor as the result collection. */ public void useScrollableCursor() { useScrollableCursor(10); } /** * PUBLIC: * Use a ScrollableCursor as the result collection. * @param pageSize the number of elements to be read into a the cursor * when more elements are needed from the database. */ public void useScrollableCursor(int pageSize) { setContainerPolicy(new ScrollableCursorPolicy(this, pageSize)); } /** * PUBLIC: * Use a ScrollableCursor as the result collection. * @param policy the scrollable cursor policy allows for additional result set options. * Example:<p> * ScrollableCursorPolicy policy = new ScrollableCursorPolicy()<p> * policy.setResultSetType(ScrollableCursorPolicy.TYPE_SCROLL_INSENSITIVE);<p> * query.useScrollableCursor(policy); */ public void useScrollableCursor(ScrollableCursorPolicy policy) { policy.setQuery(this); setContainerPolicy(policy); } }