Package org.hsqldb.persist

Source Code of org.hsqldb.persist.LobManager

/* 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();
    }
}
TOP

Related Classes of org.hsqldb.persist.LobManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.