/* Copyright (c) 2001-2010, The HSQL Development Group * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the HSQL Development Group nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG, * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.hsqldb.persist; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import org.hsqldb.Database; import org.hsqldb.DatabaseURL; import org.hsqldb.HsqlException; import org.hsqldb.HsqlNameManager.HsqlName; import org.hsqldb.Session; import org.hsqldb.Statement; import org.hsqldb.Table; import org.hsqldb.error.Error; import org.hsqldb.error.ErrorCode; import org.hsqldb.lib.ArrayUtil; import org.hsqldb.lib.HashMappedList; import org.hsqldb.lib.LineGroupReader; import org.hsqldb.navigator.RowSetNavigator; import org.hsqldb.result.Result; import org.hsqldb.result.ResultLob; import org.hsqldb.result.ResultMetaData; import org.hsqldb.result.ResultProperties; import org.hsqldb.store.ValuePool; import org.hsqldb.types.BlobData; import org.hsqldb.types.BlobDataID; import org.hsqldb.types.ClobData; import org.hsqldb.types.ClobDataID; import org.hsqldb.types.Types; /** * @author Fred Toussi (fredt@users dot sourceforge.net) * @version 1.9.0 * @since 1.9.0 */ public class LobManager { static final String resourceFileName = "/org/hsqldb/resources/lob-schema.sql"; static final String[] starters = new String[]{ "/*" }; // Database database; LobStore lobStore; Session sysLobSession; // // int lobBlockSize; int totalBlockLimitCount = Integer.MAX_VALUE; // int deletedLobCount; // Statement getLob; Statement getLobPart; Statement deleteLobCall; Statement deleteLobPartCall; Statement divideLobPartCall; Statement createLob; Statement createLobPartCall; Statement updateLobLength; Statement updateLobUsage; Statement getNextLobId; Statement deleteUnusedLobs; Statement getLobCount; // LOBS columns private interface LOBS { int BLOCK_ADDR = 0; int BLOCK_COUNT = 1; int BLOCK_OFFSET = 2; int LOB_ID = 3; } private interface LOB_IDS { int LOB_ID = 0; int LOB_LENGTH = 1; int LOB_USAGE_COUNT = 2; int LOB_TYPE = 3; } private interface GET_LOB_PART { int LOB_ID = 0; int BLOCK_OFFSET = 1; int BLOCK_LIMIT = 2; } private interface DIVIDE_BLOCK { int BLOCK_OFFSET = 0; int LOB_ID = 1; } private interface DELETE_BLOCKS { int LOB_ID = 0; int BLOCK_OFFSET = 1; int BLOCK_LIMIT = 2; int TX_ID = 3; } private interface ALLOC_BLOCKS { int BLOCK_COUNT = 0; int BLOCK_OFFSET = 1; int LOB_ID = 2; } private interface UPDATE_USAGE { int BLOCK_COUNT = 0; int LOB_ID = 1; } private interface UPDATE_LENGTH { int LOB_LENGTH = 0; int LOB_ID = 1; } //BLOCK_ADDR INT, BLOCK_COUNT INT, TX_ID BIGINT private static String initialiseBlocksSQL = "INSERT INTO SYSTEM_LOBS.BLOCKS VALUES(?,?,?)"; private static String getLobSQL = "SELECT * FROM SYSTEM_LOBS.LOB_IDS WHERE LOB_ID = ?"; private static String getLobPartSQL = "SELECT * FROM SYSTEM_LOBS.LOBS WHERE LOB_ID = ? AND BLOCK_OFFSET + BLOCK_COUNT > ? AND BLOCK_OFFSET < ? ORDER BY BLOCK_OFFSET"; private static String deleteLobPartCallSQL = "CALL SYSTEM_LOBS.DELETE_BLOCKS(?,?,?,?)"; private static String createLobSQL = "INSERT INTO SYSTEM_LOBS.LOB_IDS VALUES(?, ?, ?, ?)"; private static String updateLobLengthSQL = "UPDATE SYSTEM_LOBS.LOB_IDS SET LOB_LENGTH = ? WHERE LOB_ID = ?"; private static String createLobPartCallSQL = "CALL SYSTEM_LOBS.ALLOC_BLOCKS(?, ?, ?)"; private static String divideLobPartCallSQL = "CALL SYSTEM_LOBS.DIVIDE_BLOCK(?, ?)"; private static String getSpanningBlockSQL = "SELECT * FROM SYSTEM_LOBS.LOBS WHERE LOB_ID = ? AND ? > BLOCK_OFFSET AND ? < BLOCK_OFFSET + BLOCK_COUNT"; private static String updateLobUsageSQL = "UPDATE SYSTEM_LOBS.LOB_IDS SET LOB_USAGE_COUNT = ? WHERE LOB_ID = ?"; private static String getNextLobIdSQL = "VALUES NEXT VALUE FOR SYSTEM_LOBS.LOB_ID"; private static String deleteLobCallSQL = "CALL SYSTEM_LOBS.DELETE_LOB(?, ?)"; private static String deleteUnusedCallSQL = "CALL SYSTEM_LOBS.DELETE_UNUSED()"; private static String getLobCountSQL = "SELECT COUNT(*) FROM SYSTEM_LOBS.LOB_IDS"; public LobManager(Database database) { this.database = database; } synchronized public void createSchema() { sysLobSession = database.sessionManager.getSysLobSession(); InputStream fis = getClass().getResourceAsStream(resourceFileName); InputStreamReader reader = null; try { reader = new InputStreamReader(fis, "ISO-8859-1"); } catch (Exception e) {} LineNumberReader lineReader = new LineNumberReader(reader); LineGroupReader lg = new LineGroupReader(lineReader, starters); HashMappedList map = lg.getAsMap(); lg.close(); String sql = (String) map.get("/*lob_schema_definition*/"); Statement statement = sysLobSession.compileStatement(sql, ResultProperties.defaultPropsValue); Result result = statement.execute(sysLobSession); if (result.isError()) { throw result.getException(); } HsqlName name = database.schemaManager.getSchemaHsqlName("SYSTEM_LOBS"); Table table = database.schemaManager.getTable(sysLobSession, "BLOCKS", "SYSTEM_LOBS"); getLob = sysLobSession.compileStatement(getLobSQL, ResultProperties.defaultPropsValue); getLobPart = sysLobSession.compileStatement(getLobPartSQL, ResultProperties.defaultPropsValue); createLob = sysLobSession.compileStatement(createLobSQL, ResultProperties.defaultPropsValue); createLobPartCall = sysLobSession.compileStatement(createLobPartCallSQL, ResultProperties.defaultPropsValue); divideLobPartCall = sysLobSession.compileStatement(divideLobPartCallSQL, ResultProperties.defaultPropsValue); deleteLobCall = sysLobSession.compileStatement(deleteLobCallSQL, ResultProperties.defaultPropsValue); deleteLobPartCall = sysLobSession.compileStatement(deleteLobPartCallSQL, ResultProperties.defaultPropsValue); updateLobLength = sysLobSession.compileStatement(updateLobLengthSQL, ResultProperties.defaultPropsValue); updateLobUsage = sysLobSession.compileStatement(updateLobUsageSQL, ResultProperties.defaultPropsValue); getNextLobId = sysLobSession.compileStatement(getNextLobIdSQL, ResultProperties.defaultPropsValue); deleteUnusedLobs = sysLobSession.compileStatement(deleteUnusedCallSQL, ResultProperties.defaultPropsValue); getLobCount = sysLobSession.compileStatement(getLobCountSQL, ResultProperties.defaultPropsValue); } synchronized public void initialiseLobSpace() { Statement statement = sysLobSession.compileStatement(initialiseBlocksSQL, ResultProperties.defaultPropsValue); Object[] params = new Object[3]; params[0] = ValuePool.INTEGER_0; params[1] = ValuePool.getInt(totalBlockLimitCount); params[2] = ValuePool.getLong(0); sysLobSession.executeCompiledStatement(statement, params); } synchronized public void open() { if (lobStore != null) { return; } lobBlockSize = database.logger.getLobBlockSize(); if (database.getType() == DatabaseURL.S_RES) { lobStore = new LobStoreInJar(database, lobBlockSize); } else if (database.getType() == DatabaseURL.S_FILE) { lobStore = new LobStoreRAFile(database, lobBlockSize); } else { lobStore = new LobStoreMem(lobBlockSize); } } synchronized public void close() { lobStore.close(); } LobStore getLobStore() { if (lobStore == null) { open(); } return lobStore; } // private long getNewLobID() { Result result = getNextLobId.execute(sysLobSession); if (result.isError()) { return 0; } RowSetNavigator navigator = result.getNavigator(); boolean next = navigator.next(); if (!next) { navigator.close(); return 0; } Object[] data = navigator.getCurrent(); return ((Long) data[0]).longValue(); } private Object[] getLobHeader(long lobID) { ResultMetaData meta = getLob.getParametersMetaData(); Object params[] = new Object[meta.getColumnCount()]; params[0] = ValuePool.getLong(lobID); sysLobSession.sessionContext.pushDynamicArguments(params); Result result = getLob.execute(sysLobSession); sysLobSession.sessionContext.pop(); if (result.isError()) { return null; } RowSetNavigator navigator = result.getNavigator(); boolean next = navigator.next(); if (!next) { navigator.close(); return null; } Object[] data = navigator.getCurrent(); return data; } synchronized public BlobData getBlob(long lobID) { Object[] data = getLobHeader(lobID); if (data == null) { return null; } BlobData blob = new BlobDataID(lobID); return blob; } synchronized public ClobData getClob(long lobID) { Object[] data = getLobHeader(lobID); if (data == null) { return null; } ClobData clob = new ClobDataID(lobID); return clob; } synchronized public long createBlob(long length) { long lobID = getNewLobID(); ResultMetaData meta = createLob.getParametersMetaData(); Object params[] = new Object[meta.getColumnCount()]; params[LOB_IDS.LOB_ID] = ValuePool.getLong(lobID); params[LOB_IDS.LOB_LENGTH] = ValuePool.getLong(length); params[LOB_IDS.LOB_USAGE_COUNT] = ValuePool.INTEGER_0; params[LOB_IDS.LOB_TYPE] = ValuePool.getInt(Types.SQL_BLOB); Result result = sysLobSession.executeCompiledStatement(createLob, params); return lobID; } synchronized public long createClob(long length) { long lobID = getNewLobID(); ResultMetaData meta = createLob.getParametersMetaData(); Object params[] = new Object[meta.getColumnCount()]; params[LOB_IDS.LOB_ID] = ValuePool.getLong(lobID); params[LOB_IDS.LOB_LENGTH] = ValuePool.getLong(length); params[LOB_IDS.LOB_USAGE_COUNT] = ValuePool.INTEGER_0; params[LOB_IDS.LOB_TYPE] = ValuePool.getInt(Types.SQL_CLOB); Result result = sysLobSession.executeCompiledStatement(createLob, params); return lobID; } synchronized public Result deleteLob(long lobID) { ResultMetaData meta = deleteLobCall.getParametersMetaData(); Object params[] = new Object[meta.getColumnCount()]; params[0] = ValuePool.getLong(lobID); params[1] = ValuePool.getLong(0); Result result = sysLobSession.executeCompiledStatement(deleteLobCall, params); return result; } synchronized public Result deleteUnusedLobs() { Result result = sysLobSession.executeCompiledStatement(deleteUnusedLobs, ValuePool.emptyObjectArray); deletedLobCount = 0; return result; } synchronized public Result getLength(long lobID) { try { Object[] data = getLobHeader(lobID); if (data == null) { throw Error.error(ErrorCode.X_0F502); } long length = ((Long) data[LOB_IDS.LOB_LENGTH]).longValue(); int type = ((Integer) data[LOB_IDS.LOB_TYPE]).intValue(); return ResultLob.newLobSetResponse(lobID, length); } catch (HsqlException e) { return Result.newErrorResult(e); } } synchronized public int compare(BlobData a, byte[] b) { Object[] data = getLobHeader(a.getId()); long aLength = ((Long) data[LOB_IDS.LOB_LENGTH]).longValue(); int[][] aAddresses = getBlockAddresses(a.getId(), 0, Integer.MAX_VALUE); int aIndex = 0; int bOffset = 0; int aOffset = 0; while (true) { int aBlockOffset = aAddresses[aIndex][LOBS.BLOCK_ADDR] + aOffset; byte[] aBytes = getLobStore().getBlockBytes(aBlockOffset, 1); for (int i = 0; i < aBytes.length; i++) { if (bOffset + i >= b.length) { if (aLength == b.length) { return 0; } return 1; } if (aBytes[i] == b[bOffset + i]) { continue; } return (((int) aBytes[i]) & 0xff) > (((int) b[bOffset + i]) & 0xff) ? 1 : -1; } aOffset++; bOffset += lobBlockSize; if (aOffset == aAddresses[aIndex][LOBS.BLOCK_COUNT]) { aOffset = 0; aIndex++; } if (aIndex == aAddresses.length) { break; } } return -1; } synchronized public int compare(BlobData a, BlobData b) { if (a.getId() == b.getId()) { return 0; } Object[] data = getLobHeader(a.getId()); // abnormal case if (data == null) { return 1; } long lengthA = ((Long) data[LOB_IDS.LOB_LENGTH]).longValue(); data = getLobHeader(b.getId()); // abnormal case if (data == null) { return -1; } long lengthB = ((Long) data[LOB_IDS.LOB_LENGTH]).longValue(); if (lengthA > lengthB) { return 1; } if (lengthA < lengthB) { return -1; } return compareBytes(a.getId(), b.getId()); } // todo - implement as compareText() synchronized public int compare(ClobData a, String b) { Object[] data = getLobHeader(a.getId()); long aLength = ((Long) data[LOB_IDS.LOB_LENGTH]).longValue(); int[][] aAddresses = getBlockAddresses(a.getId(), 0, Integer.MAX_VALUE); int aIndex = 0; int bOffset = 0; int aOffset = 0; while (true) { int aBlockOffset = aAddresses[aIndex][LOBS.BLOCK_ADDR] + aOffset; byte[] aBytes = getLobStore().getBlockBytes(aBlockOffset, 1); long aLimit = aLength - (aAddresses[aIndex][LOBS.BLOCK_OFFSET] + aOffset) * lobBlockSize / 2; if (aLimit > lobBlockSize / 2) { aLimit = lobBlockSize / 2; } String aString = new String(ArrayUtil.byteArrayToChars(aBytes), 0, (int) aLimit); int bLimit = b.length() - bOffset; if (bLimit > lobBlockSize / 2) { bLimit = lobBlockSize / 2; } String bString = b.substring(bOffset, bOffset + bLimit); int diff = database.collation.compare(aString, bString); if (diff != 0) { return diff; } aOffset++; bOffset += lobBlockSize / 2; if (aOffset == aAddresses[aIndex][LOBS.BLOCK_COUNT]) { aOffset = 0; aIndex++; } if (aIndex == aAddresses.length) { break; } } return 0; } synchronized public int compare(ClobData a, ClobData b) { if (a.getId() == b.getId()) { return 0; } return compareText(a.getId(), b.getId()); } synchronized int compareBytes(long aID, long bID) { int[][] aAddresses = getBlockAddresses(aID, 0, Integer.MAX_VALUE); int[][] bAddresses = getBlockAddresses(bID, 0, Integer.MAX_VALUE); int aIndex = 0; int bIndex = 0; int aOffset = 0; int bOffset = 0; while (true) { int aBlockOffset = aAddresses[aIndex][LOBS.BLOCK_ADDR] + aOffset; int bBlockOffset = bAddresses[bIndex][LOBS.BLOCK_ADDR] + bOffset; byte[] aBytes = getLobStore().getBlockBytes(aBlockOffset, 1); byte[] bBytes = getLobStore().getBlockBytes(bBlockOffset, 1); for (int i = 0; i < aBytes.length; i++) { if (aBytes[i] == bBytes[i]) { continue; } return (((int) aBytes[i]) & 0xff) > (((int) bBytes[i]) & 0xff) ? 1 : -1; } aOffset++; bOffset++; if (aOffset == aAddresses[aIndex][LOBS.BLOCK_COUNT]) { aOffset = 0; aIndex++; } if (bOffset == bAddresses[bIndex][LOBS.BLOCK_COUNT]) { bOffset = 0; bIndex++; } if (aIndex == aAddresses.length) { break; } } return 0; } /** @todo - word-separator and end block zero issues */ synchronized int compareText(long aID, long bID) { Object[] data = getLobHeader(aID); long aLength = ((Long) data[LOB_IDS.LOB_LENGTH]).longValue(); data = getLobHeader(bID); long bLength = ((Long) data[LOB_IDS.LOB_LENGTH]).longValue(); int[][] aAddresses = getBlockAddresses(aID, 0, Integer.MAX_VALUE); int[][] bAddresses = getBlockAddresses(bID, 0, Integer.MAX_VALUE); int aIndex = 0; int bIndex = 0; int aOffset = 0; int bOffset = 0; while (true) { int aBlockOffset = aAddresses[aIndex][LOBS.BLOCK_ADDR] + aOffset; int bBlockOffset = bAddresses[bIndex][LOBS.BLOCK_ADDR] + bOffset; byte[] aBytes = getLobStore().getBlockBytes(aBlockOffset, 1); byte[] bBytes = getLobStore().getBlockBytes(bBlockOffset, 1); long aLimit = aLength - (aAddresses[aIndex][LOBS.BLOCK_OFFSET] + aOffset) * lobBlockSize / 2; if (aLimit > lobBlockSize / 2) { aLimit = lobBlockSize / 2; } long bLimit = bLength - (bAddresses[bIndex][LOBS.BLOCK_OFFSET] + bOffset) * lobBlockSize / 2; if (bLimit > lobBlockSize / 2) { bLimit = lobBlockSize / 2; } String aString = new String(ArrayUtil.byteArrayToChars(aBytes), 0, (int) aLimit); String bString = new String(ArrayUtil.byteArrayToChars(bBytes), 0, (int) bLimit); int diff = database.collation.compare(aString, bString); if (diff != 0) { return diff; } aOffset++; bOffset++; if (aOffset == aAddresses[aIndex][LOBS.BLOCK_COUNT]) { aOffset = 0; aIndex++; } if (bOffset == bAddresses[bIndex][LOBS.BLOCK_COUNT]) { bOffset = 0; bIndex++; } if (aIndex == aAddresses.length) { break; } } return 0; } /** * Used for SUBSTRING */ synchronized public Result getLob(long lobID, long offset, long length) { throw Error.runtimeError(ErrorCode.U_S0500, "LobManager"); } synchronized public Result createDuplicateLob(long lobID) { Object[] data = getLobHeader(lobID); if (data == null) { return Result.newErrorResult(Error.error(ErrorCode.X_0F502)); } long newLobID = getNewLobID(); Object params[] = new Object[data.length]; params[LOB_IDS.LOB_ID] = ValuePool.getLong(newLobID); params[1] = data[1]; params[2] = data[2]; params[3] = data[3]; Result result = sysLobSession.executeCompiledStatement(createLob, params); if (result.isError()) { return result; } long length = ((Long) data[1]).longValue(); long byteLength = length; int lobType = ((Integer) data[1]).intValue(); if (lobType == Types.SQL_CLOB) { byteLength *= 2; } int newBlockCount = (int) byteLength / lobBlockSize; if (byteLength % lobBlockSize != 0) { newBlockCount++; } createBlockAddresses(newLobID, 0, newBlockCount); // copy the contents int[][] sourceBlocks = getBlockAddresses(lobID, 0, Integer.MAX_VALUE); int[][] targetBlocks = getBlockAddresses(newLobID, 0, Integer.MAX_VALUE); try { copyBlockSet(sourceBlocks, targetBlocks); } catch (HsqlException e) { return Result.newErrorResult(e); } return ResultLob.newLobSetResponse(newLobID, length); } private void copyBlockSet(int[][] source, int[][] target) { int sourceIndex = 0; int targetIndex = 0; while (true) { int sourceOffset = source[sourceIndex][LOBS.BLOCK_OFFSET] + sourceIndex; int targetOffset = target[targetIndex][LOBS.BLOCK_OFFSET] + targetIndex; byte[] bytes = getLobStore().getBlockBytes(sourceOffset, 1); getLobStore().setBlockBytes(bytes, targetOffset, 1); sourceOffset++; targetOffset++; if (sourceOffset == source[sourceIndex][LOBS.BLOCK_COUNT]) { sourceOffset = 0; sourceIndex++; } if (targetOffset == target[sourceIndex][LOBS.BLOCK_COUNT]) { targetOffset = 0; targetIndex++; } if (sourceIndex == source.length) { break; } } } synchronized public Result getChars(long lobID, long offset, int length) { Result result = getBytes(lobID, offset * 2, length * 2); if (result.isError()) { return result; } byte[] bytes = ((ResultLob) result).getByteArray(); char[] chars = ArrayUtil.byteArrayToChars(bytes); /* HsqlByteArrayInputStream be = new HsqlByteArrayInputStream(bytes); char[] chars = new char[bytes.length / 2]; try { for (int i = 0; i < chars.length; i++) { chars[i] = be.readChar(); } } catch (Exception e) { return Result.newErrorResult(e); } */ return ResultLob.newLobGetCharsResponse(lobID, offset, chars); } synchronized public Result getBytes(long lobID, long offset, int length) { int blockOffset = (int) (offset / lobBlockSize); int byteBlockOffset = (int) (offset % lobBlockSize); int blockLimit = (int) ((offset + length) / lobBlockSize); int byteLimitOffset = (int) ((offset + length) % lobBlockSize); if (byteLimitOffset == 0) { byteLimitOffset = lobBlockSize; } else { blockLimit++; } int dataBytesPosition = 0; byte[] dataBytes = new byte[length]; int[][] blockAddresses = getBlockAddresses(lobID, blockOffset, blockLimit); if (blockAddresses.length == 0) { return Result.newErrorResult(Error.error(ErrorCode.X_0F502)); } // int i = 0; int blockCount = blockAddresses[i][LOBS.BLOCK_COUNT] + blockAddresses[i][LOBS.BLOCK_OFFSET] - blockOffset; if (blockAddresses[i][LOBS.BLOCK_COUNT] + blockAddresses[i][LOBS.BLOCK_OFFSET] > blockLimit) { blockCount -= (blockAddresses[i][LOBS.BLOCK_COUNT] + blockAddresses[i][LOBS.BLOCK_OFFSET] - blockLimit); } byte[] bytes; try { bytes = getLobStore().getBlockBytes(blockAddresses[i][LOBS.BLOCK_ADDR] + blockOffset, blockCount); } catch (HsqlException e) { return Result.newErrorResult(e); } int subLength = lobBlockSize * blockCount - byteBlockOffset; if (subLength > length) { subLength = length; } System.arraycopy(bytes, byteBlockOffset, dataBytes, dataBytesPosition, subLength); dataBytesPosition += subLength; i++; for (; i < blockAddresses.length && dataBytesPosition < length; i++) { blockCount = blockAddresses[i][LOBS.BLOCK_COUNT]; if (blockAddresses[i][LOBS.BLOCK_COUNT] + blockAddresses[i][LOBS.BLOCK_OFFSET] > blockLimit) { blockCount -= (blockAddresses[i][LOBS.BLOCK_COUNT] + blockAddresses[i][LOBS.BLOCK_OFFSET] - blockLimit); } try { bytes = getLobStore().getBlockBytes( blockAddresses[i][LOBS.BLOCK_ADDR], blockCount); } catch (HsqlException e) { return Result.newErrorResult(e); } subLength = lobBlockSize * blockCount; if (subLength > length - dataBytesPosition) { subLength = length - dataBytesPosition; } System.arraycopy(bytes, 0, dataBytes, dataBytesPosition, subLength); dataBytesPosition += subLength; } return ResultLob.newLobGetBytesResponse(lobID, offset, dataBytes); } synchronized public Result setBytesBA(long lobID, byte[] dataBytes, long offset, int length) { int blockOffset = (int) (offset / lobBlockSize); int byteBlockOffset = (int) (offset % lobBlockSize); int blockLimit = (int) ((offset + length) / lobBlockSize); int byteLimitOffset = (int) ((offset + length) % lobBlockSize); if (byteLimitOffset == 0) { byteLimitOffset = lobBlockSize; } else { blockLimit++; } int[][] blockAddresses = getBlockAddresses(lobID, blockOffset, blockLimit); byte[] newBytes = new byte[(blockLimit - blockOffset) * lobBlockSize]; if (blockAddresses.length > 0) { int blockAddress = blockAddresses[0][LOBS.BLOCK_ADDR] + (blockOffset - blockAddresses[0][LOBS.BLOCK_OFFSET]); try { byte[] block = getLobStore().getBlockBytes(blockAddress, 1); System.arraycopy(block, 0, newBytes, 0, lobBlockSize); if (blockAddresses.length > 1) { blockAddress = blockAddresses[blockAddresses.length - 1][LOBS.BLOCK_ADDR] + (blockLimit - blockAddresses[blockAddresses.length - 1][LOBS.BLOCK_OFFSET] - 1); block = getLobStore().getBlockBytes(blockAddress, 1); System.arraycopy(block, 0, newBytes, blockLimit - blockOffset - 1, lobBlockSize); } else if (blockLimit - blockOffset > 1) { blockAddress = blockAddresses[0][LOBS.BLOCK_ADDR] + (blockLimit - blockAddresses[0][LOBS.BLOCK_OFFSET] - 1); block = getLobStore().getBlockBytes(blockAddress, 1); System.arraycopy(block, 0, newBytes, (blockLimit - blockOffset - 1) * lobBlockSize, lobBlockSize); } } catch (HsqlException e) { return Result.newErrorResult(e); } // should turn into SP divideBlockAddresses(lobID, blockOffset); divideBlockAddresses(lobID, blockLimit); deleteBlockAddresses(lobID, blockOffset, blockLimit); } createBlockAddresses(lobID, blockOffset, blockLimit - blockOffset); System.arraycopy(dataBytes, 0, newBytes, byteBlockOffset, length); blockAddresses = getBlockAddresses(lobID, blockOffset, blockLimit); // try { for (int i = 0; i < blockAddresses.length; i++) { getLobStore().setBlockBytes(newBytes, blockAddresses[i][0], blockAddresses[i][1]); } } catch (HsqlException e) { return Result.newErrorResult(e); } return ResultLob.newLobSetResponse(lobID, 0); } private Result setBytesIS(long lobID, InputStream inputStream, long length) { int blockLimit = (int) (length / lobBlockSize); int byteLimitOffset = (int) (length % lobBlockSize); if (byteLimitOffset == 0) { byteLimitOffset = lobBlockSize; } else { blockLimit++; } createBlockAddresses(lobID, 0, blockLimit); int[][] blockAddresses = getBlockAddresses(lobID, 0, blockLimit); byte[] dataBytes = new byte[lobBlockSize]; for (int i = 0; i < blockAddresses.length; i++) { for (int j = 0; j < blockAddresses[i][LOBS.BLOCK_COUNT]; j++) { int localLength = lobBlockSize; if (i == blockAddresses.length - 1 && j == blockAddresses[i][LOBS.BLOCK_COUNT] - 1) { localLength = byteLimitOffset; // todo -- use block op for (int k = localLength; k < lobBlockSize; k++) { dataBytes[k] = 0; } } try { int count = 0; while (localLength > 0) { int read = inputStream.read(dataBytes, count, localLength); if (read == -1) { return Result.newErrorResult(new EOFException()); } localLength -= read; count += read; } // read more } catch (IOException e) { // deallocate return Result.newErrorResult(e); } try { getLobStore().setBlockBytes( dataBytes, blockAddresses[i][LOBS.BLOCK_ADDR] + j, 1); } catch (HsqlException e) { return Result.newErrorResult(e); } } } return ResultLob.newLobSetResponse(lobID, 0); } synchronized public Result setBytes(long lobID, byte[] dataBytes, long offset) { if (dataBytes.length == 0) { return ResultLob.newLobSetResponse(lobID, 0); } Object[] data = getLobHeader(lobID); if (data == null) { return Result.newErrorResult(Error.error(ErrorCode.X_0F502)); } long length = ((Long) data[LOB_IDS.LOB_LENGTH]).longValue(); Result result = setBytesBA(lobID, dataBytes, offset, dataBytes.length); if (offset + dataBytes.length > length) { setLength(lobID, offset + dataBytes.length); } return result; } synchronized public Result setBytesForNewBlob(long lobID, InputStream inputStream, long length) { if (length == 0) { return ResultLob.newLobSetResponse(lobID, 0); } Result result = setBytesIS(lobID, inputStream, length); return result; } synchronized public Result setChars(long lobID, long offset, char[] chars) { if (chars.length == 0) { return ResultLob.newLobSetResponse(lobID, 0); } Object[] data = getLobHeader(lobID); if (data == null) { return Result.newErrorResult(Error.error(ErrorCode.X_0F502)); } long length = ((Long) data[LOB_IDS.LOB_LENGTH]).longValue(); byte[] bytes = ArrayUtil.charArrayToBytes(chars); /* HsqlByteArrayOutputStream os = new HsqlByteArrayOutputStream(chars.length * 2); os.write(chars, 0, chars.length); byte[] bytes = os.getBuffer(); */ Result result = setBytesBA(lobID, bytes, offset * 2, chars.length * 2); if (result.isError()) { return result; } if (offset + chars.length > length) { result = setLength(lobID, offset + chars.length); if (result.isError()) { return result; } } return ResultLob.newLobSetResponse(lobID, 0); } synchronized public Result setCharsForNewClob(long lobID, InputStream inputStream, long length) { if (length == 0) { return ResultLob.newLobSetResponse(lobID, 0); } Result result = setBytesIS(lobID, inputStream, length * 2); if (result.isError()) { return result; } return ResultLob.newLobSetResponse(lobID, 0); } synchronized public Result truncate(long lobID, long offset) { Object[] data = getLobHeader(lobID); if (data == null) { return Result.newErrorResult(Error.error(ErrorCode.X_0F502)); } /** @todo 1.9.0 - double offset for clob */ long length = ((Long) data[LOB_IDS.LOB_LENGTH]).longValue(); int blockOffset = (int) (offset / lobBlockSize); ResultMetaData meta = deleteLobPartCall.getParametersMetaData(); Object params[] = new Object[meta.getColumnCount()]; params[DELETE_BLOCKS.LOB_ID] = ValuePool.getLong(lobID); params[DELETE_BLOCKS.BLOCK_OFFSET] = new Integer(blockOffset); params[DELETE_BLOCKS.BLOCK_LIMIT] = new Integer(Integer.MAX_VALUE); params[DELETE_BLOCKS.TX_ID] = ValuePool.getLong(sysLobSession.getTransactionTimestamp()); Result result = sysLobSession.executeCompiledStatement(deleteLobPartCall, params); setLength(lobID, offset); return ResultLob.newLobTruncateResponse(lobID); } synchronized Result setLength(long lobID, long length) { ResultMetaData meta = updateLobLength.getParametersMetaData(); Object params[] = new Object[meta.getColumnCount()]; params[UPDATE_LENGTH.LOB_LENGTH] = ValuePool.getLong(length); params[UPDATE_LENGTH.LOB_ID] = ValuePool.getLong(lobID); Result result = sysLobSession.executeCompiledStatement(updateLobLength, params); return result; } synchronized public Result adjustUsageCount(long lobID, int delta) { Object[] data = getLobHeader(lobID); int count = ((Number) data[LOB_IDS.LOB_USAGE_COUNT]).intValue(); if (count + delta == 0) { deletedLobCount++; } ResultMetaData meta = updateLobUsage.getParametersMetaData(); Object params[] = new Object[meta.getColumnCount()]; params[UPDATE_USAGE.BLOCK_COUNT] = ValuePool.getInt(count + delta); params[UPDATE_USAGE.LOB_ID] = ValuePool.getLong(lobID); sysLobSession.sessionContext.pushDynamicArguments(params); Result result = updateLobUsage.execute(sysLobSession); sysLobSession.sessionContext.pop(); return result; } private int[][] getBlockAddresses(long lobID, int offset, int limit) { ResultMetaData meta = getLobPart.getParametersMetaData(); Object params[] = new Object[meta.getColumnCount()]; params[GET_LOB_PART.LOB_ID] = ValuePool.getLong(lobID); params[GET_LOB_PART.BLOCK_OFFSET] = ValuePool.getInt(offset); params[GET_LOB_PART.BLOCK_LIMIT] = ValuePool.getInt(limit); sysLobSession.sessionContext.pushDynamicArguments(params); Result result = getLobPart.execute(sysLobSession); sysLobSession.sessionContext.pop(); RowSetNavigator navigator = result.getNavigator(); int size = navigator.getSize(); int[][] blocks = new int[size][3]; for (int i = 0; i < size; i++) { navigator.absolute(i); Object[] data = navigator.getCurrent(); blocks[i][LOBS.BLOCK_ADDR] = ((Integer) data[LOBS.BLOCK_ADDR]).intValue(); blocks[i][LOBS.BLOCK_COUNT] = ((Integer) data[LOBS.BLOCK_COUNT]).intValue(); blocks[i][LOBS.BLOCK_OFFSET] = ((Integer) data[LOBS.BLOCK_OFFSET]).intValue(); } navigator.close(); return blocks; } private void deleteBlockAddresses(long lobID, int offset, int limit) { ResultMetaData meta = deleteLobPartCall.getParametersMetaData(); Object params[] = new Object[meta.getColumnCount()]; params[DELETE_BLOCKS.LOB_ID] = ValuePool.getLong(lobID); params[DELETE_BLOCKS.BLOCK_OFFSET] = ValuePool.getInt(offset); params[DELETE_BLOCKS.BLOCK_LIMIT] = ValuePool.getInt(limit); params[DELETE_BLOCKS.TX_ID] = ValuePool.getLong(sysLobSession.getTransactionTimestamp()); Result result = sysLobSession.executeCompiledStatement(deleteLobPartCall, params); } private void divideBlockAddresses(long lobID, int offset) { ResultMetaData meta = divideLobPartCall.getParametersMetaData(); Object params[] = new Object[meta.getColumnCount()]; params[DIVIDE_BLOCK.BLOCK_OFFSET] = ValuePool.getInt(offset); params[DIVIDE_BLOCK.LOB_ID] = ValuePool.getLong(lobID); Result result = sysLobSession.executeCompiledStatement(divideLobPartCall, params); } private void createBlockAddresses(long lobID, int offset, int count) { ResultMetaData meta = createLobPartCall.getParametersMetaData(); Object params[] = new Object[meta.getColumnCount()]; params[ALLOC_BLOCKS.BLOCK_COUNT] = ValuePool.getInt(count); params[ALLOC_BLOCKS.BLOCK_OFFSET] = ValuePool.getInt(offset); params[ALLOC_BLOCKS.LOB_ID] = ValuePool.getLong(lobID); Result result = sysLobSession.executeCompiledStatement(createLobPartCall, params); } synchronized public int getLobCount() { sysLobSession.sessionContext.pushDynamicArguments(new Object[]{}); Result result = getLobCount.execute(sysLobSession); sysLobSession.sessionContext.pop(); RowSetNavigator navigator = result.getNavigator(); boolean next = navigator.next(); if (!next) { navigator.close(); return 0; } Object[] data = navigator.getCurrent(); return ((Number) data[0]).intValue(); } }