blob: e2e44c4a59d326d3e94a63d1929d4a98e683691f [file] [log] [blame]
/*
* 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
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);
}
}