/*
* Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 2.0, as published by the
* Free Software Foundation.
*
* This program is also distributed with certain software (including but not
* limited to OpenSSL) that is licensed under separate terms, as designated in a
* particular file or component or in included license documentation. The
* authors of MySQL hereby grant you an additional permission to link the
* program and your derivative works with the separately licensed software that
* they have included with MySQL.
*
* Without limiting anything contained in the foregoing, this file, which is
* part of MySQL Connector/J, is also subject to the Universal FOSS Exception,
* version 1.0, a copy of which can be found at
* http://oss.oracle.com/licenses/universal-foss-exception.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0,
* for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.mysql.cj.jdbc;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import com.mysql.cj.Constants;
import com.mysql.cj.Messages;
import com.mysql.cj.exceptions.ExceptionInterceptor;
import com.mysql.cj.exceptions.MysqlErrorNumbers;
import com.mysql.cj.jdbc.exceptions.SQLError;
import com.mysql.cj.jdbc.result.ResultSetInternalMethods;
import com.mysql.cj.protocol.OutputStreamWatcher;
import com.mysql.cj.protocol.WatchableOutputStream;
import com.mysql.cj.protocol.WatchableStream;
/**
* The representation (mapping) in the JavaTM programming language of an SQL BLOB value. An SQL BLOB is a built-in type that stores a Binary Large Object
* as a column value in a row of a database table. The driver implements Blob using an SQL locator(BLOB), which means that a Blob object contains a logical
* pointer to the SQL BLOB data rather than the data itself. A Blob object is valid for the duration of the transaction in which is was created. Methods in
* the interfaces ResultSet, CallableStatement, and PreparedStatement, such as getBlob and setBlob allow a programmer to access an SQL BLOB value. The Blob
* interface provides methods for getting the length of an SQL BLOB (Binary Large Object) value, for materializing a BLOB value on the client, and for
* determining the position of a pattern of bytes within a BLOB value. This class is new in the JDBC 2.0 API.
*/
public class Blob implements java.sql.Blob, OutputStreamWatcher {
//
// This is a real brain-dead implementation of BLOB. Once I add streamability to the I/O for MySQL this will be more efficiently implemented
// (except for the position() method, ugh).
//
/** The binary data that makes up this BLOB */
private byte[] binaryData = null;
private boolean isClosed = false;
private ExceptionInterceptor exceptionInterceptor;
/**
* Creates a Blob without data
*/
Blob(ExceptionInterceptor exceptionInterceptor) {
setBinaryData(Constants.EMPTY_BYTE_ARRAY);
this.exceptionInterceptor = exceptionInterceptor;
}
/**
* Creates a BLOB encapsulating the given binary data
*
* @param data
*/
public Blob(byte[] data, ExceptionInterceptor exceptionInterceptor) {
setBinaryData(data);
this.exceptionInterceptor = exceptionInterceptor;
}
/**
* Creates an updatable BLOB that can update in-place (not implemented yet).
*
* @param data
* @param creatorResultSetToSet
* @param columnIndexToSet
*/
Blob(byte[] data, ResultSetInternalMethods creatorResultSetToSet, int columnIndexToSet) {
setBinaryData(data);
}
private synchronized byte[] getBinaryData() {
return this.binaryData;
}
/**
* Retrieves the BLOB designated by this Blob instance as a stream.
*
* @return this BLOB represented as a binary stream of bytes.
*
* @throws SQLException
* if a database error occurs
*/
public synchronized java.io.InputStream getBinaryStream() throws SQLException {
checkClosed();
return new ByteArrayInputStream(getBinaryData());
}
/**
* Returns as an array of bytes, part or all of the BLOB value that this
* Blob object designates.
*
* @param pos
* where to start the part of the BLOB
* @param length
* the length of the part of the BLOB you want returned.
*
* @return the bytes stored in the blob starting at position pos
and having a length of length
.
*
* @throws SQLException
* if a database error occurs
*/
public synchronized byte[] getBytes(long pos, int length) throws SQLException {
checkClosed();
if (pos < 1) {
throw SQLError.createSQLException(Messages.getString("Blob.2"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
}
pos--;
if (pos > this.binaryData.length) {
throw SQLError.createSQLException(Messages.getString("Blob.3"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
}
if (pos + length > this.binaryData.length) {
throw SQLError.createSQLException(Messages.getString("Blob.4"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
}
byte[] newData = new byte[length];
System.arraycopy(getBinaryData(), (int) (pos), newData, 0, length);
return newData;
}
/**
* Returns the number of bytes in the BLOB value designated by this Blob
* object.
*
* @return the length of this blob
*
* @throws SQLException
* if a database error occurs
*/
public synchronized long length() throws SQLException {
checkClosed();
return getBinaryData().length;
}
/**
* @see java.sql.Blob#position(byte[], long)
*/
public synchronized long position(byte[] pattern, long start) throws SQLException {
throw SQLError.createSQLFeatureNotSupportedException();
}
/**
* Finds the position of the given pattern in this BLOB.
*
* @param pattern
* the pattern to find
* @param start
* where to start finding the pattern
*
* @return the position where the pattern is found in the BLOB, -1 if not
* found
*
* @throws SQLException
* if a database error occurs
*/
public synchronized long position(java.sql.Blob pattern, long start) throws SQLException {
checkClosed();
return position(pattern.getBytes(0, (int) pattern.length()), start);
}
private synchronized void setBinaryData(byte[] newBinaryData) {
this.binaryData = newBinaryData;
}
/**
* @see Blob#setBinaryStream(long)
*/
public synchronized OutputStream setBinaryStream(long indexToWriteAt) throws SQLException {
checkClosed();
if (indexToWriteAt < 1) {
throw SQLError.createSQLException(Messages.getString("Blob.0"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
}
WatchableOutputStream bytesOut = new WatchableOutputStream();
bytesOut.setWatcher(this);
if (indexToWriteAt > 0) {
bytesOut.write(this.binaryData, 0, (int) (indexToWriteAt - 1));
}
return bytesOut;
}
/**
* @see Blob#setBytes(long, byte[])
*/
public synchronized int setBytes(long writeAt, byte[] bytes) throws SQLException {
checkClosed();
return setBytes(writeAt, bytes, 0, bytes.length);
}
/**
* @see Blob#setBytes(long, byte[], int, int)
*/
public synchronized int setBytes(long writeAt, byte[] bytes, int offset, int length) throws SQLException {
checkClosed();
OutputStream bytesOut = setBinaryStream(writeAt);
try {
bytesOut.write(bytes, offset, length);
} catch (IOException ioEx) {
SQLException sqlEx = SQLError.createSQLException(Messages.getString("Blob.1"), MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR,
this.exceptionInterceptor);
sqlEx.initCause(ioEx);
throw sqlEx;
} finally {
try {
bytesOut.close();
} catch (IOException doNothing) {
// do nothing
}
}
return length;
}
public synchronized void streamClosed(byte[] byteData) {
this.binaryData = byteData;
}
public synchronized void streamClosed(WatchableStream out) {
int streamSize = out.size();
if (streamSize < this.binaryData.length) {
out.write(this.binaryData, streamSize, this.binaryData.length - streamSize);
}
this.binaryData = out.toByteArray();
}
/**
* Truncates the BLOB
value that this Blob
object represents to be len
bytes in length.
*
* Note: If the value specified for len
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.
*
* @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 SQLFeatureNotSupportedException
* if the JDBC driver does not support
* this method
* @since 1.4
*/
public synchronized void truncate(long len) throws SQLException {
checkClosed();
if (len < 0) {
throw SQLError.createSQLException(Messages.getString("Blob.5"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
}
if (len > this.binaryData.length) {
throw SQLError.createSQLException(Messages.getString("Blob.6"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
}
// TODO: Do this without copying byte[]s by maintaining some end pointer on the original data
byte[] newData = new byte[(int) len];
System.arraycopy(getBinaryData(), 0, newData, 0, (int) len);
this.binaryData = newData;
}
/**
* 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.
*
*
* @throws SQLException
* if an error occurs releasing
* the Blob's resources
* @exception SQLFeatureNotSupportedException
* if the JDBC driver does not support
* this method
* @since 1.6
*/
public synchronized void free() throws SQLException {
this.binaryData = null;
this.isClosed = true;
}
/**
* Returns an InputStream
object that contains a partial Blob
value,
* starting with the byte specified by pos, which is length bytes in length.
*
* @param pos
* the offset to the first byte of the partial value to be retrieved.
* The first byte in the Blob
is at position 1
* @param length
* the length in bytes of the partial value to be retrieved
* @return InputStream
through which the partial Blob
value can be read.
* @throws SQLException
* if pos is less than 1 or if pos is greater than the number of bytes
* in the Blob
or if pos + length is greater than the number of bytes
* in the Blob
*
* @exception SQLFeatureNotSupportedException
* if the JDBC driver does not support
* this method
* @since 1.6
*/
public synchronized InputStream getBinaryStream(long pos, long length) throws SQLException {
checkClosed();
if (pos < 1) {
throw SQLError.createSQLException(Messages.getString("Blob.2"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
}
pos--;
if (pos > this.binaryData.length) {
throw SQLError.createSQLException(Messages.getString("Blob.6"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
}
if (pos + length > this.binaryData.length) {
throw SQLError.createSQLException(Messages.getString("Blob.4"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
}
return new ByteArrayInputStream(getBinaryData(), (int) pos, (int) length);
}
private synchronized void checkClosed() throws SQLException {
if (this.isClosed) {
throw SQLError.createSQLException(Messages.getString("Blob.7"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
}
}
}