blob: e7eb563d2b2c179ba7242b6deb1d750627e0b9fa [file] [log] [blame]
/*
* Copyright (c) 1998, 2020 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 java.sql.*;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.internal.helper.InvalidObject;
import org.eclipse.persistence.internal.queries.JoinedAttributeManager;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.databaseaccess.*;
public class ScrollableCursor extends Cursor implements ListIterator {
protected transient Object nextObject;
protected transient Object previousObject;
/** Store the previous row, for 1-m joining. */
protected AbstractRecord previousRow;
/** Internal flag indicating if the end of the cursor has been reached */
protected boolean atEndOfCursor = false;
/**
* INTERNAL:
* Default constructor.
*/
public ScrollableCursor() {
super();
}
/**
* INTERNAL:
* constructor.
*/
public ScrollableCursor(DatabaseCall call, ScrollableCursorPolicy policy) {
super(call, policy);
setPosition(-1);
}
/**
* PUBLIC:
* Moves the cursor to the given row number in the result set
*/
public boolean absolute(int rows) throws DatabaseException {
clearNextAndPrevious();
try {
boolean suceeded = false;
int initiallyConforming = this.objectCollection.size();
if ((rows >= 0) && (rows <= initiallyConforming)) {
this.resultSet.beforeFirst();
setPosition(rows);
return true;
} else if (rows > initiallyConforming) {
suceeded = this.resultSet.absolute(rows - initiallyConforming);
if (suceeded) {
setPosition(initiallyConforming + rows);
} else {
// Must be afterLast.
setPosition(size() + 1);
}
return suceeded;
} else {// (rows < 0)
// Need to know how big the result set is...
return absolute(size() + rows);
}
} catch (SQLException exception) {
DatabaseException commException = getAccessor().processExceptionForCommError(this.session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, getAccessor(), this.session, false);
}
}
/**
* PUBLIC:
* Add is not support for scrollable cursors.
*/
@Override
public void add(Object object) throws QueryException {
QueryException.invalidOperation("add");
}
/**
* PUBLIC:
* Moves the cursor to the end of the result set, just after the last row.
*/
public void afterLast() throws DatabaseException {
clearNextAndPrevious();
try {
this.resultSet.afterLast();
setPosition(size() + 1);
} catch (SQLException exception) {
DatabaseException commException = getAccessor().processExceptionForCommError(this.session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, getAccessor(), this.session, false);
}
}
/**
* PUBLIC:
* Moves the cursor to the front of the result set, just before the first row
*/
public void beforeFirst() throws DatabaseException {
clearNextAndPrevious();
try {
this.resultSet.beforeFirst();
setPosition(0);
} catch (SQLException exception) {
DatabaseException commException = getAccessor().processExceptionForCommError(this.session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, getAccessor(), this.session, false);
}
}
/**
* INTERNAL:
* Clear the cached next and previous object and row values.
* This must be called whenever the cursor is re-positioned.
*/
protected void clearNextAndPrevious() {
this.nextObject = null;
this.previousObject = null;
this.nextRow = null;
this.previousRow = null;
this.atEndOfCursor = false;
}
/**
* INTERNAL:
* Clear only the cached next and previous object values.
* Called by previous() and next() to maintain the cached next
* and previous row values.
*/
protected void clearNextAndPreviousObject() {
this.nextObject = null;
this.previousObject = null;
}
/**
* PUBLIC:
* Retrieves the current row index number
*/
public int currentIndex() throws DatabaseException {
return getPosition();
}
/**
* PUBLIC:
* Moves the cursor to the first row in the result set
*/
public boolean first() throws DatabaseException {
clearNextAndPrevious();
try {
if (this.objectCollection.size() > 0) {
setPosition(1);
this.resultSet.beforeFirst();
return true;
} else {
return this.resultSet.first();
}
} catch (SQLException exception) {
DatabaseException commException = getAccessor().processExceptionForCommError(this.session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, getAccessor(), this.session, false);
}
}
/**
* INTERNAL:
* Retrieve the size of the open cursor by executing a count on the same query as the cursor.
*/
@Override
protected int getCursorSize() throws DatabaseException {
if (getKnownCursorSize() != -1) {
return getKnownCursorSize();
}
int currentPos = 0;
// If afterLast getRow() will return 0!
boolean wasAfterLast = false;
try {
wasAfterLast = this.resultSet.isAfterLast();
currentPos = this.resultSet.getRow();
this.resultSet.last();
} catch (SQLException exception) {
DatabaseException commException = getAccessor().processExceptionForCommError(this.session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, getAccessor(), this.session, false);
}
int size = 0;
try {
size = this.resultSet.getRow();
if (wasAfterLast) {// Move the cursor back to where we were before calling last()
this.resultSet.afterLast();
} else if (currentPos == 0) {
this.resultSet.beforeFirst();
} else {
this.resultSet.absolute(currentPos);
}
} catch (SQLException exception) {
DatabaseException commException = getAccessor().processExceptionForCommError(this.session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, getAccessor(), this.session, false);
}
return size;
}
protected int getKnownCursorSize() {
if (size == -1) {
return size;
} else {
return size - this.objectCollection.size();
}
}
protected Object getNextObject() {
return nextObject;
}
/**
* PUBLIC:
* Retrieves the current cursor position (current row). The first row is number 1, the second number 2, and so on.
* Unlike java.sql.ResultSet.getRow(), 0 is not returned if afterLast.
* Instead size() + 1 is returned.
* @return the current row number; 0 if there is no current row
* @exception DatabaseException if a database access error occurs
*/
@Override
public int getPosition() throws DatabaseException {
try {
if (this.position == -1) {
this.position = this.resultSet.getRow();
if (this.position == 0) {
// This could mean either beforeFirst or afterLast!
if (isAfterLast()) {
this.position = size() + 1;
}
} else {
this.position += this.objectCollection.size();
}
}
return this.position;
} catch (SQLException exception) {
DatabaseException commException = getAccessor().processExceptionForCommError(this.session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, getAccessor(), this.session, false);
}
}
protected Object getPreviousObject() {
return previousObject;
}
/**
* PUBLIC:
* Indicates whether the cursor can move to the the next row
*/
@Override
public boolean hasMoreElements() throws DatabaseException {
return hasNext();
}
/**
* PUBLIC:
* Indicates whether the cursor can move to the the next row
*/
@Override
public boolean hasNext() throws DatabaseException {
if (isClosed()) {
return false;
}
loadNext();
return (this.nextObject != null);
}
/**
* PUBLIC:
* Indicates whether the cursor can move to the the next row
*/
public boolean hasNextElement() throws DatabaseException {
return hasNext();
}
/**
* PUBLIC:
* Indicates whether the cursor can move to the the previous row
*/
@Override
public boolean hasPrevious() throws DatabaseException {
if (isClosed()) {
return false;
}
loadPrevious();
return (getPreviousObject() != null);
}
/**
* PUBLIC:
* Indicates whether the cursor is after the last row in the result set.
*/
public boolean isAfterLast() throws DatabaseException {
try {
if (this.nextObject != null) {
return false;
}
if ((this.objectCollection.size() > 0) && (getPosition() <= this.objectCollection.size())) {
return false;
}
return this.resultSet.isAfterLast();
} catch (SQLException exception) {
DatabaseException commException = getAccessor().processExceptionForCommError(this.session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, getAccessor(), this.session, false);
}
}
/**
* PUBLIC:
* Indicates whether the cursor is before the first row in the result set.
*/
public boolean isBeforeFirst() throws DatabaseException {
if (getPreviousObject() != null) {
return false;
}
return getPosition() == 0;
}
/**
* PUBLIC:
* Indicates whether the cursor is on the first row of the result set.
*/
public boolean isFirst() throws DatabaseException {
if (getPreviousObject() != null) {
return false;
}
try {
if (this.objectCollection.size() > 0) {
return getPosition() == 1;
}
return this.resultSet.isFirst();
} catch (SQLException exception) {
DatabaseException commException = getAccessor().processExceptionForCommError(this.session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, getAccessor(), this.session, false);
}
}
/**
* PUBLIC:
* Indicates whether the cursor is on the last row of the result set.
*/
public boolean isLast() throws DatabaseException {
if (this.nextObject != null) {
return false;
}
try {
return this.resultSet.isLast();
} catch (UnsupportedOperationException ex) {
// isLast() is not supported by some drivers (specifically JConnect5.0)
// Do this the hard way instead.
try {
return this.resultSet.getRow() == getCursorSize();
} catch (SQLException ex2) {
DatabaseException commException = getAccessor().processExceptionForCommError(this.session, ex2, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(ex2, getAccessor(), this.session, false);
}
} catch (SQLException exception) {
DatabaseException commException = getAccessor().processExceptionForCommError(this.session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, getAccessor(), this.session, false);
}
}
/**
* PUBLIC:
* Moves the cursor to the last row in the result set
*/
public boolean last() throws DatabaseException {
clearNextAndPrevious();
try {
boolean isLast = this.resultSet.last();
if (!isLast) {
// cursor must be empty.
if (this.objectCollection.size() > 0) {
setPosition(this.objectCollection.size());
isLast = true;
}
} else {
setSize(this.objectCollection.size() + this.resultSet.getRow());
setPosition(size);
}
return isLast;
} catch (SQLException exception) {
DatabaseException commException = getAccessor().processExceptionForCommError(this.session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, getAccessor(), this.session, false);
}
}
/**
* Load the next object
*/
protected void loadNext() {
if (this.nextObject == null) {
Object next = retrieveNextObject();
this.nextObject = next;
}
}
/**
* Load the previous object. This is used solely for scrollable cursor support
*/
protected void loadPrevious() {
// CR#4139
if (this.previousObject == null) {
this.previousObject = retrievePreviousObject();
}
}
/**
* PUBLIC:
* This method differs slightly from conventional read() operation on a Java stream. This
* method return the next object in the collection rather than specifying the number of
* bytes to be read in.
*
* Return the next object from the collection, if beyond the read limit read from the cursor
* @return - next object in stream
* @throws DatabaseException if read pass end of stream
*/
@Override
public Object next() throws DatabaseException, QueryException {
loadNext();
if (this.nextObject == null) {
throw QueryException.readBeyondStream(this.query);
}
Object next = this.nextObject;
clearNextAndPreviousObject();
return next;
}
/**
* PUBLIC:
* This method differs slightly from conventional read() operation on a Java stream. This
* method returns the next number of objects in the collection in a vector.
*
* Return the next specified number of objects from the collection, if beyond the read limit read from the cursor
* @param number - number of objects to be returned
* @return - vector containing next number of objects
* @throws DatabaseException if read pass end of stream
*/
public List<Object> next(int number) throws DatabaseException {
List<Object> result = new ArrayList(number);
for (int index = 0; index < number; index++) {
result.add(next());
}
return result;
}
/**
* PUBLIC:
* Return the next object from the collection, if beyond the read limit read from the cursor.
* @return next object in stream
*/
@Override
public Object nextElement() throws DatabaseException, QueryException {
return next();
}
/**
* PUBLIC:
* Retrieves the next row index (against the current row)
*/
@Override
public int nextIndex() throws DatabaseException {
return currentIndex() + 1;
}
/**
* PUBLIC:
* Return the previous object from the collection.
*
* @return - previous object in stream
* @throws DatabaseException if read pass first of stream
*/
@Override
public Object previous() throws DatabaseException, QueryException {
loadPrevious();
if (this.previousObject == null) {
throw QueryException.readBeyondStream(this.query);
}
Object previous = this.previousObject;
clearNextAndPreviousObject();
return previous;
}
/**
* PUBLIC:
* Retrieves the previous row index (against the current row)
*/
@Override
public int previousIndex() throws DatabaseException {
return currentIndex() - 1;
}
/**
* PUBLIC:
* Moves the cursor a relative number of rows, either positive or negative.
* Attempting to move beyond the first/last row in the result set positions the cursor before/after the
* the first/last row
*/
public boolean relative(int rows) throws DatabaseException {
clearNextAndPrevious();
try {
boolean suceeded = false;
int oldPosition = getPosition();
int newPosition = getPosition() + rows;
int initiallyConforming = this.objectCollection.size();
if (newPosition <= initiallyConforming) {
setPosition(newPosition);
if (oldPosition > initiallyConforming) {
this.resultSet.beforeFirst();
}
if (newPosition < 0) {
setPosition(0);
}
suceeded = (newPosition > 0);
} else {
suceeded = this.resultSet.relative(rows);
if (!suceeded) {
// Must be afterLast now.
setPosition(size() + 1);
} else {
setPosition(newPosition);
}
}
return suceeded;
} catch (SQLException exception) {
DatabaseException commException = getAccessor().processExceptionForCommError(this.session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, getAccessor(), this.session, false);
}
}
/**
* INTERNAL:
* Read the next row from the result set.
*/
@Override
protected Object retrieveNextObject() throws DatabaseException {
while (true) {
int currentPosition = getPosition();
if (currentPosition < this.objectCollection.size()) {
this.position = currentPosition + 1;
return this.objectCollection.get(currentPosition);
}
if (isClosed()) {
return null;
}
AbstractRecord row = null;
// if the end of the cursor has been reached, do not retrieve more rows
if (!this.atEndOfCursor) {
if (this.nextRow == null) {
row = getAccessor().cursorRetrieveNextRow(this.fields, this.resultSet, this.executionSession);
} else {
row = this.nextRow;
this.nextRow = null;
}
}
this.position = currentPosition + 1; // bug 309142
if (row == null) {
this.atEndOfCursor = true;
return null;
}
// If using 1-m joining need to fetch 1-m rows as well.
if (this.query.isObjectLevelReadQuery() && ((ObjectLevelReadQuery)this.query).hasJoining()) {
JoinedAttributeManager joinManager = ((ObjectLevelReadQuery)this.query).getJoinedAttributeManager();
if (joinManager.isToManyJoin()) {
this.nextRow = joinManager.processDataResults(row, this, true);
// if the join manager returns a null next row, we are at the end of the cursor
if (this.nextRow == null) {
this.atEndOfCursor = true;
}
}
}
Object object = buildAndRegisterObject(row);
if (object == InvalidObject.instance) {
continue;
}
return object;
}
}
/**
* INTERNAL:
* CR#4139
* Read the previous row from the result set. It is used solely
* for scrollable cursor support.
*/
protected Object retrievePreviousObject() throws DatabaseException {
while (true) {
int currentPosition = getPosition();
if (currentPosition <= (this.objectCollection.size() + 1)) {
// If at first of cursor, move cursor to beforeFirst.
if ((currentPosition == (this.objectCollection.size() + 1)) && (!isClosed())) {
getAccessor().cursorRetrievePreviousRow(this.fields, this.resultSet, this.executionSession);
}
if (currentPosition <= 1) {
// Cursor can not move back further than beforeFirst.
this.position = 0;
return null;
} else {
this.position = currentPosition - 1;
return this.objectCollection.get(this.position - 1);
}
}
if (isClosed()) {
return null;
}
AbstractRecord row = null;
if (this.previousRow == null) {
row = getAccessor().cursorRetrievePreviousRow(this.fields, this.resultSet, this.executionSession);
} else {
row = this.previousRow;
this.previousRow = null;
}
this.position = currentPosition - 1;
if (row == null) {
return null;
}
// If using 1-m joining need to fetch 1-m rows as well.
if (this.query.isObjectLevelReadQuery() && ((ObjectLevelReadQuery)this.query).hasJoining()) {
JoinedAttributeManager joinManager = ((ObjectLevelReadQuery)this.query).getJoinedAttributeManager();
if (joinManager.isToManyJoin()) {
this.previousRow = joinManager.processDataResults(row, this, false);
}
}
Object object = buildAndRegisterObject(row);
// keep going until find one that conforms.
if (object == InvalidObject.instance) {
continue;
}
return object;
}
}
/**
* PUBLIC:
* Set is not supported for scrollable cursors.
*/
@Override
public void set(Object object) throws QueryException {
QueryException.invalidOperation("set");
}
protected void setNextObject(Object nextObject) {
this.nextObject = nextObject;
}
protected void setPreviousObject(Object previousObject) {
this.previousObject = previousObject;
}
}