blob: 4d2617e8892eb30fe5a688ed0e603232a6e8638a [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.sql.*;
import java.util.*;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.expressions.*;
import org.eclipse.persistence.internal.databaseaccess.*;
import org.eclipse.persistence.internal.helper.*;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
/**
* <p><b>Purpose</b>:
* Abstract class for CursoredStream and ScrolableCursor
*/
public abstract class Cursor implements Enumeration, Iterator, java.io.Serializable {
/** The preparedStatement that holds the handle to the database that the results are read from. */
protected transient Statement statement;
/** The result set (cursor) that holds the handle to the database that the results are read from. */
protected transient ResultSet resultSet;
/** The session that executed the query for the stream. */
protected transient AbstractSession session;
/** The root session that executed the call for the query. Knows the database platform. */
protected transient AbstractSession executionSession;
/** The fields expected in the result set. */
protected transient Vector<DatabaseField> fields;
/** Cached size of the stream. */
protected int size = -1;
/** Read query that initialize the stream. */
public transient ReadQuery query;
/** Query policy that initialize the stream. */
public transient CursorPolicy policy;
/** Internal collection of objects. */
protected List<Object> objectCollection;
/** Conforming instances found in memory when building the result. */
protected Map<Object, Object> initiallyConformingIndex;
/** SelectionCriteria {@literal &} translation row ready for incremental conforming. */
protected Expression selectionCriteriaClone;
protected AbstractRecord translationRow;
/** Store the next row, for 1-m joining. */
protected AbstractRecord nextRow;
/** Current position in the objectCollection of the stream. */
protected int position;
/**
* INTERNAL:
* Default constructor.
*/
protected Cursor() {
super();
}
/**
* INTERNAL:
*/
protected Cursor(DatabaseCall call, CursorPolicy policy) {
ReadQuery query = policy.getQuery();
this.query = query;
this.session = query.getSession();
this.executionSession = session.getExecutionSession(query);
this.statement = call.getStatement();
this.fields = call.getFields();
this.resultSet = call.getResult();
this.policy = policy;
this.objectCollection = new Vector();
if (query.getSession().isUnitOfWork() && query.isObjectLevelReadQuery()) {
// Call register on the cursor itself. This will set up
// incremental conforming by setting the
// selection criteria clone and arguments, and building the
// intially conforming index (scans the UOW cache).
// The incremental registration/conforming is done
// in retrieveNext/PreviousObject -> buildAndRegisterObject
((ObjectLevelReadQuery)query).registerResultInUnitOfWork(this, (UnitOfWorkImpl)this.session, query.getTranslationRow(), false);// object collection is empty, so setting irrelevant.
}
}
/**
* PUBLIC:
* Closes the stream.
* This should be performed whenever the user has finished with the stream.
*/
public void close() throws DatabaseException {
RuntimeException exception = null;
try {
if (isClosed()) {
return;
}
try {
getAccessor().closeCursor(this.resultSet, this.session);
getAccessor().closeStatement(this.statement, this.session, null);
} catch (RuntimeException caughtException) {
exception = caughtException;
} finally {
//release the connection (back into the pool if Three tier)
try {
//bug 4668234 -- used to only release connections on server sessions but should always release
this.session.releaseReadConnection(this.query.getAccessor());
} catch (RuntimeException releaseException) {
if (exception == null) {
throw releaseException;
}
//else ignore
}
if (exception != null) {
throw exception;
}
}
this.statement = null;
this.resultSet = null;
this.nextRow = null;
} catch (SQLException sqlException) {
throw DatabaseException.sqlException(sqlException, getAccessor(), getSession(), false);
}
}
/**
* Close in case not closed.
*/
@Override
protected void finalize() throws DatabaseException {
close();
}
/**
* INTERNAL:
* Return the accessor associated with the cursor.
*/
public DatabaseAccessor getAccessor() {
// Assume we have a JDBC accessor
try {
return (DatabaseAccessor)this.query.getAccessor();
} catch (ClassCastException e) {
throw QueryException.invalidDatabaseAccessor(this.query.getAccessor());
}
}
/**
* INTERNAL:
* Retrieve the size of the open cursor by executing a count on the same query as the cursor.
*/
protected abstract int getCursorSize() throws DatabaseException, QueryException;
/**
* INTERNAL:
* Return the fields for the stream.
*/
public Vector<DatabaseField> getFields() {
return fields;
}
/**
* INTERNAL:
* Conforming instances found in memory when building the result.
* These objects are returned first by the cursor, and a fast lookup
* is needed to make sure the same objects appearing in the cursor are
* filtered out.
*/
public Map<Object, Object> getInitiallyConformingIndex() {
return initiallyConformingIndex;
}
/**
* INTERNAL:
* Return the internal object collection that stores the objects.
*/
public List<Object> getObjectCollection() {
return objectCollection;
}
/**
* INTERNAL:
* Return the number of items to be faulted in for the stream.
*/
public int getPageSize() {
return this.policy.getPageSize();
}
/**
* INTERNAL:
* Return the cursor policy.
*/
public CursorPolicy getPolicy() {
return policy;
}
/**
* INTERNAL:
* Return the position of the stream inside the object collection.
*/
public abstract int getPosition();
/**
* INTERNAL:
* Return the query associated with the stream.
*/
public ReadQuery getQuery() {
return this.query;
}
/**
* INTERNAL:
* Return the result set (cursor).
*/
public ResultSet getResultSet() {
return resultSet;
}
/**
* INTERNAL:
* The clone of the selection criteria is needed for in-memory conforming
* each object read from the Cursor.
*/
public Expression getSelectionCriteriaClone() {
return selectionCriteriaClone;
}
/**
* INTERNAL:
* Return the handle to the session
*/
public AbstractSession getSession() {
return session;
}
/**
* INTERNAL:
* Returns the session the underlying call was executed on. This root
* session knows the database platform.
*/
public AbstractSession getExecutionSession() {
return executionSession;
}
/**
* INTERNAL:
* Return the Statement.
*/
protected Statement getStatement() {
return statement;
}
/**
* INTERNAL:
* Gets the translation row the query was executed with, used for incremental
* conforming.
*/
protected AbstractRecord getTranslationRow() {
return translationRow;
}
/**
* PUBLIC:
* Return if the stream is closed.
*/
public boolean isClosed() {
return (this.resultSet == null);
}
/**
* INTERNAL:
* builds and registers an object from a row for cursors.
* Behavior is different from the query version in that refreshing is not
* supported.
*/
protected Object buildAndRegisterObject(AbstractRecord row) {
ReadQuery query = this.query;
if (query.isObjectLevelReadQuery()) {
ObjectLevelReadQuery objectQuery = (ObjectLevelReadQuery)query;
if (objectQuery.hasBatchReadAttributes() && objectQuery.getBatchFetchPolicy().isIN()) {
objectQuery.getBatchFetchPolicy().addDataResults(row);
}
if (this.session.isUnitOfWork() && (!query.isReportQuery()) && query.shouldMaintainCache()
&& (objectQuery.shouldConformResultsInUnitOfWork() || objectQuery.getDescriptor().shouldAlwaysConformResultsInUnitOfWork())) {
Object object = objectQuery.conformIndividualResult(
objectQuery.buildObject(row), (UnitOfWorkImpl)this.session, this.translationRow, this.selectionCriteriaClone, this.initiallyConformingIndex);
// Notifies caller to continue until conforming instance found
if (object == null) {
return InvalidObject.instance;
}
return object;
}
}
return query.buildObject(row);
}
/**
* INTERNAL:
* Read the next row from the result set.
*/
protected abstract Object retrieveNextObject() throws DatabaseException;
/**
* INTERNAL:
* Set the fields for the stream.
*/
protected void setFields(Vector<DatabaseField> fields) {
this.fields = fields;
}
/**
* INTERNAL:
* Conforming instances found in memory when building the result.
* These objects are returned first by the cursor, and a fast lookup
* is needed to make sure the same objects appearing in the cursor are
* filtered out.
*/
public void setInitiallyConformingIndex(Map<Object, Object> index) {
this.initiallyConformingIndex = index;
}
/**
* INTERNAL:
* Set the internal object collection
*/
public void setObjectCollection(List<Object> collection) {
objectCollection = collection;
}
/**
* INTERNAL:
* Set the cursor policy.
*/
public void setPolicy(CursorPolicy policy) {
this.policy = policy;
}
/**
* INTERNAL:
* Set the current position of the stream
*/
protected void setPosition(int value) {
position = value;
}
/**
* INTERNAL:
* Set the result set (cursor)
*/
protected void setResultSet(ResultSet result) {
resultSet = result;
}
/**
* INTERNAL:
* The clone of the selection criteria is needed for in-memory conforming
* each object read from the Cursor.
*/
public void setSelectionCriteriaClone(Expression expression) {
this.selectionCriteriaClone = expression;
}
/**
* INTERNAL:
* Set the session handle
*/
public void setSession(AbstractSession databaseSession) {
session = databaseSession;
}
/**
* INTERNAL:
* Sets the session the underlying call was executed on. This root
* session knows the database platform.
*/
protected void setExecutionSession(AbstractSession executionSession) {
this.executionSession = executionSession;
}
/**
* INTERNAL:
* Set the cache size
*/
public void setSize(int size) {
this.size = size;
}
/**
* INTERNAL:
* Sets the translation row this query was executed with. Used for
* incremental conforming.
*/
public void setTranslationRow(AbstractRecord row) {
this.translationRow = row;
}
/**
* PUBLIC:
* Retrieve the size of the open cursor by executing a count on the same query as the cursor.
*
* If this cursor is conforming size() can only be an estimate. cursor size
* plus number of conforming instances found in memory will be returned. The
* union (actual result) may be smaller than this.
*/
public int size() throws DatabaseException {
if (this.size == -1) {
this.size = getCursorSize();
if (this.initiallyConformingIndex != null) {
this.size += this.initiallyConformingIndex.size();
}
}
return this.size;
}
/**
* PUBLIC:
* Remove is not support with cursors.
*/
@Override
public void remove() throws QueryException {
QueryException.invalidOperation("remove");
}
/**
* PUBLIC:
* Release all objects read in so far.
* This should be performed when reading in a large collection of
* objects in order to preserve memory.
*/
public void clear() {
// If using 1-m joining need to release 1-m rows as well.
if ((this.query != null) && this.query.isObjectLevelReadQuery() && ((ObjectLevelReadQuery)this.query).hasJoining()) {
((ObjectLevelReadQuery)this.query).getJoinedAttributeManager().clearDataResults();
}
}
}