
org.hsqldb.jdbc.JDBCBlobFile Maven / Gradle / Ivy
Show all versions of hsqldb Show documentation
/* Copyright (c) 2001-2017, 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.jdbc;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.io.Writer;
import java.sql.Blob;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.lib.CountdownInputStream;
import org.hsqldb.lib.FileUtil;
import org.hsqldb.lib.FrameworkLogger;
import org.hsqldb.lib.InOutUtil;
import org.hsqldb.lib.KMPSearchAlgorithm;
/**
*
*
* HSQLDB-Specific Information:
*
* Starting with 2.1, in addition to HSQLDB driver support for both client-side
* in-memory and remote SQL CLOB data implementations, this class is provided
* to expose efficient, relatively high-performance BLOB operations over client
* accessible files.
*
* Design Notes
*
* Although it is possible to implement a transactional version of this class,
* the present implementation directly propagates changes to the underlying
* file such that changes become visible as soon as they are either
* implicitly or explicitly flushed to disk.
*
*
*
*
* @author campbell-burnet@users
* @version 2.4.0
* @since HSQLDB 2.1
*/
public class JDBCBlobFile implements java.sql.Blob {
private static final FrameworkLogger LOG = FrameworkLogger.getLog(
JDBCBlobFile.class);
/**
* Returns the number of bytes in the BLOB
value
* designated by this Blob
object.
* @return length of the BLOB
in bytes
* @exception SQLException if there is an error accessing the
* length of the BLOB
* @exception java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support
* this method
* @since JDK 1.2
*/
public long length() throws SQLException {
checkClosed();
try {
return m_file.length();
} catch (SecurityException e) {
throw JDBCUtil.sqlException(e);
}
}
/**
* Retrieves all or part of the BLOB
* value that this Blob
object represents, as an array of
* bytes. This byte
array contains up to length
* consecutive bytes starting at position pos
.
*
* @param pos the ordinal position of the first byte in the
* BLOB
value to be extracted; the first byte is at
* position 1
* @param length the number of consecutive bytes to be copied; the value
* for length must be 0 or greater
* @return a byte array containing up to length
* consecutive bytes from the BLOB
value designated
* by this Blob
object, starting with the
* byte at position pos
* @exception SQLException if there is an error accessing the
* BLOB
value; if pos is less than 1 or length is
* less than 0
* @exception java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support
* this method
* @see #setBytes
* @since JDK 1.2
*/
public byte[] getBytes(final long pos,
final int length) throws SQLException {
InputStream is = null;
ByteArrayOutputStream baos = null;
final int initialBufferSize =
Math.min(InOutUtil.DEFAULT_COPY_BUFFER_SIZE, length);
try {
is = getBinaryStream(pos, length);
baos = new ByteArrayOutputStream(initialBufferSize);
InOutUtil.copy(is, baos, length);
} catch (IOException ex) {
throw JDBCUtil.sqlException(ex);
} finally {
closeSafely(is);
}
return baos.toByteArray();
}
/**
* Retrieves the BLOB
value designated by this
* Blob
instance as a stream.
*
* @return a stream containing the BLOB
data
* @exception SQLException if there is an error accessing the
* BLOB
value
* @exception java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support
* this method
* @see #setBinaryStream
* @since JDK 1.2
*/
public InputStream getBinaryStream() throws SQLException {
return getBinaryStream(1, Long.MAX_VALUE);
}
/**
* Retrieves the byte position at which the specified byte array
* pattern
begins within the BLOB
* value that this Blob
object represents. The
* search for pattern
begins at position
* start
.
*
* @param pattern the byte array for which to search
* @param start the position at which to begin searching; the
* first position is 1
* @return the position at which the pattern appears, else -1
* @exception SQLException if there is an error accessing the
* BLOB
or if start is less than 1
* @exception java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support
* this method
* @since JDK 1.2
*/
public long position(final byte[] pattern,
final long start) throws SQLException {
if (start < 1) {
throw JDBCUtil.outOfRangeArgument("start: " + start);
} else if (pattern == null || pattern.length == 0) {
return -1L;
}
final long length = this.length();
if (start > length || pattern.length > length || start > length
- pattern.length) {
return -1;
}
return position0(pattern, start);
}
private long position0(final byte[] pattern, final long start) throws
SQLException {
InputStream is = null;
try {
is = getBinaryStream(start, Long.MAX_VALUE);
//@todo maybe single-byte encoding reader
// and java.util.Scanner.findWithinHorizon.
// Need to do comparative benchmark and unit
// tests first.
final long matchOffset = KMPSearchAlgorithm.search(is, pattern,
KMPSearchAlgorithm.computeTable(pattern));
return (matchOffset == -1) ? -1
: start + matchOffset;
} catch (IOException ex) {
throw JDBCUtil.sqlException(ex);
} finally {
closeSafely(is);
}
}
/**
* Retrieves the byte position in the BLOB
value
* designated by this Blob
object at which
* pattern
begins. The search begins at position
* start
.
*
* @param pattern the Blob
object designating
* the BLOB
value for which to search
* @param start 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
* @exception SQLException if there is an error accessing the
* BLOB
value or if start is less than 1
* @exception java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support
* this method
* @since JDK 1.2
*/
public long position(final Blob pattern,
final long start) throws SQLException {
if (start < 1) {
throw JDBCUtil.outOfRangeArgument("start: " + start);
}
if (pattern == null) {
return -1;
}
final long patternLength = pattern.length();
if (patternLength == 0) {
return -1;
} else if (patternLength > Integer.MAX_VALUE) {
throw JDBCUtil.outOfRangeArgument("pattern.length(): "
+ patternLength);
}
final long length = this.length();
if (start > length || patternLength > length || start > length
- patternLength) {
return -1;
}
final byte[] bytePattern = (pattern instanceof JDBCBlob)
? ((JDBCBlob) pattern).data()
: pattern.getBytes(1L, (int) patternLength);
return position0(bytePattern, start);
}
// -------------------------- JDBC 3.0 -----------------------------------
/**
* Writes the given array of bytes to the BLOB
value that
* this Blob
object represents, starting at position
* pos
, and returns the number of bytes written.
* The array of bytes will overwrite the existing bytes
* in the Blob
object starting at the position
* pos
. If the end of the Blob
value is reached
* while writing the array of bytes, then the length of the Blob
* value will be increased to accommodate the extra bytes.
*
* Note: If the value specified for pos
* is greater then the length+1 of the BLOB
value then the
* behavior is undefined. Some JDBC drivers may throw a
* SQLException
while other drivers may support this
* operation.
*
*
*
* HSQLDB-Specific Information:
*
* This operation affects only the content of the underlying file; it has no
* effect upon a value stored in a database. To propagate the updated
* Blob value to a database, it is required to supply the Blob instance to
* an updating or inserting setXXX method of a Prepared or Callable
* Statement, or to supply the Blob instance to an updateXXX method of an
* updatable ResultSet.
*
*
*
*
* @param pos the position in the BLOB
object at which
* to start writing; the first position is 1
* @param bytes the array of bytes to be written to the BLOB
* value that this Blob
object represents
* @return the number of bytes written
* @exception SQLException if there is an error accessing the
* BLOB
value or if pos is less than 1
* @exception java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support
* this method
* @see #getBytes
* @since JDK 1.4
*/
public int setBytes(final long pos,
final byte[] bytes) throws SQLException {
return setBytes(pos, bytes, 0, bytes == null ? 0
: bytes.length);
}
/**
* Writes all or part of the given byte
array to the
* BLOB
value that this Blob
object represents
* and returns the number of bytes written.
* Writing starts at position pos
in the BLOB
* value; len
bytes from the given byte array are written.
* The array of bytes will overwrite the existing bytes
* in the Blob
object starting at the position
* pos
. If the end of the Blob
value is reached
* while writing the array of bytes, then the length of the Blob
* value will be increased to accommodate the extra bytes.
*
* Note: If the value specified for pos
* is greater then the length+1 of the BLOB
value then the
* behavior is undefined. Some JDBC drivers may throw a
* SQLException
while other drivers may support this
* operation.
*
*
*
* HSQLDB-Specific Information:
*
* This operation affects only the content of the underlying file; it has no
* effect upon a value stored in a database. To propagate the updated
* Blob value to a database, it is required to supply the Blob instance to
* an updating or inserting setXXX method of a Prepared or Callable
* Statement, or to supply the Blob instance to an updateXXX method of an
* updatable ResultSet.
*
*
*
*
* @param pos the position in the BLOB
object at which
* to start writing; the first position is 1
* @param bytes the array of bytes to be written to this BLOB
* object
* @param offset the offset into the array bytes
at which
* to start reading the bytes to be set
* @param len the number of bytes to be written to the BLOB
* value from the array of bytes bytes
* @return the number of bytes written
* @exception SQLException if there is an error accessing the
* BLOB
value or if pos is less than 1
* @exception java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support
* this method
* @see #getBytes
* @since JDK 1.4
*/
public int setBytes(final long pos, final byte[] bytes, final int offset,
final int len) throws SQLException {
if (bytes == null) {
throw JDBCUtil.nullArgument("bytes");
}
final OutputStream os = setBinaryStream(pos);
try {
os.write(bytes, offset, len);
} catch (IOException ex) {
throw JDBCUtil.sqlException(ex);
} finally {
closeSafely(os);
}
return len;
}
/**
* Retrieves a stream that can be used to write to the BLOB
* value that this Blob
object represents. The stream begins
* at position pos
.
* The bytes written to the stream will overwrite the existing bytes
* in the Blob
object starting at the position
* pos
. If the end of the Blob
value is reached
* while writing to the stream, then the length of the Blob
* value will be increased to accommodate the extra bytes.
*
* Note: If the value specified for pos
* is greater then the length+1 of the BLOB
value then the
* behavior is undefined. Some JDBC drivers may throw a
* SQLException
while other drivers may support this
* operation.
*
*
*
* HSQLDB-Specific Information:
*
* Data written to the returned stream affects only the content of the
* underlying file; it has no effect upon a value stored in a database.
* To propagate the updated Blob value to a database, it is required to
* supply the Blob instance to an updating or inserting setXXX method of a
* Prepared or Callable Statement, or to supply the Blob instance to an
* updateXXX method of an updateable ResultSet.
*
*
*
*
* @param pos the position in the BLOB
value at which
* to start writing; the first position is 1
* @return a java.io.OutputStream
object to which data can
* be written
* @exception SQLException if there is an error accessing the
* BLOB
value or if pos is less than 1
* @exception java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support
* this method
* @see #getBinaryStream
* @since JDK 1.4
*/
public OutputStream setBinaryStream(final long pos) throws SQLException {
if (pos < 1) {
throw JDBCUtil.invalidArgument("pos: " + pos);
}
checkClosed();
createFile();
OutputStream adapter;
try {
adapter = new OutputStreamAdapter(m_file, pos - 1) {
private boolean closed;
public synchronized void close() throws IOException {
if (closed) {
return;
}
closed = true;
try {
super.close();
} finally {
m_streams.remove(this);
}
}
};
} catch (IOException ex) {
throw JDBCUtil.sqlException(ex);
} catch (IllegalArgumentException ex) {
throw JDBCUtil.sqlException(ex);
} catch (SecurityException ex) {
throw JDBCUtil.sqlException(ex);
}
m_streams.add(adapter);
final OutputStream result = new BufferedOutputStream(adapter);
return result;
}
/**
* Truncates the BLOB
value that this Blob
* object represents to be len
bytes in length.
*
* Note: If the value specified for pos
* is greater then the length+1 of the BLOB
value then the
* behavior is undefined. Some JDBC drivers may throw a
* SQLException
while other drivers may support this
* operation.
*
*
*
* HSQLDB-Specific Information:
*
* This operation affects only the length of the underlying file; it has no
* effect upon a value stored in a database. To propagate the truncated
* Blob value to a database, it is required to supply the Blob instance to
* an updating or inserting setXXX method of a Prepared or Callable
* Statement, or to supply the Blob instance to an updateXXX method of an
* updatable ResultSet.
*
*
*
*
* @param len the length, in bytes, to which the BLOB
value
* that this Blob
object represents should be truncated
* @exception SQLException if there is an error accessing the
* BLOB
value or if len is less than 0
* @exception java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support
* this method
* @since JDK 1.4
*/
public void truncate(long len) throws SQLException {
if (len < 0) {
throw JDBCUtil.invalidArgument("len: " + len);
}
checkClosed();
RandomAccessFile randomAccessFile = null;
try {
randomAccessFile = new RandomAccessFile(m_file, "rw");
randomAccessFile.setLength(len);
} catch (IOException ex) {
throw JDBCUtil.sqlException(ex);
} catch (IllegalArgumentException ex) {
throw JDBCUtil.sqlException(ex);
} catch (SecurityException ex) {
throw JDBCUtil.sqlException(ex);
} finally {
closeSafely(randomAccessFile);
}
}
/**
* This method frees the Blob
object and releases the resources that
* it holds. The object is invalid once the free
* method is called.
*
* After free
has been called, any attempt to invoke a
* method other than free
will result in a SQLException
* being thrown. If free
is called multiple times, the subsequent
* calls to free
are treated as a no-op.
*
*
*
* HSQLDB-Specific Information:
*
* This operation closes any input and/or output streams obtained
* via {@link #getBinaryStream()}, {@link #getBinaryStream(long, long)} or
* {@link #setBinaryStream(long)}.
*
* Additionally, if the property {@link #isDeleteOnFree()} is true, then
* an attempt is made to delete the backing file.
*
*
*
*
* @throws SQLException if an error occurs releasing
* the Blob's resources
* @exception java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support
* this method
* @see #setDeleteOnFree(boolean)
* @see #isDeleteOnFree()
* @since JDK 1.6
*/
public synchronized void free() throws SQLException {
if (m_closed) {
return;
}
m_closed = true;
final List