| /* |
| |
| Derby - Class org.apache.derby.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. |
| |
| */ |
| |
| package org.apache.derby.client.am; |
| |
| import java.sql.ResultSet; |
| import java.sql.Types; |
| import org.apache.derby.shared.common.error.ExceptionUtil; |
| import org.apache.derby.shared.common.reference.SQLState; |
| |
| /** |
| * 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 <code>Connection</code> parameter and |
| * all calls will be made on that connection. |
| * <p> |
| * The class makes sure that each procedure call is only prepared once |
| * per instance. Hence, it will keep references to |
| * <code>CallableStatement</code> objects for procedures that have |
| * been called through this instance. This makes it possible to |
| * prepare each procedure call only once per <code>Connection</code>. |
| * <p> |
| * 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 ClientConnection#locatorProcedureCall for an example of how to use |
| * this class. |
| */ |
| class CallableLocatorProcedures |
| { |
| //caches the information from a Stored Procedure |
| //call as to whether locator support is available in |
| //the server or not. |
| private 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 ClientCallableStatement blobCreateLocatorCall; |
| private ClientCallableStatement blobReleaseLocatorCall; |
| private ClientCallableStatement blobGetPositionFromLocatorCall; |
| private ClientCallableStatement blobGetPositionFromBytesCall; |
| private ClientCallableStatement blobGetLengthCall; |
| private ClientCallableStatement blobGetBytesCall; |
| private ClientCallableStatement blobSetBytesCall; |
| private ClientCallableStatement blobTruncateCall; |
| private ClientCallableStatement clobCreateLocatorCall; |
| private ClientCallableStatement clobReleaseLocatorCall; |
| private ClientCallableStatement clobGetPositionFromStringCall; |
| private ClientCallableStatement clobGetPositionFromLocatorCall; |
| private ClientCallableStatement clobGetLengthCall; |
| private ClientCallableStatement clobGetSubStringCall; |
| private ClientCallableStatement clobSetStringCall; |
| private ClientCallableStatement clobTruncateCall; |
| |
| /** |
| * The connection to be used when calling the stored procedures. |
| */ |
| private final ClientConnection 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; |
| |
| /** |
| * Create an instance to be used for calling locator-based stored |
| * procedures. |
| * |
| * @param conn the connection to be used to prepare calls. |
| */ |
| CallableLocatorProcedures(ClientConnection 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. |
| * |
| * @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()", |
| ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_READ_ONLY, |
| connection.holdability()); |
| blobCreateLocatorCall |
| .registerOutParameterX(1, 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; |
| } |
| } |
| |
| return blobCreateLocatorCall.getIntX(1); |
| } |
| |
| /** |
| * 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. |
| */ |
| void blobReleaseLocator(int locator) throws SqlException |
| { |
| if (blobReleaseLocatorCall == null || |
| !blobReleaseLocatorCall.openOnClient_) { |
| blobReleaseLocatorCall = connection.prepareCallX |
| ("CALL SYSIBM.BLOBRELEASELOCATOR(?)", |
| ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_READ_ONLY, |
| ResultSet.CLOSE_CURSORS_AT_COMMIT); |
| // Make sure this statement does not commit user transaction |
| blobReleaseLocatorCall.isAutoCommittableStatement_ = false; |
| } |
| |
| blobReleaseLocatorCall.setIntX(1, locator); |
| try { |
| blobReleaseLocatorCall.executeX(); |
| } catch (SqlException sqle) { |
| sqle = handleInvalidLocator(sqle); |
| throw sqle; |
| } |
| } |
| |
| /** |
| * Retrieves the byte position in the BLOB value designated by this |
| * <code>locator</code> at which pattern given by |
| * <code>searchLocator</code> begins. The search begins at position |
| * <code>fromPosition</code>. |
| * @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 |
| * @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(?, ?, ?)", |
| ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_READ_ONLY, |
| ResultSet.CLOSE_CURSORS_AT_COMMIT); |
| blobGetPositionFromLocatorCall |
| .registerOutParameterX(1, Types.BIGINT); |
| // Make sure this statement does not commit user transaction |
| blobGetPositionFromLocatorCall.isAutoCommittableStatement_ = false; |
| } |
| |
| blobGetPositionFromLocatorCall.setIntX(2, locator); |
| blobGetPositionFromLocatorCall.setIntX(3, searchLocator); |
| blobGetPositionFromLocatorCall.setLongX(4, fromPosition); |
| try { |
| blobGetPositionFromLocatorCall.executeX(); |
| } catch (SqlException sqle) { |
| sqle = handleInvalidLocator(sqle); |
| throw sqle; |
| } |
| return blobGetPositionFromLocatorCall.getLongX(1); |
| } |
| |
| |
| /** |
| * Retrieves the byte position at which the specified byte array |
| * <code>searchLiteral</code> begins within the <code>BLOB</code> value |
| * identified by <code>locator</code>. The search for |
| * <code>searchLiteral</code> begins at position <code>fromPosition</code>. |
| * <p> |
| * If <code>searchLiteral</code> 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 |
| * @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; |
| |
| // 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 <code>searchLiteral</code> begins within the <code>BLOB</code> |
| * value identified by <code>locator</code>. The search for |
| * <code>searchLiteral</code> begins at position <code>fromPosition</code>. |
| * <p> |
| * 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 <code>searchLiteral</code> at |
| * which the pattern to search for starts |
| * @param length the number of bytes from the array of bytes |
| * <code>searchLiteral</code> 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. |
| * @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(?, ?, ?)", |
| ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_READ_ONLY, |
| ResultSet.CLOSE_CURSORS_AT_COMMIT); |
| blobGetPositionFromBytesCall |
| .registerOutParameterX(1, 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); |
| } |
| |
| blobGetPositionFromBytesCall.setIntX(2, locator); |
| blobGetPositionFromBytesCall.setBytesX(3, bytesToBeCompared); |
| blobGetPositionFromBytesCall.setLongX(4, fromPosition); |
| try { |
| blobGetPositionFromBytesCall.executeX(); |
| } catch (SqlException sqle) { |
| sqle = handleInvalidLocator(sqle); |
| throw sqle; |
| } |
| return blobGetPositionFromBytesCall.getLongX(1); |
| } |
| |
| |
| /** |
| * Returns the number of bytes in the <code>BLOB</code> value |
| * designated by this <code>sourceLocator</code>. |
| * |
| * @param sourceLocator locator that identifies the BLOB |
| * @return length of the <code>BLOB</code> in bytes |
| */ |
| long blobGetLength(int sourceLocator) throws SqlException |
| { |
| if (blobGetLengthCall == null || !blobGetLengthCall.openOnClient_) { |
| blobGetLengthCall = connection.prepareCallX |
| ("? = CALL SYSIBM.BLOBGETLENGTH(?)", |
| ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_READ_ONLY, |
| ResultSet.CLOSE_CURSORS_AT_COMMIT); |
| blobGetLengthCall.registerOutParameterX(1, Types.BIGINT); |
| // Make sure this statement does not commit user transaction |
| blobGetLengthCall.isAutoCommittableStatement_ = false; |
| } |
| |
| blobGetLengthCall.setIntX(2, sourceLocator); |
| try { |
| blobGetLengthCall.executeX(); |
| } catch (SqlException sqle) { |
| sqle = handleInvalidLocator(sqle); |
| throw sqle; |
| } |
| return blobGetLengthCall.getLongX(1); |
| } |
| |
| /** |
| * Retrieves all or part of the <code>BLOB</code> value that is identified |
| * by <code>sourceLocator</code>, as an array of bytes. This |
| * <code>byte</code> array contains up to <code>forLength</code> |
| * consecutive bytes starting at position <code>fromPosition</code>. |
| * <p> |
| * If <code>forLength</code> 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 |
| * <code>BLOB</code> 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., <code>fromPosition + forLength |
| * > blob.length()</code>), will result in an error. |
| * @return a byte array containing up to <code>forLength</code> consecutive |
| * bytes from the <code>BLOB</code> value designated by |
| * <code>sourceLocator</code>, starting with the byte at position |
| * <code>fromPosition</code> |
| */ |
| 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(?, ?, ?)", |
| ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_READ_ONLY, |
| ResultSet.CLOSE_CURSORS_AT_COMMIT); |
| blobGetBytesCall.registerOutParameterX(1, Types.VARBINARY); |
| // Make sure this statement does not commit user transaction |
| blobGetBytesCall.isAutoCommittableStatement_ = false; |
| } |
| |
| 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) { |
| sqle = handleInvalidLocator(sqle); |
| 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 <code>byte</code> array to the |
| * <code>BLOB</code> value designated by <code>sourceLocator</code>. |
| * Writing starts at position <code>fromPosition</code> in the |
| * <code>BLOB</code> value; <code>forLength</code> bytes from the given |
| * byte array are written. If the end of the <code>Blob</code> value is |
| * reached while writing the array of bytes, then the length of the |
| * <code>Blob</code> value will be increased to accomodate the extra bytes. |
| * <p> |
| * If <code>forLength</code> 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 <code>BLOB</code> value at which |
| * to start writing; the first position is 1 |
| * @param forLength the number of bytes to be written to the |
| * <code>BLOB</code> value from the array of bytes |
| * <code>bytes</code>. Specifying a length that goes beyond the end |
| * of the BLOB (i.e., <code>fromPosition + forLength > |
| * blob.length()</code>, will result in an error. |
| * @param bytes the array of bytes to be written |
| */ |
| void blobSetBytes(int sourceLocator, |
| long fromPosition, |
| int forLength, |
| byte[] bytes) throws SqlException |
| { |
| if (blobSetBytesCall == null || !blobSetBytesCall.openOnClient_) { |
| blobSetBytesCall = connection.prepareCallX |
| ("CALL SYSIBM.BLOBSETBYTES(?, ?, ?, ?)", |
| ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_READ_ONLY, |
| ResultSet.CLOSE_CURSORS_AT_COMMIT); |
| // Make sure this statement does not commit user transaction |
| blobSetBytesCall.isAutoCommittableStatement_ = false; |
| } |
| |
| 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) { |
| sqle = handleInvalidLocator(sqle); |
| throw sqle; |
| } |
| |
| sentSoFar += numBytesThisRound; |
| } |
| } |
| |
| /** |
| * Truncates the <code>BLOB</code> value identified by |
| * <code>sourceLocator</code> to be <code>length</code> bytes. |
| * <p> |
| * <b>Note:</b> If the value specified for <code>length</code> is greater |
| * than the length+1 of the <code>BLOB</code> value then an |
| * <code>SqlException</code> will be thrown. |
| * |
| * @param sourceLocator locator identifying the Blob to be truncated |
| * @param length the length, in bytes, to which the <code>BLOB</code> value |
| * should be truncated |
| */ |
| void blobTruncate(int sourceLocator, long length) throws SqlException |
| { |
| if (blobTruncateCall == null || !blobTruncateCall.openOnClient_) { |
| blobTruncateCall = connection.prepareCallX |
| ("CALL SYSIBM.BLOBTRUNCATE(?, ?)", |
| ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_READ_ONLY, |
| ResultSet.CLOSE_CURSORS_AT_COMMIT); |
| // Make sure this statement does not commit user transaction |
| blobTruncateCall.isAutoCommittableStatement_ = false; |
| } |
| |
| blobTruncateCall.setIntX(1, sourceLocator); |
| blobTruncateCall.setLongX(2, length); |
| try { |
| blobTruncateCall.executeX(); |
| } catch (SqlException sqle) { |
| sqle = handleInvalidLocator(sqle); |
| 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. |
| * |
| * @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()", |
| ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_READ_ONLY, |
| ResultSet.CLOSE_CURSORS_AT_COMMIT); |
| clobCreateLocatorCall |
| .registerOutParameterX(1, 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; |
| } |
| } |
| |
| return clobCreateLocatorCall.getIntX(1); |
| } |
| |
| /** |
| * 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. |
| */ |
| void clobReleaseLocator(int locator) throws SqlException |
| { |
| if (clobReleaseLocatorCall == null || |
| !clobReleaseLocatorCall.openOnClient_) { |
| clobReleaseLocatorCall = connection.prepareCallX |
| ("CALL SYSIBM.CLOBRELEASELOCATOR(?)", |
| ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_READ_ONLY, |
| ResultSet.CLOSE_CURSORS_AT_COMMIT); |
| // Make sure this statement does not commit user transaction |
| clobReleaseLocatorCall.isAutoCommittableStatement_ = false; |
| } |
| |
| clobReleaseLocatorCall.setIntX(1, locator); |
| try { |
| clobReleaseLocatorCall.executeX(); |
| } catch (SqlException sqle) { |
| sqle = handleInvalidLocator(sqle); |
| throw sqle; |
| } |
| } |
| |
| |
| /** |
| * Retrieves the character position at which the specified substring |
| * <code>searchLiteral</code> begins within the <code>CLOB</code> value |
| * identified by <code>locator</code>. The search for |
| * <code>searchLiteral</code> begins at position <code>fromPosition</code>. |
| * <p> |
| * If <code>searchLiteral</code> 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 |
| * @return the position at which the pattern appears, else -1 |
| */ |
| long clobGetPositionFromString(int locator, |
| String searchLiteral, |
| long fromPosition) throws SqlException |
| { |
| 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 <code>searchLiteral</code> begins within the <code>CLOB</code> |
| * value identified by <code>locator</code>. The search for |
| * <code>searchLiteral</code> begins at position <code>fromPosition</code>. |
| * <p> |
| * 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 <code>searchLiteral</code> at |
| * which the pattern to search for starts |
| * @param length the number of characters from the string |
| * <code>searchLiteral</code> 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. |
| * @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(?, ?, ?)", |
| ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_READ_ONLY, |
| ResultSet.CLOSE_CURSORS_AT_COMMIT); |
| clobGetPositionFromStringCall |
| .registerOutParameterX(1, Types.BIGINT); |
| // Make sure this statement does not commit user transaction |
| clobGetPositionFromStringCall.isAutoCommittableStatement_ = false; |
| } |
| |
| 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) { |
| sqle = handleInvalidLocator(sqle); |
| throw sqle; |
| } |
| return clobGetPositionFromStringCall.getLongX(1); |
| } |
| |
| /** |
| * Retrieves the character position in the CLOB value designated by this |
| * <code>locator</code> at which substring given by |
| * <code>searchLocator</code> begins. The search begins at position |
| * <code>fromPosition</code>. |
| * @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 |
| * @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(?, ?, ?)", |
| ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_READ_ONLY, |
| ResultSet.CLOSE_CURSORS_AT_COMMIT); |
| clobGetPositionFromLocatorCall |
| .registerOutParameterX(1, Types.BIGINT); |
| // Make sure this statement does not commit user transaction |
| clobGetPositionFromLocatorCall.isAutoCommittableStatement_ = false; |
| } |
| |
| clobGetPositionFromLocatorCall.setIntX(2, locator); |
| clobGetPositionFromLocatorCall.setIntX(3, searchLocator); |
| clobGetPositionFromLocatorCall.setLongX(4, fromPosition); |
| try { |
| clobGetPositionFromLocatorCall.executeX(); |
| } catch (SqlException sqle) { |
| sqle = handleInvalidLocator(sqle); |
| throw sqle; |
| } |
| return clobGetPositionFromLocatorCall.getLongX(1); |
| } |
| |
| /** |
| * Returns the number of character in the <code>CLOB</code> value |
| * designated by this <code>sourceLocator</code>. |
| * |
| * @param sourceLocator locator that identifies the CLOB |
| * @return length of the <code>CLOB</code> in characters |
| */ |
| long clobGetLength(int sourceLocator) throws SqlException |
| { |
| if (clobGetLengthCall == null || !clobGetLengthCall.openOnClient_) { |
| clobGetLengthCall = connection.prepareCallX |
| ("? = CALL SYSIBM.CLOBGETLENGTH(?)", |
| ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_READ_ONLY, |
| ResultSet.CLOSE_CURSORS_AT_COMMIT); |
| clobGetLengthCall.registerOutParameterX(1, Types.BIGINT); |
| // Make sure this statement does not commit user transaction |
| clobGetLengthCall.isAutoCommittableStatement_ = false; |
| } |
| |
| clobGetLengthCall.setIntX(2, sourceLocator); |
| try { |
| clobGetLengthCall.executeX(); |
| } catch (SqlException sqle) { |
| sqle = handleInvalidLocator(sqle); |
| throw sqle; |
| } |
| return clobGetLengthCall.getLongX(1); |
| } |
| |
| /** |
| * Retrieves all or part of the <code>CLOB</code> value that is identified |
| * by <code>sourceLocator</code>, as a <code>String</code>. This |
| * <code>String</code> contains up to <code>forLength</code> consecutive |
| * characters starting at position <code>fromPosition</code>. |
| * <p> |
| * If <code>forLength</code> 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 |
| * <code>CLOB</code> 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., <code>fromPosition + |
| * forLength > clob.length()</code>, will result in an error. |
| * @return a string containing up to <code>forLength</code> consecutive |
| * characters from the <code>CLOB</code> value designated by |
| * <code>sourceLocator</code>, starting with the character at |
| * position <code>fromPosition</code> |
| */ |
| 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(?, ?, ?)", |
| ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_READ_ONLY, |
| ResultSet.CLOSE_CURSORS_AT_COMMIT); |
| clobGetSubStringCall |
| .registerOutParameterX(1, Types.VARCHAR); |
| // Make sure this statement does not commit user transaction |
| clobGetSubStringCall.isAutoCommittableStatement_ = false; |
| } |
| |
| StringBuffer 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) { |
| sqle = handleInvalidLocator(sqle); |
| 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 StringBuffer for putting pieces together |
| retVal = new StringBuffer(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 <code>String</code> to the |
| * <code>CLOB</code> value designated by <code>sourceLocator</code>. |
| * Writing starts at position <code>fromPosition</code> in the |
| * <code>CLOB</code> value; <code>forLength</code> characters from the |
| * given string are written. If the end of the <code>CLOB</code> value is |
| * reached while writing the string, then the length of the |
| * <code>CLOB</code> value will be increased to accomodate the extra |
| * characters. |
| * <p> |
| * If <code>forLength</code> 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 <code>CLOB</code> value at which |
| * to start writing; the first position is 1 |
| * @param forLength the number of characters to be written to the |
| * <code>CLOB</code> value from the string <code>string</code>. |
| * Specifying a length that goes beyond the end of the CLOB (i.e., |
| * <code>fromPosition + forLength > clob.length()</code>, will |
| * result in an error. |
| * @param string the string to be written |
| */ |
| void clobSetString(int sourceLocator, |
| long fromPosition, |
| int forLength, |
| String string) throws SqlException |
| { |
| if (clobSetStringCall == null || !clobSetStringCall.openOnClient_) { |
| clobSetStringCall = connection.prepareCallX |
| ("CALL SYSIBM.CLOBSETSTRING(?, ?, ?, ?)", |
| ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_READ_ONLY, |
| ResultSet.CLOSE_CURSORS_AT_COMMIT); |
| // Make sure this statement does not commit user transaction |
| clobSetStringCall.isAutoCommittableStatement_ = false; |
| } |
| |
| 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) { |
| sqle = handleInvalidLocator(sqle); |
| throw sqle; |
| } |
| |
| sentSoFar += numCharsThisRound; |
| } |
| } |
| |
| /** |
| * Truncates the <code>CLOB</code> value identified by |
| * <code>sourceLocator</code> to be <code>length</code> characters. |
| * <p> |
| * <b>Note:</b> If the value specified for <code>length</code> is greater |
| * than the length+1 of the <code>CLOB</code> value then an |
| * <code>SqlException</code> will be thrown. |
| * |
| * @param sourceLocator locator identifying the CLOB to be truncated |
| * @param length the length, in characters, to which the <code>CLOB</code> |
| * value should be truncated |
| */ |
| void clobTruncate(int sourceLocator, long length) throws SqlException |
| { |
| if (clobTruncateCall == null || !clobTruncateCall.openOnClient_) { |
| clobTruncateCall = connection.prepareCallX |
| ("CALL SYSIBM.CLOBTRUNCATE(?, ?)", |
| ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_READ_ONLY, |
| ResultSet.CLOSE_CURSORS_AT_COMMIT); |
| // Make sure this statement does not commit user transaction |
| clobTruncateCall.isAutoCommittableStatement_ = false; |
| } |
| |
| clobTruncateCall.setIntX(1, sourceLocator); |
| clobTruncateCall.setLongX(2, length); |
| try { |
| clobTruncateCall.executeX(); |
| } catch (SqlException sqle) { |
| sqle = handleInvalidLocator(sqle); |
| 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 <code>LOB_OBJECT_INVALID</code>. |
| * @param sqle Exception to be checked |
| * @return If <code>sqle</code> indicates that locator was |
| * invalid, an <code>SqlException</code> with SQL state |
| * <code>LOB_OBJECT_INVALID</code>. Otherwise, the |
| * incoming exception is returned. |
| */ |
| private SqlException handleInvalidLocator(SqlException sqle) |
| { |
| SqlException ex = sqle; |
| while (ex != null) { |
| if (ex.getSQLState().compareTo |
| (ExceptionUtil.getSQLStateFromIdentifier |
| (SQLState.LOB_LOCATOR_INVALID)) == 0) { |
| 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; |
| } |
| } |