blob: c4f8e21d54064fcc7c86035997621bc6257b349c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.calcite.avatica.jdbc;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Statement;
/**
* All we know about a statement. Encapsulates a {@link ResultSet}.
*/
public class StatementInfo {
private volatile Boolean relativeSupported = null;
final Statement statement; // sometimes a PreparedStatement
private ResultSet resultSet;
private long position = 0;
// True when setResultSet(ResultSet) is called to let us determine the difference between
// a null ResultSet (from an update) from the lack of a ResultSet.
private boolean resultsInitialized = false;
public StatementInfo(Statement statement) {
// May be null when coming from a DatabaseMetaData call
this.statement = statement;
}
// Visible for testing
void setPosition(long position) {
this.position = position;
}
// Visible for testing
long getPosition() {
return this.position;
}
/**
* Set a ResultSet on this object.
*
* @param resultSet The current ResultSet
*/
public void setResultSet(ResultSet resultSet) {
resultsInitialized = true;
this.resultSet = resultSet;
}
/**
* @return The {@link ResultSet} for this Statement, may be null.
*/
public ResultSet getResultSet() {
return this.resultSet;
}
/**
* @return True if {@link #setResultSet(ResultSet)} was ever invoked.
*/
public boolean isResultSetInitialized() {
return resultsInitialized;
}
/**
* @see ResultSet#next()
*/
public boolean next() throws SQLException {
return _next(resultSet);
}
boolean _next(ResultSet results) throws SQLException {
boolean ret = results.next();
position++;
return ret;
}
/**
* Consumes <code>offset - position</code> elements from the {@link ResultSet}.
*
* @param offset The offset to advance to
* @return True if the resultSet was advanced to the current point, false if insufficient rows
* were present to advance to the requested offset.
*/
public boolean advanceResultSetToOffset(ResultSet results, long offset) throws SQLException {
if (offset < 0 || offset < position) {
throw new IllegalArgumentException("Offset should be "
+ " non-negative and not less than the current position. " + offset + ", " + position);
}
if (position >= offset) {
return true;
}
if (null == relativeSupported) {
Boolean moreResults = null;
synchronized (this) {
if (null == relativeSupported) {
try {
moreResults = advanceByRelative(results, offset);
relativeSupported = true;
} catch (SQLFeatureNotSupportedException e) {
relativeSupported = false;
}
}
}
if (null != moreResults) {
// We figured out whether or not relative is supported.
// Make sure we actually do the necessary work.
if (!relativeSupported) {
// We avoided calling advanceByNext in the synchronized block earlier.
moreResults = advanceByNext(results, offset);
}
return moreResults;
}
// Another thread updated the RELATIVE_SUPPORTED before we did, fall through.
}
if (relativeSupported) {
return advanceByRelative(results, offset);
} else {
return advanceByNext(results, offset);
}
}
private boolean advanceByRelative(ResultSet results, long offset) throws SQLException {
long diff = offset - position;
while (diff > Integer.MAX_VALUE) {
if (!results.relative(Integer.MAX_VALUE)) {
// Avoid updating position until relative succeeds.
position += Integer.MAX_VALUE;
return false;
}
// Avoid updating position until relative succeeds.
position += Integer.MAX_VALUE;
diff -= Integer.MAX_VALUE;
}
boolean ret = results.relative((int) diff);
// Make sure we only update the position after successfully calling relative(int).
position += diff;
return ret;
}
private boolean advanceByNext(ResultSet results, long offset) throws SQLException {
while (position < offset) {
// Advance while maintaining `position`
if (!_next(results)) {
return false;
}
}
return true;
}
}
// End StatementInfo.java