org.firebirdsql.gds.ng.wire.version10.V10Statement Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jaybird-jdk17 Show documentation
Show all versions of jaybird-jdk17 Show documentation
JDBC Driver for the Firebird RDBMS
/*
* Firebird Open Source JavaEE Connector - JDBC Driver
*
* Distributable under LGPL license.
* You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* LGPL License for more details.
*
* This file was created by members of the firebird development team.
* All individual contributions remain the Copyright (C) of those
* individuals. Contributors to this file are either listed here or
* can be obtained from a source control history command.
*
* All rights reserved.
*/
package org.firebirdsql.gds.ng.wire.version10;
import org.firebirdsql.gds.ISCConstants;
import org.firebirdsql.gds.impl.wire.WireProtocolConstants;
import org.firebirdsql.gds.impl.wire.XdrInputStream;
import org.firebirdsql.gds.impl.wire.XdrOutputStream;
import org.firebirdsql.gds.ng.FbExceptionBuilder;
import org.firebirdsql.gds.ng.StatementState;
import org.firebirdsql.gds.ng.StatementType;
import org.firebirdsql.gds.ng.WarningMessageCallback;
import org.firebirdsql.gds.ng.fields.*;
import org.firebirdsql.gds.ng.wire.*;
import org.firebirdsql.logging.Logger;
import org.firebirdsql.logging.LoggerFactory;
import java.io.IOException;
import java.sql.SQLException;
import java.sql.SQLNonTransientException;
import java.sql.SQLWarning;
import static org.firebirdsql.gds.ng.TransactionHelper.checkTransactionActive;
/**
* @author Mark Rotteveel
* @since 3.0
*/
public class V10Statement extends AbstractFbWireStatement implements FbWireStatement {
// TODO Handle error state in a consistent way (eg when does an exception lead to the error state, or when is it 'just' valid feedback)
// TODO Fix state transitions
private static final int NULL_INDICATOR_NOT_NULL = 0;
private static final int NULL_INDICATOR_NULL = -1;
private static final Logger log = LoggerFactory.getLogger(V10Statement.class);
/**
* Creates a new instance of V10Statement for the specified database.
*
* @param database
* FbWireDatabase implementation
*/
public V10Statement(FbWireDatabase database) {
super(database);
}
@Override
public byte[] getSqlInfo(final byte[] requestItems, final int bufferLength) throws SQLException {
try {
synchronized (getSynchronizationObject()) {
checkStatementValid();
try {
sendInfoSql(requestItems, bufferLength);
getXdrOut().flush();
} catch (IOException ex) {
switchState(StatementState.ERROR);
throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException();
}
try {
return processInfoSqlResponse(getDatabase().readGenericResponse(getStatementWarningCallback()));
} catch (IOException ex) {
switchState(StatementState.ERROR);
throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(ex).toSQLException();
}
}
} catch (SQLException e) {
exceptionListenerDispatcher.errorOccurred(e);
throw e;
}
}
/**
* Sends the info sql request to the database
*
* @param requestItems
* Info request items
* @param bufferLength
* Requested response buffer length
* @throws IOException
* @throws SQLException
*/
protected void sendInfoSql(final byte[] requestItems, final int bufferLength) throws IOException, SQLException {
final XdrOutputStream xdrOut = getXdrOut();
xdrOut.writeInt(WireProtocolConstants.op_info_sql);
xdrOut.writeInt(getHandle());
xdrOut.writeInt(0); // incarnation
xdrOut.writeBuffer(requestItems);
xdrOut.writeInt(bufferLength);
}
/**
* Processes the info sql response.
*
* @param response
* GenericResponse
* @return info sql response buffer
*/
protected byte[] processInfoSqlResponse(GenericResponse response) {
return response.getData();
}
@Override
protected void free(final int option) throws SQLException {
synchronized (getSynchronizationObject()) {
try {
doFreePacket(option);
getXdrOut().flush();
} catch (IOException ex) {
switchState(StatementState.ERROR);
throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException();
}
try {
processFreeResponse(getDatabase().readResponse(getStatementWarningCallback()));
} catch (IOException ex) {
switchState(StatementState.ERROR);
throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(ex).toSQLException();
}
}
}
/**
* Handles sending the free statement packet and associated state changes on this statement
*
* @param option
* Free statement option
* @throws SQLException
*/
protected void doFreePacket(int option) throws SQLException, IOException {
sendFree(option);
// Reset statement information
reset(option == ISCConstants.DSQL_drop);
}
/**
* Sends the free statement to the database
*
* @param option
* Free statement option
* @throws IOException
* @throws SQLException
*/
protected void sendFree(int option) throws IOException, SQLException {
final XdrOutputStream xdrOut = getXdrOut();
xdrOut.writeInt(WireProtocolConstants.op_free_statement);
xdrOut.writeInt(getHandle());
xdrOut.writeInt(option);
}
/**
* Processes the response to the free statement.
*
* @param response
* Response object
*/
protected void processFreeResponse(@SuppressWarnings("UnusedParameters") Response response) {
// No processing needed
}
@Override
public void prepare(final String statementText) throws SQLException {
try {
synchronized (getSynchronizationObject()) {
checkTransactionActive(getTransaction());
final StatementState currentState = getState();
if (!isPrepareAllowed(currentState)) {
throw new SQLNonTransientException(String.format("Current statement state (%s) does not allow call to prepare", currentState));
}
resetAll();
final FbWireDatabase db = getDatabase();
if (currentState == StatementState.NEW) {
try {
sendAllocate();
getXdrOut().flush();
} catch (IOException ex) {
switchState(StatementState.ERROR);
throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException();
}
try {
processAllocateResponse(db.readGenericResponse(getStatementWarningCallback()));
} catch (IOException ex) {
switchState(StatementState.ERROR);
throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(ex).toSQLException();
}
} else {
checkStatementValid();
}
try {
sendPrepare(statementText);
getXdrOut().flush();
} catch (IOException ex) {
switchState(StatementState.ERROR);
throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException();
}
try {
processPrepareResponse(db.readGenericResponse(getStatementWarningCallback()));
} catch (IOException ex) {
switchState(StatementState.ERROR);
throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(ex).toSQLException();
}
}
} catch (SQLException e) {
exceptionListenerDispatcher.errorOccurred(e);
throw e;
}
}
/**
* Sends the statement prepare to the connection.
*
* @param statementText
* Statement
* @throws SQLException
* @throws IOException
*/
protected void sendPrepare(final String statementText) throws SQLException, IOException {
final XdrOutputStream xdrOut = getXdrOut();
xdrOut.writeInt(WireProtocolConstants.op_prepare_statement);
xdrOut.writeInt(getTransaction().getHandle());
xdrOut.writeInt(getHandle());
xdrOut.writeInt(getDatabase().getConnectionDialect());
xdrOut.writeString(statementText, getDatabase().getEncoding());
xdrOut.writeBuffer(getStatementInfoRequestItems());
xdrOut.writeInt(getDefaultSqlInfoSize());
}
/**
* Processes the prepare response from the server.
*
* @param genericResponse
* GenericResponse
* @throws SQLException
*/
protected void processPrepareResponse(final GenericResponse genericResponse) throws SQLException {
parseStatementInfo(genericResponse.getData());
switchState(StatementState.PREPARED);
}
public void setCursorName(String cursorName) throws SQLException {
try {
synchronized (getSynchronizationObject()) {
checkStatementValid();
// TODO Check other statement states?
try {
final XdrOutputStream xdrOut = getXdrOut();
xdrOut.writeInt(WireProtocolConstants.op_set_cursor);
xdrOut.writeInt(getHandle());
// Null termination is needed due to a quirk of the protocol
xdrOut.writeString(cursorName + '\0', getDatabase().getEncoding());
xdrOut.writeInt(0); // Cursor type
xdrOut.flush();
} catch (IOException ex) {
switchState(StatementState.ERROR);
throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException();
}
try {
// TODO Do we need to do anything else with this response?
getDatabase().readGenericResponse(getStatementWarningCallback());
} catch (IOException ex) {
switchState(StatementState.ERROR);
throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(ex).toSQLException();
}
}
} catch (SQLException e) {
exceptionListenerDispatcher.errorOccurred(e);
throw e;
}
}
@Override
public void execute(final RowValue parameters) throws SQLException {
final StatementState initialState = getState();
try {
synchronized (getSynchronizationObject()) {
checkStatementValid();
checkTransactionActive(getTransaction());
validateParameters(parameters);
reset(false);
switchState(StatementState.EXECUTING);
final StatementType statementType = getType();
final boolean hasSingletonResult = hasSingletonResult();
int expectedResponseCount = 0;
try {
if (hasSingletonResult) {
expectedResponseCount++;
}
sendExecute(hasSingletonResult
? WireProtocolConstants.op_execute2
: WireProtocolConstants.op_execute,
parameters);
expectedResponseCount++;
getXdrOut().flush();
} catch (IOException ex) {
switchState(StatementState.ERROR);
throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException();
}
final WarningMessageCallback statementWarningCallback = getStatementWarningCallback();
try {
final FbWireDatabase db = getDatabase();
try {
expectedResponseCount--;
Response response = db.readResponse(statementWarningCallback);
if (hasSingletonResult) {
/* A type with a singleton result (ie an execute procedure with return fields), doesn't actually
* have a result set that will be fetched, instead we have a singleton result if we have fields
*/
statementListenerDispatcher.statementExecuted(this, false, true);
if (response instanceof SqlResponse) {
processExecuteSingletonResponse((SqlResponse) response);
expectedResponseCount--;
response = db.readResponse(statementWarningCallback);
} else {
// We didn't get an op_sql_response first, something is iffy, maybe cancellation or very low level problem?
// We don't expect any more responses after this
expectedResponseCount = 0;
SQLWarning sqlWarning = new SQLWarning("Expected an SqlResponse, instead received a " + response.getClass().getName());
log.warn(sqlWarning.getMessage(), sqlWarning);
statementWarningCallback.processWarning(sqlWarning);
}
setAllRowsFetched(true);
} else {
// A normal execute is never a singleton result (even if it only produces a single result)
statementListenerDispatcher.statementExecuted(this, hasFields(), false);
}
// This should always be a GenericResponse, otherwise something went fundamentally wrong anyway
processExecuteResponse((GenericResponse) response);
} finally {
db.consumePackets(expectedResponseCount, getStatementWarningCallback());
}
if (getState() != StatementState.ERROR) {
switchState(statementType.isTypeWithCursor() ? StatementState.CURSOR_OPEN : StatementState.PREPARED);
}
} catch (IOException ex) {
switchState(StatementState.ERROR);
throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(ex).toSQLException();
}
}
} catch (SQLException e) {
if (getState() != StatementState.ERROR) {
switchState(initialState);
}
exceptionListenerDispatcher.errorOccurred(e);
throw e;
}
}
/**
* Sends the execute (for op_execute
or op_execute2
) to the database.
*
* @param operation
* Operation (op_execute
or op_execute2
)
* @param parameters
* Parameters
* @throws IOException
* @throws SQLException
*/
protected void sendExecute(final int operation, final RowValue parameters) throws IOException, SQLException {
assert operation == WireProtocolConstants.op_execute || operation == WireProtocolConstants.op_execute2 : "Needs to be called with operation op_execute or op_execute2";
final XdrOutputStream xdrOut = getXdrOut();
xdrOut.writeInt(operation);
xdrOut.writeInt(getHandle());
xdrOut.writeInt(getTransaction().getHandle());
if (parameters != null && parameters.getCount() > 0) {
final RowDescriptor parameterDescriptor = getParameterDescriptor();
xdrOut.writeBuffer(calculateBlr(parameterDescriptor, parameters));
xdrOut.writeInt(0); // message number = in_message_type
xdrOut.writeInt(1); // Number of messages
writeSqlData(parameterDescriptor, parameters);
} else {
xdrOut.writeBuffer(null);
xdrOut.writeInt(0); // message number = in_message_type
xdrOut.writeInt(0); // Number of messages
}
if (operation == WireProtocolConstants.op_execute2) {
final RowDescriptor fieldDescriptor = getFieldDescriptor();
xdrOut.writeBuffer(fieldDescriptor != null && fieldDescriptor.getCount() > 0 ? calculateBlr(fieldDescriptor) : null);
xdrOut.writeInt(0); // out_message_number = out_message_type
}
}
/**
* Process the execute response for statements with a singleton response (op_execute2
; stored procedures).
*
* @param sqlResponse
* SQL response object
* @throws SQLException
* @throws IOException
*/
protected void processExecuteSingletonResponse(SqlResponse sqlResponse) throws SQLException, IOException {
if (sqlResponse.getCount() > 0) {
queueRowData(readSqlData());
}
}
/**
* Process the execute response.
*
* @param genericResponse
* Generic response object
*/
protected void processExecuteResponse(GenericResponse genericResponse) {
// Nothing to do here
}
@Override
public void fetchRows(int fetchSize) throws SQLException {
try {
synchronized (getSynchronizationObject()) {
checkStatementValid();
if (!getState().isCursorOpen()) {
throw new FbExceptionBuilder().exception(ISCConstants.isc_cursor_not_open).toSQLException();
}
if (isAllRowsFetched()) return;
try {
sendFetch(fetchSize);
getXdrOut().flush();
} catch (IOException ex) {
switchState(StatementState.ERROR);
throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException();
}
try {
processFetchResponse();
} catch (IOException ex) {
switchState(StatementState.ERROR);
throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(ex).toSQLException();
}
}
} catch (SQLException e) {
exceptionListenerDispatcher.errorOccurred(e);
throw e;
}
}
/**
* Process the fetch response by reading the returned rows and queuing them.
*
* @throws IOException
* @throws SQLException
*/
protected void processFetchResponse() throws IOException, SQLException {
Response response;
while (!isAllRowsFetched() && (response = getDatabase().readResponse(getStatementWarningCallback())) instanceof FetchResponse) {
final FetchResponse fetchResponse = (FetchResponse) response;
if (fetchResponse.getCount() > 0 && fetchResponse.getStatus() == ISCConstants.FETCH_OK) {
queueRowData(readSqlData());
} else if (fetchResponse.getStatus() == ISCConstants.FETCH_NO_MORE_ROWS) {
setAllRowsFetched(true);
// Note: we are not explicitly 'closing' the cursor here
} else {
// TODO Log, raise exception, or simply 'not possible'?
break;
}
}
// TODO Handle other response type?
}
/**
* Sends the fetch request to the database.
*
* @param fetchSize Number of rows to fetch.
* @throws SQLException
* @throws IOException
*/
protected void sendFetch(int fetchSize) throws SQLException, IOException {
final XdrOutputStream xdrOut = getXdrOut();
xdrOut.writeInt(WireProtocolConstants.op_fetch);
xdrOut.writeInt(getHandle());
xdrOut.writeBuffer(calculateBlr(getFieldDescriptor()));
xdrOut.writeInt(0); // out_message_number = out_message_type
xdrOut.writeInt(fetchSize); // fetch size
}
/**
* Reads a single row from the database.
*
* @return Row as a list of {@link FieldValue} instances
* @throws SQLException
* @throws IOException
*/
protected RowValue readSqlData() throws SQLException, IOException {
final RowDescriptor rowDescriptor = getFieldDescriptor();
final RowValue rowValue = rowDescriptor.createDefaultFieldValues();
final BlrCalculator blrCalculator = getDatabase().getBlrCalculator();
final XdrInputStream xdrIn = getXdrIn();
for (int idx = 0; idx < rowDescriptor.getCount(); idx++) {
final FieldDescriptor fieldDescriptor = rowDescriptor.getFieldDescriptor(idx);
final FieldValue fieldValue = rowValue.getFieldValue(idx);
final int len = blrCalculator.calculateIoLength(fieldDescriptor);
byte[] buffer = readColumnData(xdrIn, len);
if (xdrIn.readInt() == NULL_INDICATOR_NULL)
buffer = null;
fieldValue.setFieldData(buffer);
}
return rowValue;
}
protected byte[] readColumnData(XdrInputStream xdrIn, int len) throws IOException {
byte[] buffer;
if (len == 0) {
// Length specified in response
len = xdrIn.readInt();
buffer = new byte[len];
xdrIn.readFully(buffer, 0, len);
xdrIn.skipPadding(len);
} else if (len < 0) {
// Buffer is not padded
buffer = new byte[-len];
xdrIn.readFully(buffer, 0, -len);
} else {
// len is incremented in calculateIoLength to avoid value 0 so it must be decremented
len--;
buffer = new byte[len];
xdrIn.readFully(buffer, 0, len);
xdrIn.skipPadding(len);
}
return buffer;
}
/**
* Write a set of SQL data from a list of {@link FieldValue} instances.
*
* @param rowDescriptor
* The row descriptor
* @param fieldValues
* The List containing the SQL data to be written
* @throws IOException
* if an error occurs while writing to the underlying output stream
*/
protected void writeSqlData(final RowDescriptor rowDescriptor, final RowValue fieldValues) throws IOException, SQLException {
final XdrOutputStream xdrOut = getXdrOut();
final BlrCalculator blrCalculator = getDatabase().getBlrCalculator();
for (int idx = 0; idx < fieldValues.getCount(); idx++) {
final FieldValue fieldValue = fieldValues.getFieldValue(idx);
final FieldDescriptor fieldDescriptor = rowDescriptor.getFieldDescriptor(idx);
final int len = blrCalculator.calculateIoLength(fieldDescriptor, fieldValue);
final byte[] buffer = fieldValue.getFieldData();
final int fieldType = fieldDescriptor.getType();
writeColumnData(xdrOut, len, buffer, fieldType);
// sqlind (null indicator)
xdrOut.writeInt(buffer != null ? NULL_INDICATOR_NOT_NULL : NULL_INDICATOR_NULL);
}
}
protected void writeColumnData(XdrOutputStream xdrOut, int len, byte[] buffer, int fieldType) throws IOException {
final int tempType = fieldType & ~1;
// TODO Correctly pad with 0x00 instead of 0x20 for octets.
if (tempType == ISCConstants.SQL_NULL) {
// Nothing to write for SQL_NULL (except null indicator, which happens at end)
} else if (len == 0) {
if (buffer != null) {
len = buffer.length;
xdrOut.writeInt(len);
xdrOut.write(buffer, 0, len, (4 - len) & 3);
} else {
xdrOut.writeInt(0);
}
} else if (len < 0) {
if (buffer != null) {
xdrOut.write(buffer, 0, -len);
} else {
xdrOut.writeZeroPadding(-len);
}
} else {
// decrement length because it was incremented before
// increment happens in BlrCalculator.calculateIoLength
len--;
if (buffer != null) {
final int buflen = buffer.length;
if (buflen >= len) {
xdrOut.write(buffer, 0, len, (4 - len) & 3);
} else {
xdrOut.write(buffer, 0, buflen, 0);
xdrOut.writeSpacePadding(len - buflen + ((4 - len) & 3));
}
} else {
xdrOut.writeSpacePadding(len + ((4 - len) & 3));
}
}
}
/**
* Sends the allocate request to the server.
*
* @throws SQLException
* @throws IOException
*/
protected void sendAllocate() throws SQLException, IOException {
final XdrOutputStream xdrOut = getXdrOut();
xdrOut.writeInt(WireProtocolConstants.op_allocate_statement);
xdrOut.writeInt(getDatabase().getHandle());
}
/**
* Processes the allocate response from the server.
*
* @param response
* GenericResponse
*/
protected void processAllocateResponse(GenericResponse response) throws SQLException {
synchronized (getSynchronizationObject()) {
setHandle(response.getObjectHandle());
setAllRowsFetched(false);
switchState(StatementState.ALLOCATED);
setType(StatementType.NONE);
}
}
@Override
public int getDefaultSqlInfoSize() {
// TODO Test for an optimal buffer size
return getMaxSqlInfoSize();
}
@Override
public int getMaxSqlInfoSize() {
// TODO Is this the actual max for the v10 protocol, or is it 65535?
return 32767;
}
}