| /* |
| * 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); |
| } |
| } |