com.pivotal.gemfirexd.internal.client.am.CallableLocatorProcedures Maven / Gradle / Ivy
Show all versions of snappydata-store-client Show documentation
/*
Derby - Class com.pivotal.gemfirexd.internal.client.am.CallableLocatorProcedures
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.
*/
/*
* Changes for GemFireXD distributed data platform (some marked by "GemStone changes")
*
* Portions Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
*
* Licensed 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. See accompanying
* LICENSE file.
*/
package com.pivotal.gemfirexd.internal.client.am;
import com.pivotal.gemfirexd.internal.shared.common.error.ExceptionUtil;
import com.pivotal.gemfirexd.internal.shared.common.reference.SQLState;
// GemStone changes BEGIN
import com.pivotal.gemfirexd.internal.shared.common.sanity.SanityManager;
// GemStone changes END
/**
* Contains the necessary methods to call the stored procedure that
* operate on LOBs identified by locators. An instance of this class
* will be initialized with a Connection
parameter and
* all calls will be made on that connection.
*
* The class makes sure that each procedure call is only prepared once
* per instance. Hence, it will keep references to
* CallableStatement
objects for procedures that have
* been called through this instance. This makes it possible to
* prepare each procedure call only once per Connection
.
*
* Since LOBs can not be parameters to stored procedures, the
* framework should make sure that calls involving a byte[] or String
* that does not fit in a VARCHAR (FOR BIT DATA), are split into
* several calls each operating on a fragment of the LOB.
*
* @see Connection#locatorProcedureCall for an example of how to use
* this class.
*/
public /* GemStone addition */
class CallableLocatorProcedures
{
//caches the information from a Stored Procedure
//call as to whether locator support is available in
//the server or not.
boolean isLocatorSupportAvailable = true;
// One member variable for each stored procedure that can be called.
// Used to be able to only prepare each procedure call once per connection.
private CallableStatement blobCreateLocatorCall;
private CallableStatement blobReleaseLocatorCall;
private CallableStatement blobGetPositionFromLocatorCall;
private CallableStatement blobGetPositionFromBytesCall;
private CallableStatement blobGetLengthCall;
private CallableStatement blobGetBytesCall;
private CallableStatement blobSetBytesCall;
private CallableStatement blobTruncateCall;
private CallableStatement clobCreateLocatorCall;
private CallableStatement clobReleaseLocatorCall;
private CallableStatement clobGetPositionFromStringCall;
private CallableStatement clobGetPositionFromLocatorCall;
private CallableStatement clobGetLengthCall;
private CallableStatement clobGetSubStringCall;
private CallableStatement clobSetStringCall;
private CallableStatement clobTruncateCall;
/**
* The connection to be used when calling the stored procedures.
*/
private final Connection connection;
/**
* Max size of byte[] and String parameters to procedures
*/
private static final int VARCHAR_MAXWIDTH = 32672;
//Constant representing an invalid locator value
private static final int INVALID_LOCATOR = -1;
// GemStone changes BEGIN
/**
* Stores the last server that failed that lead to failover
* and causes the LOB methods to fail on the new server.
*/
public volatile String failedServer;
// GemStone changes END
/**
* Create an instance to be used for calling locator-based stored
* procedures.
*
* @param conn the connection to be used to prepare calls.
*/
CallableLocatorProcedures(Connection conn)
{
this.connection = conn;
}
/**
* Allocates an empty BLOB on server and returns its locator. Any
* subsequent operations on this BLOB value will be stored in temporary
* space on the server.
*
* @throws com.pivotal.gemfirexd.internal.client.am.SqlException
* @return locator that identifies the created BLOB.
*/
int blobCreateLocator() throws SqlException
{
//The information on whether the locator support
//is available is cached in the boolean
//isLocatorSupportAvailable. If this is false
//we can return -1
if (!isLocatorSupportAvailable) {
return INVALID_LOCATOR;
}
try {
if (blobCreateLocatorCall == null ||
!blobCreateLocatorCall.openOnClient_) {
blobCreateLocatorCall = connection.prepareCallX
("? = CALL SYSIBM.BLOBCREATELOCATOR()",
java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY,
connection.holdability());
blobCreateLocatorCall
.registerOutParameterX(1, java.sql.Types.INTEGER);
// Make sure this statement does not commit user transaction
blobCreateLocatorCall.isAutoCommittableStatement_ = false;
}
blobCreateLocatorCall.executeX();
}
catch(SqlException sqle) {
//An exception has occurred while calling the stored procedure
//used to create the locator value.
//We verify to see if this SqlException has a SQLState of
//42Y03(SQLState.LANG_NO_SUCH_METHOD_ALIAS)
//(corresponding to the stored procedure not being found)
//This means that locator support is not available.
//This information is cached so that each time to determine
//if locator support is available we do not have to make a
//round trip to the server.
if (sqle.getSQLState().compareTo
(ExceptionUtil.getSQLStateFromIdentifier
(SQLState.LANG_NO_SUCH_METHOD_ALIAS)) == 0) {
isLocatorSupportAvailable = false;
return INVALID_LOCATOR;
}
else {
//The SqlException has not occurred because of the
//stored procedure not being found. Hence we simply throw
//it back.
throw sqle;
}
}
// GemStone changes BEGIN
final int locator = blobCreateLocatorCall.getIntX(1);
if (SanityManager.TraceClientHA) {
SanityManager.DEBUG_PRINT(SanityManager.TRACE_CLIENT_HA, "Created "
+ "new locator for BLOB" + locator
+ " for agent " + connection.agent_);
}
return locator;
/* (original code)
return blobCreateLocatorCall.getIntX(1);
*/
// GemStone changes END
}
/**
* This method frees the BLOB and releases the resources that it
* holds. (E.g., temporary space used to store this BLOB on the server.)
* @param locator locator that designates the BLOB to be released.
* @throws com.pivotal.gemfirexd.internal.client.am.SqlException
*/
void blobReleaseLocator(int locator) throws SqlException
{
if (blobReleaseLocatorCall == null ||
!blobReleaseLocatorCall.openOnClient_) {
blobReleaseLocatorCall = connection.prepareCallX
("CALL SYSIBM.BLOBRELEASELOCATOR(?)",
java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY,
java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT);
// Make sure this statement does not commit user transaction
blobReleaseLocatorCall.isAutoCommittableStatement_ = false;
}
// GemStone changes BEGIN
if (SanityManager.TraceClientHA) {
SanityManager.DEBUG_PRINT(SanityManager.TRACE_CLIENT_HA, "Releasing "
+ "BLOB using locator " + locator + " for agent "
+ connection.agent_);
}
// GemStone changes END
blobReleaseLocatorCall.setIntX(1, locator);
try {
blobReleaseLocatorCall.executeX();
} catch (SqlException sqle) {
// GemStone changes BEGIN
sqle = handleInvalidLocator(sqle, "Blob.free()");
/* (original code)
sqle = handleInvalidLocator(sqle);
*/
// GemStone changes END
throw sqle;
}
}
/**
* Retrieves the byte position in the BLOB value designated by this
* locator
at which pattern given by
* searchLocator
begins. The search begins at position
* fromPosition
.
* @param locator locator that identifies the BLOB to be searched.
* @param searchLocator locator designating the BLOB value for which to
* search
* @param fromPosition the position in the BLOB value
* at which to begin searching; the first position is 1
* @throws com.pivotal.gemfirexd.internal.client.am.SqlException
* @return the position at which the pattern begins, else -1
*/
long blobGetPositionFromLocator(int locator,
int searchLocator,
long fromPosition) throws SqlException
{
if (blobGetPositionFromLocatorCall == null ||
!blobGetPositionFromLocatorCall.openOnClient_) {
blobGetPositionFromLocatorCall = connection.prepareCallX
("? = CALL SYSIBM.BLOBGETPOSITIONFROMLOCATOR(?, ?, ?)",
java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY,
java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT);
blobGetPositionFromLocatorCall
.registerOutParameterX(1, java.sql.Types.BIGINT);
// Make sure this statement does not commit user transaction
blobGetPositionFromLocatorCall.isAutoCommittableStatement_ = false;
}
// GemStone changes BEGIN
if (SanityManager.TraceClientHA) {
SanityManager.DEBUG_PRINT(SanityManager.TRACE_CLIENT_HA, "Getting "
+ "BLOB position using locator " + locator + " searchLocator="
+ searchLocator + " fromPosition=" + fromPosition + " for agent "
+ connection.agent_);
}
// GemStone changes END
blobGetPositionFromLocatorCall.setIntX(2, locator);
blobGetPositionFromLocatorCall.setIntX(3, searchLocator);
blobGetPositionFromLocatorCall.setLongX(4, fromPosition);
try {
blobGetPositionFromLocatorCall.executeX();
} catch (SqlException sqle) {
// GemStone changes BEGIN
sqle = handleInvalidLocator(sqle, "Blob.position()");
/* (original code)
sqle = handleInvalidLocator(sqle);
*/
// GemStone changes END
throw sqle;
}
return blobGetPositionFromLocatorCall.getLongX(1);
}
/**
* Retrieves the byte position at which the specified byte array
* searchLiteral
begins within the BLOB
value
* identified by locator
. The search for
* searchLiteral
begins at position fromPosition
.
*
* If searchLiteral
is longer than the maximum length of a
* VARCHAR FOR BIT DATA, it will be split into smaller fragments, and
* repeated procedure calls will be made to perform the entire search
*
* @param locator locator that identifies the BLOB to be searched.
* @param searchLiteral the byte array for which to search
* @param fromPosition the position at which to begin searching; the
* first position is 1
* @throws com.pivotal.gemfirexd.internal.client.am.SqlException
* @return the position at which the pattern appears, else -1
*/
long blobGetPositionFromBytes(int locator,
byte[] searchLiteral,
long fromPosition) throws SqlException
{
long blobLength = -1; // Will be fetched from server if needed
int patternLength = searchLiteral.length;
// GemStone changes BEGIN
if (SanityManager.TraceClientHA) {
SanityManager.DEBUG_PRINT(SanityManager.TRACE_CLIENT_HA, "Getting "
+ "BLOB position from bytes using locator " + locator
+ " fromPosition=" + fromPosition
+ " for agent " + connection.agent_);
}
// GemStone changes END
// If searchLiteral needs to be partitioned,
// we may have to try several start positions
do {
long foundAt = blobGetPositionFromBytes(locator,
fromPosition,
searchLiteral,
0,
VARCHAR_MAXWIDTH);
// If searchLiteral is longer than VARCHAR_MAXWIDTH,
// we need to check the rest
boolean tryAgain = false;
if ((patternLength > VARCHAR_MAXWIDTH) && (foundAt > 0)) {
// First part of searchLiteral matched, check rest
int comparedSoFar = VARCHAR_MAXWIDTH;
while (comparedSoFar < patternLength) {
int numBytesThisRound
= Math.min(patternLength - comparedSoFar,
VARCHAR_MAXWIDTH);
long pos = blobGetPositionFromBytes(locator,
foundAt + comparedSoFar,
searchLiteral,
comparedSoFar,
numBytesThisRound);
if (pos != (foundAt + comparedSoFar)) {
// This part did not match
// Try to find a later match for the same prefix
tryAgain = true;
fromPosition = foundAt + 1;
break;
}
comparedSoFar += numBytesThisRound;
}
}
if (!tryAgain) return foundAt;
// Need Blob length in order to determine when to stop
if (blobLength < 0) {
blobLength = blobGetLength(locator);
}
} while (fromPosition + patternLength <= blobLength);
return -1; // No match
}
/**
* Retrieves the byte position at which the specified part of the byte
* array searchLiteral
begins within the BLOB
* value identified by locator
. The search for
* searchLiteral
begins at position fromPosition
.
*
* This is a helper function used by blobGetPositionFromBytes(int, byte[],
* long) for each call to the BLOBGETPOSITIONFROMBYTES procedure.
*
* @param locator locator that identifies the BLOB to be searched.
* @param searchLiteral the byte array for which to search
* @param fromPosition the position at which to begin searching; the
* first position is 1
* @param offset the offset into the array searchLiteral
at
* which the pattern to search for starts
* @param length the number of bytes from the array of bytes
* searchLiteral
to use for the pattern to search
* for. It is assumed that this length is smaller than the maximum
* size of a VARCHAR FOR BIT DATA column. Otherwise, an exception
* will be thrown.
* @throws com.pivotal.gemfirexd.internal.client.am.SqlException
* @return the position at which the pattern appears, else -1
*/
private long blobGetPositionFromBytes(int locator,
long fromPosition,
byte[] searchLiteral,
int offset,
int length) throws SqlException
{
if (blobGetPositionFromBytesCall == null ||
!blobGetPositionFromBytesCall.openOnClient_) {
blobGetPositionFromBytesCall = connection.prepareCallX
("? = CALL SYSIBM.BLOBGETPOSITIONFROMBYTES(?, ?, ?)",
java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY,
java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT);
blobGetPositionFromBytesCall
.registerOutParameterX(1, java.sql.Types.BIGINT);
// Make sure this statement does not commit user transaction
blobGetPositionFromBytesCall.isAutoCommittableStatement_ = false;
}
byte[] bytesToBeCompared = searchLiteral;
int numBytes = Math.min(searchLiteral.length - offset, length);
if (numBytes != bytesToBeCompared.length) {
// Need an array that contains just what is to be sent
bytesToBeCompared = new byte[numBytes];
System.arraycopy(searchLiteral, offset,
bytesToBeCompared, 0, numBytes);
}
// GemStone changes BEGIN
if (SanityManager.TraceClientHA) {
SanityManager.DEBUG_PRINT(SanityManager.TRACE_CLIENT_HA, "Getting "
+ "BLOB position from bytes2 using locator " + locator
+ " fromPosition=" + fromPosition + " offset=" + offset
+ " length=" + length + " for agent " + connection.agent_);
}
// GemStone changes END
blobGetPositionFromBytesCall.setIntX(2, locator);
blobGetPositionFromBytesCall.setBytesX(3, bytesToBeCompared);
blobGetPositionFromBytesCall.setLongX(4, fromPosition);
try {
blobGetPositionFromBytesCall.executeX();
} catch (SqlException sqle) {
// GemStone changes BEGIN
sqle = handleInvalidLocator(sqle, "Blob.position()");
/* (original code)
sqle = handleInvalidLocator(sqle);
*/
// GemStone changes END
throw sqle;
}
return blobGetPositionFromBytesCall.getLongX(1);
}
/**
* Returns the number of bytes in the BLOB
value
* designated by this sourceLocator
.
*
* @param sourceLocator locator that identifies the BLOB
* @throws com.pivotal.gemfirexd.internal.client.am.SqlException
* @return length of the BLOB
in bytes
*/
long blobGetLength(int sourceLocator) throws SqlException
{
if (blobGetLengthCall == null || !blobGetLengthCall.openOnClient_) {
blobGetLengthCall = connection.prepareCallX
("? = CALL SYSIBM.BLOBGETLENGTH(?)",
java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY,
java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT);
blobGetLengthCall.registerOutParameterX(1, java.sql.Types.BIGINT);
// Make sure this statement does not commit user transaction
blobGetLengthCall.isAutoCommittableStatement_ = false;
}
// GemStone changes BEGIN
if (SanityManager.TraceClientHA) {
SanityManager.DEBUG_PRINT(SanityManager.TRACE_CLIENT_HA, "Getting "
+ "BLOB length using locator " + sourceLocator
+ " for agent " + connection.agent_);
}
// GemStone changes END
blobGetLengthCall.setIntX(2, sourceLocator);
try {
blobGetLengthCall.executeX();
} catch (SqlException sqle) {
// GemStone changes BEGIN
sqle = handleInvalidLocator(sqle, "Blob.getLength()");
/* (original code)
sqle = handleInvalidLocator(sqle);
*/
// GemStone changes END
throw sqle;
}
return blobGetLengthCall.getLongX(1);
}
/**
* Retrieves all or part of the BLOB
value that is identified
* by sourceLocator
, as an array of bytes. This
* byte
array contains up to forLength
* consecutive bytes starting at position fromPosition
.
*
* If forLength
is larger than the maximum length of a VARCHAR
* FOR BIT DATA, the reading of the BLOB will be split into repeated
* procedure calls.
*
* @param sourceLocator locator that identifies the Blob to operate on
* @param fromPosition the ordinal position of the first byte in the
* BLOB
value to be extracted; the first byte is at
* position 1
* @param forLength the number of consecutive bytes to be copied; the value
* for length must be 0 or greater. Specifying a length that goes
* beyond the end of the BLOB (i.e., fromPosition + forLength
* > blob.length()
), will result in an error.
* @throws com.pivotal.gemfirexd.internal.client.am.SqlException
* @return a byte array containing up to forLength
consecutive
* bytes from the BLOB
value designated by
* sourceLocator
, starting with the byte at position
* fromPosition
*/
byte[] blobGetBytes(int sourceLocator, long fromPosition, int forLength)
throws SqlException
{
if (forLength == 0) return new byte[0];
if (blobGetBytesCall == null || !blobGetBytesCall.openOnClient_) {
blobGetBytesCall = connection.prepareCallX
("? = CALL SYSIBM.BLOBGETBYTES(?, ?, ?)",
java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY,
java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT);
blobGetBytesCall.registerOutParameterX(1, java.sql.Types.VARBINARY);
// Make sure this statement does not commit user transaction
blobGetBytesCall.isAutoCommittableStatement_ = false;
}
// GemStone changes BEGIN
if (SanityManager.TraceClientHA) {
SanityManager.DEBUG_PRINT(SanityManager.TRACE_CLIENT_HA, "Getting "
+ "BLOB bytes using locator " + sourceLocator
+ " fromPosition=" + fromPosition + " forLength=" + forLength
+ " for agent " + connection.agent_);
}
// GemStone changes END
byte retVal[] = null;
int gotSoFar = 0;
while (gotSoFar < forLength) {
blobGetBytesCall.setIntX(2, sourceLocator);
blobGetBytesCall.setLongX(3, fromPosition + gotSoFar);
blobGetBytesCall.setIntX(4, forLength - gotSoFar);
try {
blobGetBytesCall.executeX();
} catch (SqlException sqle) {
// GemStone changes BEGIN
sqle = handleInvalidLocator(sqle, "Blob.getBytes()");
/* (original code)
sqle = handleInvalidLocator(sqle);
*/
// GemStone changes END
throw sqle;
}
byte[] result = blobGetBytesCall.getBytesX(1);
if (gotSoFar == 0) { // First round of reading
if (result.length == forLength) { // Got everything
return result;
} else {
// Blob is probably greater than MAX VARCHAR length, need to
// read in parts, create array for putting pieces together
retVal = new byte[forLength];
}
}
// If not able to read more, stop
if (result.length == 0) break;
System.arraycopy(result, 0,
retVal, gotSoFar, result.length);
gotSoFar += result.length;
}
return retVal;
}
/**
* Writes all or part of the given byte
array to the
* BLOB
value designated by sourceLocator
.
* Writing starts at position fromPosition
in the
* BLOB
value; forLength
bytes from the given
* byte array are written. If the end of the Blob
value is
* reached while writing the array of bytes, then the length of the
* Blob
value will be increased to accomodate the extra bytes.
*
* If forLength
is larger than the maximum length of a VARCHAR
* FOR BIT DATA, the writing to the BLOB value will be split into repeated
* procedure calls.
*
* @param sourceLocator locator that identifies the Blob to operated on
* @param fromPosition the position in the BLOB
value at which
* to start writing; the first position is 1
* @param forLength the number of bytes to be written to the
* BLOB
value from the array of bytes
* bytes
. Specifying a length that goes beyond the end
* of the BLOB (i.e., fromPosition + forLength >
* blob.length()
, will result in an error.
* @param bytes the array of bytes to be written
* @throws com.pivotal.gemfirexd.internal.client.am.SqlException
*/
void blobSetBytes(int sourceLocator,
long fromPosition,
int forLength,
byte[] bytes) throws SqlException
{
if (blobSetBytesCall == null || !blobSetBytesCall.openOnClient_) {
blobSetBytesCall = connection.prepareCallX
("CALL SYSIBM.BLOBSETBYTES(?, ?, ?, ?)",
java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY,
java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT);
// Make sure this statement does not commit user transaction
blobSetBytesCall.isAutoCommittableStatement_ = false;
}
// GemStone changes BEGIN
if (SanityManager.TraceClientHA) {
SanityManager.DEBUG_PRINT(SanityManager.TRACE_CLIENT_HA, "Setting "
+ "BLOB bytes using locator " + sourceLocator
+ " fromPosition=" + fromPosition + " forLength=" + forLength
+ " for agent " + connection.agent_);
}
// GemStone changes END
int sentSoFar = 0;
byte[] bytesToBeSent = bytes;
while (sentSoFar < forLength) {
// Only send what can fit in a VARCHAR FOR BIT DATA parameter
int numBytesThisRound
= Math.min(forLength - sentSoFar, VARCHAR_MAXWIDTH);
if (numBytesThisRound != bytesToBeSent.length) {
// Need an array that contains just what is to be sent
bytesToBeSent = new byte[numBytesThisRound];
}
if (bytesToBeSent != bytes) {
// Need to copy from original array
System.arraycopy(bytes, sentSoFar,
bytesToBeSent, 0, numBytesThisRound);
}
blobSetBytesCall.setIntX(1, sourceLocator);
blobSetBytesCall.setLongX(2, fromPosition + sentSoFar);
blobSetBytesCall.setIntX(3, numBytesThisRound);
blobSetBytesCall.setBytesX(4, bytesToBeSent);
try {
blobSetBytesCall.executeX();
} catch (SqlException sqle) {
// GemStone changes BEGIN
sqle = handleInvalidLocator(sqle, "Blob.setBytes()");
/* (original code)
sqle = handleInvalidLocator(sqle);
*/
// GemStone changes END
throw sqle;
}
sentSoFar += numBytesThisRound;
}
}
/**
* Truncates the BLOB
value identified by
* sourceLocator
to be length
bytes.
*
* Note: If the value specified for length
is greater
* than the length+1 of the BLOB
value then an
* SqlException
will be thrown.
*
* @param sourceLocator locator identifying the Blob to be truncated
* @param length the length, in bytes, to which the BLOB
value
* should be truncated
* @throws com.pivotal.gemfirexd.internal.client.am.SqlException
*/
void blobTruncate(int sourceLocator, long length) throws SqlException
{
if (blobTruncateCall == null || !blobTruncateCall.openOnClient_) {
blobTruncateCall = connection.prepareCallX
("CALL SYSIBM.BLOBTRUNCATE(?, ?)",
java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY,
java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT);
// Make sure this statement does not commit user transaction
blobTruncateCall.isAutoCommittableStatement_ = false;
}
// GemStone changes BEGIN
if (SanityManager.TraceClientHA) {
SanityManager.DEBUG_PRINT(SanityManager.TRACE_CLIENT_HA, "Truncate "
+ "BLOB using locator " + sourceLocator + " to length=" + length
+ " for agent " + connection.agent_);
}
// GemStone changes END
blobTruncateCall.setIntX(1, sourceLocator);
blobTruncateCall.setLongX(2, length);
try {
blobTruncateCall.executeX();
} catch (SqlException sqle) {
// GemStone changes BEGIN
sqle = handleInvalidLocator(sqle, "Blob.truncate()");
/* (original code)
sqle = handleInvalidLocator(sqle);
*/
// GemStone changes END
throw sqle;
}
}
/**
* Allocates an empty CLOB on server and returns its locator. Any
* subsequent operations on this CLOB value will be stored in temporary
* space on the server.
*
* @throws com.pivotal.gemfirexd.internal.client.am.SqlException
* @return locator that identifies the created CLOB.
*/
int clobCreateLocator() throws SqlException
{
//The information on whether the locator support
//is available is cached in the boolean
//isLocatorSupportAvailable. If this is false
//we can return -1
if (!isLocatorSupportAvailable) {
return INVALID_LOCATOR;
}
try {
if (clobCreateLocatorCall == null ||
!clobCreateLocatorCall.openOnClient_) {
clobCreateLocatorCall = connection.prepareCallX
("? = CALL SYSIBM.CLOBCREATELOCATOR()",
java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY,
java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT);
clobCreateLocatorCall
.registerOutParameterX(1, java.sql.Types.INTEGER);
// Make sure this statement does not commit user transaction
clobCreateLocatorCall.isAutoCommittableStatement_ = false;
}
clobCreateLocatorCall.executeX();
}
catch(SqlException sqle) {
//An exception has occurred while calling the stored procedure
//used to create the locator value.
//We verify to see if this SqlException has a SQLState of
//42Y03(SQLState.LANG_NO_SUCH_METHOD_ALIAS)
//(corresponding to the stored procedure not being found)
//This means that locator support is not available.
//This information is cached so that each time to determine
//if locator support is available we do not have to make a
//round trip to the server.
if (sqle.getSQLState().compareTo
(ExceptionUtil.getSQLStateFromIdentifier
(SQLState.LANG_NO_SUCH_METHOD_ALIAS)) == 0) {
isLocatorSupportAvailable = false;
return INVALID_LOCATOR;
}
else {
//The SqlException has not occurred because of the
//stored procedure not being found. Hence we simply throw
//it back.
throw sqle;
}
}
// GemStone changes BEGIN
final int locator = clobCreateLocatorCall.getIntX(1);
if (SanityManager.TraceClientHA) {
SanityManager.DEBUG_PRINT(SanityManager.TRACE_CLIENT_HA, "Created "
+ "new locator for CLOB" + locator
+ " for agent " + connection.agent_);
}
return locator;
/* (original code)
return clobCreateLocatorCall.getIntX(1);
*/
// GemStone changes END
}
/**
* This method frees the CLOB and releases the resources that it
* holds. (E.g., temporary space used to store this CLOB on the server.)
* @param locator locator that designates the CLOB to be released.
* @throws com.pivotal.gemfirexd.internal.client.am.SqlException
*/
void clobReleaseLocator(int locator) throws SqlException
{
if (clobReleaseLocatorCall == null ||
!clobReleaseLocatorCall.openOnClient_) {
clobReleaseLocatorCall = connection.prepareCallX
("CALL SYSIBM.CLOBRELEASELOCATOR(?)",
java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY,
java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT);
// Make sure this statement does not commit user transaction
clobReleaseLocatorCall.isAutoCommittableStatement_ = false;
}
// GemStone changes BEGIN
if (SanityManager.TraceClientHA) {
SanityManager.DEBUG_PRINT(SanityManager.TRACE_CLIENT_HA, "Releasing "
+ "CLOB using locator " + locator
+ " for agent " + connection.agent_);
}
// GemStone changes END
clobReleaseLocatorCall.setIntX(1, locator);
try {
clobReleaseLocatorCall.executeX();
} catch (SqlException sqle) {
// GemStone changes BEGIN
sqle = handleInvalidLocator(sqle, "Clob.free()");
/* (original code)
sqle = handleInvalidLocator(sqle);
*/
// GemStone changes END
throw sqle;
}
}
/**
* Retrieves the character position at which the specified substring
* searchLiteral
begins within the CLOB
value
* identified by locator
. The search for
* searchLiteral
begins at position fromPosition
.
*
* If searchLiteral
is longer than the maximum length of a
* VARCHAR, it will be split into smaller fragments, and
* repeated procedure calls will be made to perform the entire search
*
* @param locator locator that identifies the CLOB to be searched.
* @param searchLiteral the substring for which to search
* @param fromPosition the position at which to begin searching; the
* first position is 1
* @throws com.pivotal.gemfirexd.internal.client.am.SqlException
* @return the position at which the pattern appears, else -1
*/
long clobGetPositionFromString(int locator,
String searchLiteral,
long fromPosition) throws SqlException
{
// GemStone changes BEGIN
if (SanityManager.TraceClientHA) {
SanityManager.DEBUG_PRINT(SanityManager.TRACE_CLIENT_HA, "Getting "
+ "CLOB position from string using locator " + locator
+ " searchLiteral=" + searchLiteral + " fromPosition="
+ fromPosition + " for agent " + connection.agent_);
}
// GemStone changes END
long clobLength = -1; // Will be fetched from server if needed
int patternLength = searchLiteral.length();
do {
long foundAt = clobGetPositionFromString(locator,
fromPosition,
searchLiteral,
0,
VARCHAR_MAXWIDTH);
// If searchLiteral is longer than VARCHAR_MAXWIDTH,
// we need to check the rest
boolean tryAgain = false;
if ((patternLength > VARCHAR_MAXWIDTH) && (foundAt > 0)) {
// First part of searchLiteral matched, check rest
int comparedSoFar = VARCHAR_MAXWIDTH;
while (comparedSoFar < patternLength) {
int numCharsThisRound
= Math.min(patternLength - comparedSoFar,
VARCHAR_MAXWIDTH);
long pos = clobGetPositionFromString(locator,
foundAt+comparedSoFar,
searchLiteral,
comparedSoFar,
numCharsThisRound);
if (pos != (foundAt + comparedSoFar)) {
// This part did not match
// Try to find a later match for the same prefix
tryAgain = true;
fromPosition = foundAt + 1;
break;
}
comparedSoFar += numCharsThisRound;
}
}
if (!tryAgain) return foundAt;
// Need Clob length in order to determine when to stop
if (clobLength < 0) {
clobLength = clobGetLength(locator);
}
} while (fromPosition + patternLength <= clobLength);
return -1; // No match
}
/**
*
* Retrieves the character position at which the specified part of the
* substring searchLiteral
begins within the CLOB
* value identified by locator
. The search for
* searchLiteral
begins at position fromPosition
.
*
* This is a helper function used by clobGetPositionFromString(int,
* String, long) for each call to the CLOBGETPOSITIONFROMSTRING procedure.
*
* @param locator locator that identifies the CLOB to be searched.
* @param searchLiteral the substring for which to search
* @param fromPosition the position at which to begin searching; the
* first position is 1
* @param offset the offset into the string searchLiteral
at
* which the pattern to search for starts
* @param length the number of characters from the string
* searchLiteral
to use for the pattern to search
* for. It is assumed that this length is smaller than the maximum
* size of a VARCHAR column. Otherwise, an exception will be
* thrown.
* @throws com.pivotal.gemfirexd.internal.client.am.SqlException
* @return the position at which the pattern appears, else -1
*/
private long clobGetPositionFromString(int locator,
long fromPosition,
String searchLiteral,
int offset,
int length) throws SqlException
{
if (clobGetPositionFromStringCall == null ||
!clobGetPositionFromStringCall.openOnClient_) {
clobGetPositionFromStringCall = connection.prepareCallX
("? = CALL SYSIBM.CLOBGETPOSITIONFROMSTRING(?, ?, ?)",
java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY,
java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT);
clobGetPositionFromStringCall
.registerOutParameterX(1, java.sql.Types.BIGINT);
// Make sure this statement does not commit user transaction
clobGetPositionFromStringCall.isAutoCommittableStatement_ = false;
}
// GemStone changes BEGIN
if (SanityManager.TraceClientHA) {
SanityManager.DEBUG_PRINT(SanityManager.TRACE_CLIENT_HA, "Getting "
+ "CLOB position from string2 using locator " + locator
+ " searchLiteral=" + searchLiteral + " fromPosition="
+ fromPosition + " offset=" + offset + " length=" + length
+ " for agent " + connection.agent_);
}
// GemStone changes END
String stringToBeCompared = searchLiteral;
int numChars = Math.min(searchLiteral.length() - offset, length);
if (numChars != stringToBeCompared.length()) {
// Need a String that contains just what is to be sent
stringToBeCompared
= searchLiteral.substring(offset, offset + numChars);
}
clobGetPositionFromStringCall.setIntX(2, locator);
clobGetPositionFromStringCall.setStringX(3, stringToBeCompared);
clobGetPositionFromStringCall.setLongX(4, fromPosition);
try {
clobGetPositionFromStringCall.executeX();
} catch (SqlException sqle) {
// GemStone changes BEGIN
sqle = handleInvalidLocator(sqle, "Clob.position()");
/* (original code)
sqle = handleInvalidLocator(sqle);
*/
// GemStone changes END
throw sqle;
}
return clobGetPositionFromStringCall.getLongX(1);
}
/**
* Retrieves the character position in the CLOB value designated by this
* locator
at which substring given by
* searchLocator
begins. The search begins at position
* fromPosition
.
* @param locator locator that identifies the CLOB to be searched.
* @param searchLocator locator designating the CLOB value for which to
* search
* @param fromPosition the position in the CLOB value
* at which to begin searching; the first position is 1
* @throws com.pivotal.gemfirexd.internal.client.am.SqlException
* @return the position at which the pattern begins, else -1
*/
long clobGetPositionFromLocator(int locator,
int searchLocator,
long fromPosition) throws SqlException
{
if (clobGetPositionFromLocatorCall == null ||
!clobGetPositionFromLocatorCall.openOnClient_) {
clobGetPositionFromLocatorCall = connection.prepareCallX
("? = CALL SYSIBM.CLOBGETPOSITIONFROMLOCATOR(?, ?, ?)",
java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY,
java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT);
clobGetPositionFromLocatorCall
.registerOutParameterX(1, java.sql.Types.BIGINT);
// Make sure this statement does not commit user transaction
clobGetPositionFromLocatorCall.isAutoCommittableStatement_ = false;
}
// GemStone changes BEGIN
if (SanityManager.TraceClientHA) {
SanityManager.DEBUG_PRINT(SanityManager.TRACE_CLIENT_HA, "Getting "
+ "CLOB position using locator " + locator + " searchLocator="
+ searchLocator + " fromPosition=" + fromPosition
+ " for agent " + connection.agent_);
}
// GemStone changes END
clobGetPositionFromLocatorCall.setIntX(2, locator);
clobGetPositionFromLocatorCall.setIntX(3, searchLocator);
clobGetPositionFromLocatorCall.setLongX(4, fromPosition);
try {
clobGetPositionFromLocatorCall.executeX();
} catch (SqlException sqle) {
// GemStone changes BEGIN
sqle = handleInvalidLocator(sqle, "Clob.position()");
/* (original code)
sqle = handleInvalidLocator(sqle);
*/
// GemStone changes END
throw sqle;
}
return clobGetPositionFromLocatorCall.getLongX(1);
}
/**
* Returns the number of character in the CLOB
value
* designated by this sourceLocator
.
*
* @param sourceLocator locator that identifies the CLOB
* @throws com.pivotal.gemfirexd.internal.client.am.SqlException
* @return length of the CLOB
in characters
*/
long clobGetLength(int sourceLocator) throws SqlException
{
if (clobGetLengthCall == null || !clobGetLengthCall.openOnClient_) {
clobGetLengthCall = connection.prepareCallX
("? = CALL SYSIBM.CLOBGETLENGTH(?)",
java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY,
java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT);
clobGetLengthCall.registerOutParameterX(1, java.sql.Types.BIGINT);
// Make sure this statement does not commit user transaction
clobGetLengthCall.isAutoCommittableStatement_ = false;
}
// GemStone changes BEGIN
if (SanityManager.TraceClientHA) {
SanityManager.DEBUG_PRINT(SanityManager.TRACE_CLIENT_HA, "Getting "
+ "CLOB length using locator " + sourceLocator
+ " for agent " + connection.agent_);
}
// GemStone changes END
clobGetLengthCall.setIntX(2, sourceLocator);
try {
clobGetLengthCall.executeX();
} catch (SqlException sqle) {
// GemStone changes BEGIN
sqle = handleInvalidLocator(sqle, "Clob.getLength()");
/* (original code)
sqle = handleInvalidLocator(sqle);
*/
// GemStone changes END
throw sqle;
}
return clobGetLengthCall.getLongX(1);
}
/**
* Retrieves all or part of the CLOB
value that is identified
* by sourceLocator
, as a String
. This
* String
contains up to forLength
consecutive
* characters starting at position fromPosition
.
*
* If forLength
is larger than the maximum length of a
* VARCHAR, the reading of the CLOB will be split into repeated procedure
* calls.
*
* @param sourceLocator locator that identifies the CLOB to operate on
* @param fromPosition the ordinal position of the first character in the
* CLOB
value to be extracted; the first character is
* at position 1
* @param forLength the number of consecutive characters to be copied; the
* value for length must be 0 or greater. Specifying a length that
* goes beyond the end of the CLOB (i.e., fromPosition +
* forLength > clob.length()
, will result in an error.
* @throws com.pivotal.gemfirexd.internal.client.am.SqlException
* @return a string containing up to forLength
consecutive
* characters from the CLOB
value designated by
* sourceLocator
, starting with the character at
* position fromPosition
*/
String clobGetSubString(int sourceLocator, long fromPosition, int forLength)
throws SqlException
{
if (forLength == 0) return "";
if (clobGetSubStringCall == null ||
!clobGetSubStringCall.openOnClient_) {
clobGetSubStringCall = connection.prepareCallX
("? = CALL SYSIBM.CLOBGETSUBSTRING(?, ?, ?)",
java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY,
java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT);
clobGetSubStringCall
.registerOutParameterX(1, java.sql.Types.VARCHAR);
// Make sure this statement does not commit user transaction
clobGetSubStringCall.isAutoCommittableStatement_ = false;
}
// GemStone changes BEGIN
if (SanityManager.TraceClientHA) {
SanityManager.DEBUG_PRINT(SanityManager.TRACE_CLIENT_HA, "Getting "
+ "CLOB substring using locator " + sourceLocator
+ " fromPosition=" + fromPosition + " forLength=" + forLength
+ " for agent " + connection.agent_);
}
// GemStone changes END
StringBuilder retVal = null;
int gotSoFar = 0;
while (gotSoFar < forLength) {
clobGetSubStringCall.setIntX(2, sourceLocator);
clobGetSubStringCall.setLongX(3, fromPosition + gotSoFar);
clobGetSubStringCall.setIntX(4, forLength - gotSoFar);
try {
clobGetSubStringCall.executeX();
} catch (SqlException sqle) {
// GemStone changes BEGIN
sqle = handleInvalidLocator(sqle, "Clob.read()");
/* (original code)
sqle = handleInvalidLocator(sqle);
*/
// GemStone changes END
throw sqle;
}
String result = clobGetSubStringCall.getStringX(1);
if (gotSoFar == 0) { // First round of reading
if (result.length() == forLength) { // Got everything
return result;
} else {
// Clob is probably greater than MAX VARCHAR length,
// need to read it in parts,
// create StringBuilder for putting pieces together
retVal = new StringBuilder(forLength);
}
}
// If not able to read more, stop
if (result.length() == 0) break;
retVal.append(result);
gotSoFar += result.length();
}
return retVal.toString();
}
/**
* Writes all or part of the given String
to the
* CLOB
value designated by sourceLocator
.
* Writing starts at position fromPosition
in the
* CLOB
value; forLength
characters from the
* given string are written. If the end of the Clob
value is
* reached while writing the string, then the length of the
* Clob
value will be increased to accomodate the extra
* characters.
*
* If forLength
is larger than the maximum length of a
* VARCHAR, the writing to the CLOB value will be split into repeated
* procedure calls.
*
* @param sourceLocator locator that identifies the Clob to operated on
* @param fromPosition the position in the CLOB
value at which
* to start writing; the first position is 1
* @param forLength the number of characters to be written to the
* CLOB
value from the string string
.
* Specifying a length that goes beyond the end of the CLOB (i.e.,
* fromPosition + forLength > clob.length()
, will
* result in an error.
* @param string the string to be written
* @throws com.pivotal.gemfirexd.internal.client.am.SqlException
*/
void clobSetString(int sourceLocator,
long fromPosition,
int forLength,
String string) throws SqlException
{
if (clobSetStringCall == null || !clobSetStringCall.openOnClient_) {
clobSetStringCall = connection.prepareCallX
("CALL SYSIBM.CLOBSETSTRING(?, ?, ?, ?)",
java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY,
java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT);
// Make sure this statement does not commit user transaction
clobSetStringCall.isAutoCommittableStatement_ = false;
}
// GemStone changes BEGIN
if (SanityManager.TraceClientHA) {
SanityManager.DEBUG_PRINT(SanityManager.TRACE_CLIENT_HA, "Setting "
+ "CLOB string using locator " + sourceLocator
+ " fromPosition=" + fromPosition + " forLength=" + forLength
+ " for agent " + connection.agent_);
}
// GemStone changes END
int sentSoFar = 0;
String stringToBeSent = string;
while (sentSoFar < forLength) {
// Only send what can fit in a VARCHAR parameter
int numCharsThisRound
= Math.min(forLength - sentSoFar, VARCHAR_MAXWIDTH);
if (numCharsThisRound < string.length()) {
// Need a String that contains just what is to be sent
stringToBeSent
= string.substring(sentSoFar, sentSoFar+numCharsThisRound);
}
clobSetStringCall.setIntX(1, sourceLocator);
clobSetStringCall.setLongX(2, fromPosition + sentSoFar);
clobSetStringCall.setIntX(3, numCharsThisRound);
clobSetStringCall.setStringX(4, stringToBeSent);
try {
clobSetStringCall.executeX();
} catch (SqlException sqle) {
// GemStone changes BEGIN
sqle = handleInvalidLocator(sqle, "Clob.setString()");
/* (original code)
sqle = handleInvalidLocator(sqle);
*/
// GemStone changes END
throw sqle;
}
sentSoFar += numCharsThisRound;
}
}
/**
* Truncates the CLOB
value identified by
* sourceLocator
to be length
characters.
*
* Note: If the value specified for length
is greater
* than the length+1 of the CLOB
value then an
* SqlException
will be thrown.
*
* @param sourceLocator locator identifying the Clob to be truncated
* @param length the length, in characters, to which the CLOB
* value should be truncated
* @throws com.pivotal.gemfirexd.internal.client.am.SqlException
*/
void clobTruncate(int sourceLocator, long length) throws SqlException
{
if (clobTruncateCall == null || !clobTruncateCall.openOnClient_) {
clobTruncateCall = connection.prepareCallX
("CALL SYSIBM.CLOBTRUNCATE(?, ?)",
java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY,
java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT);
// Make sure this statement does not commit user transaction
clobTruncateCall.isAutoCommittableStatement_ = false;
}
// GemStone changes BEGIN
if (SanityManager.TraceClientHA) {
SanityManager.DEBUG_PRINT(SanityManager.TRACE_CLIENT_HA, "Truncate "
+ "CLOB using locator " + sourceLocator + " to length=" + length
+ " for agent " + connection.agent_);
}
// GemStone changes END
clobTruncateCall.setIntX(1, sourceLocator);
clobTruncateCall.setLongX(2, length);
try {
clobTruncateCall.executeX();
} catch (SqlException sqle) {
// GemStone changes BEGIN
sqle = handleInvalidLocator(sqle, "Clob.truncate()");
/* (original code)
sqle = handleInvalidLocator(sqle);
*/
// GemStone changes END
throw sqle;
}
}
/**
* If the given exception indicates that locator was not valid, we
* assume the locator has been garbage-collected due to
* transaction commit, and wrap the exception in an exception with
* SQL state LOB_OBJECT_INVALID
.
* @param sqle Exception to be checked
* @return If sqle
indicates that locator was
* invalid, an SqlException
with SQL state
* LOB_OBJECT_INVALID
. Otherwise, the
* incoming exception is returned.
*/
private SqlException handleInvalidLocator(SqlException sqle,
String method /* GemStone addition */)
{
SqlException ex = sqle;
while (ex != null) {
if (ex.getSQLState().compareTo
(ExceptionUtil.getSQLStateFromIdentifier
(SQLState.LOB_LOCATOR_INVALID)) == 0) {
// GemStone changes BEGIN
// if there has been a failover then wrap in GFXD_NODE_SHUTDOWN
if (this.failedServer != null) {
return new SqlException(this.connection.agent_.logWriter_,
new ClientMessageId(SQLState.GFXD_NODE_SHUTDOWN),
new Object[] { this.failedServer, method }, sqle);
}
// GemStone changes END
return new SqlException(connection.agent_.logWriter_,
new ClientMessageId(SQLState.LOB_OBJECT_INVALID),
null,
sqle);
}
ex = ex.getNextException();
}
// LOB_LOCATOR_INVALID not found, return original exception
return sqle;
}
}