/*
Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved.
The MySQL Connector/J is licensed under the terms of the GPLv2
<http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors.
There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
this software, see the FLOSS License Exception
<http://www.mysql.com/about/legal/licensing/foss-exception.html>.
This program is free software; you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation; version 2
of the License.
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 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.jdbc;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* 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.
*
* @author Mark Matthews
*
* @version $Id: BlobFromLocator.java,v 1.1.4.1 2005/05/19 18:31:49 mmatthews
* Exp $
*/
public class BlobFromLocator implements java.sql.Blob {
private List<String> primaryKeyColumns = null;
private List<String> primaryKeyValues = null;
/** The ResultSet that created this BLOB */
private ResultSetImpl creatorResultSet;
private String blobColumnName = null;
private String tableName = null;
private int numColsInResultSet = 0;
private int numPrimaryKeys = 0;
private String quotedId;
private ExceptionInterceptor exceptionInterceptor;
/**
* Creates an updatable BLOB that can update in-place
*/
BlobFromLocator(ResultSetImpl creatorResultSetToSet, int blobColumnIndex, ExceptionInterceptor exceptionInterceptor)
throws SQLException {
this.exceptionInterceptor = exceptionInterceptor;
this.creatorResultSet = creatorResultSetToSet;
this.numColsInResultSet = this.creatorResultSet.fields.length;
this.quotedId = this.creatorResultSet.connection.getMetaData()
.getIdentifierQuoteString();
if (this.numColsInResultSet > 1) {
this.primaryKeyColumns = new ArrayList<String>();
this.primaryKeyValues = new ArrayList<String>();
for (int i = 0; i < this.numColsInResultSet; i++) {
if (this.creatorResultSet.fields[i].isPrimaryKey()) {
StringBuffer keyName = new StringBuffer();
keyName.append(quotedId);
String originalColumnName = this.creatorResultSet.fields[i]
.getOriginalName();
if ((originalColumnName != null)
&& (originalColumnName.length() > 0)) {
keyName.append(originalColumnName);
} else {
keyName.append(this.creatorResultSet.fields[i]
.getName());
}
keyName.append(quotedId);
this.primaryKeyColumns.add(keyName.toString());
this.primaryKeyValues.add(this.creatorResultSet
.getString(i + 1));
}
}
} else {
notEnoughInformationInQuery();
}
this.numPrimaryKeys = this.primaryKeyColumns.size();
if (this.numPrimaryKeys == 0) {
notEnoughInformationInQuery();
}
if (this.creatorResultSet.fields[0].getOriginalTableName() != null) {
StringBuffer tableNameBuffer = new StringBuffer();
String databaseName = this.creatorResultSet.fields[0]
.getDatabaseName();
if ((databaseName != null) && (databaseName.length() > 0)) {
tableNameBuffer.append(quotedId);
tableNameBuffer.append(databaseName);
tableNameBuffer.append(quotedId);
tableNameBuffer.append('.');
}
tableNameBuffer.append(quotedId);
tableNameBuffer.append(this.creatorResultSet.fields[0]
.getOriginalTableName());
tableNameBuffer.append(quotedId);
this.tableName = tableNameBuffer.toString();
} else {
StringBuffer tableNameBuffer = new StringBuffer();
tableNameBuffer.append(quotedId);
tableNameBuffer.append(this.creatorResultSet.fields[0]
.getTableName());
tableNameBuffer.append(quotedId);
this.tableName = tableNameBuffer.toString();
}
this.blobColumnName = quotedId
+ this.creatorResultSet.getString(blobColumnIndex) + quotedId;
}
private void notEnoughInformationInQuery() throws SQLException {
throw SQLError.createSQLException("Emulated BLOB locators must come from "
+ "a ResultSet with only one table selected, and all primary "
+ "keys selected", SQLError.SQL_STATE_GENERAL_ERROR, this.exceptionInterceptor);
}
/**
* @see Blob#setBinaryStream(long)
*/
public OutputStream setBinaryStream(long indexToWriteAt)
throws SQLException {
throw SQLError.notImplemented();
}
/**
* 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 java.io.InputStream getBinaryStream() throws SQLException {
// TODO: Make fetch size configurable
return new BufferedInputStream(new LocatorInputStream(),
this.creatorResultSet.connection.getLocatorFetchBufferSize());
}
/**
* @see Blob#setBytes(long, byte[], int, int)
*/
public int setBytes(long writeAt, byte[] bytes, int offset, int length)
throws SQLException {
java.sql.PreparedStatement pStmt = null;
if ((offset + length) > bytes.length) {
length = bytes.length - offset;
}
byte[] bytesToWrite = new byte[length];
System.arraycopy(bytes, offset, bytesToWrite, 0, length);
// FIXME: Needs to use identifiers for column/table names
StringBuffer query = new StringBuffer("UPDATE ");
query.append(this.tableName);
query.append(" SET ");
query.append(this.blobColumnName);
query.append(" = INSERT(");
query.append(this.blobColumnName);
query.append(", ");
query.append(writeAt);
query.append(", ");
query.append(length);
query.append(", ?) WHERE ");
query.append(this.primaryKeyColumns.get(0));
query.append(" = ?");
for (int i = 1; i < this.numPrimaryKeys; i++) {
query.append(" AND ");
query.append(this.primaryKeyColumns.get(i));
query.append(" = ?");
}
try {
// FIXME: Have this passed in instead
pStmt = this.creatorResultSet.connection.prepareStatement(query
.toString());
pStmt.setBytes(1, bytesToWrite);
for (int i = 0; i < this.numPrimaryKeys; i++) {
pStmt.setString(i + 2, this.primaryKeyValues.get(i));
}
int rowsUpdated = pStmt.executeUpdate();
if (rowsUpdated != 1) {
throw SQLError.createSQLException(
"BLOB data not found! Did primary keys change?",
SQLError.SQL_STATE_GENERAL_ERROR, this.exceptionInterceptor);
}
} finally {
if (pStmt != null) {
try {
pStmt.close();
} catch (SQLException sqlEx) {
; // do nothing
}
pStmt = null;
}
}
return (int) length();
}
/**
* @see Blob#setBytes(long, byte[])
*/
public int setBytes(long writeAt, byte[] bytes) throws SQLException {
return setBytes(writeAt, bytes, 0, bytes.length);
}
/**
* 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
* <code>pos</code> and having a length of <code>length</code>.
*
* @throws SQLException
* if a database error occurs
*/
public byte[] getBytes(long pos, int length) throws SQLException {
java.sql.PreparedStatement pStmt = null;
try {
pStmt = createGetBytesStatement();
return getBytesInternal(pStmt, pos, length);
} finally {
if (pStmt != null) {
try {
pStmt.close();
} catch (SQLException sqlEx) {
; // do nothing
}
pStmt = null;
}
}
}
/**
* 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 long length() throws SQLException {
java.sql.ResultSet blobRs = null;
java.sql.PreparedStatement pStmt = null;
// FIXME: Needs to use identifiers for column/table names
StringBuffer query = new StringBuffer("SELECT LENGTH(");
query.append(this.blobColumnName);
query.append(") FROM ");
query.append(this.tableName);
query.append(" WHERE ");
query.append(this.primaryKeyColumns.get(0));
query.append(" = ?");
for (int i = 1; i < this.numPrimaryKeys; i++) {
query.append(" AND ");
query.append(this.primaryKeyColumns.get(i));
query.append(" = ?");
}
try {
// FIXME: Have this passed in instead
pStmt = this.creatorResultSet.connection.prepareStatement(query
.toString());
for (int i = 0; i < this.numPrimaryKeys; i++) {
pStmt.setString(i + 1, this.primaryKeyValues.get(i));
}
blobRs = pStmt.executeQuery();
if (blobRs.next()) {
return blobRs.getLong(1);
}
throw SQLError.createSQLException(
"BLOB data not found! Did primary keys change?",
SQLError.SQL_STATE_GENERAL_ERROR, this.exceptionInterceptor);
} finally {
if (blobRs != null) {
try {
blobRs.close();
} catch (SQLException sqlEx) {
; // do nothing
}
blobRs = null;
}
if (pStmt != null) {
try {
pStmt.close();
} catch (SQLException sqlEx) {
; // do nothing
}
pStmt = null;
}
}
}
/**
* 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 long position(java.sql.Blob pattern, long start) throws SQLException {
return position(pattern.getBytes(0, (int) pattern.length()), start);
}
/**
* @see java.sql.Blob#position(byte[], long)
*/
public long position(byte[] pattern, long start) throws SQLException {
java.sql.ResultSet blobRs = null;
java.sql.PreparedStatement pStmt = null;
// FIXME: Needs to use identifiers for column/table names
StringBuffer query = new StringBuffer("SELECT LOCATE(");
query.append("?, ");
query.append(this.blobColumnName);
query.append(", ");
query.append(start);
query.append(") FROM ");
query.append(this.tableName);
query.append(" WHERE ");
query.append(this.primaryKeyColumns.get(0));
query.append(" = ?");
for (int i = 1; i < this.numPrimaryKeys; i++) {
query.append(" AND ");
query.append(this.primaryKeyColumns.get(i));
query.append(" = ?");
}
try {
// FIXME: Have this passed in instead
pStmt = this.creatorResultSet.connection.prepareStatement(query
.toString());
pStmt.setBytes(1, pattern);
for (int i = 0; i < this.numPrimaryKeys; i++) {
pStmt.setString(i + 2, this.primaryKeyValues.get(i));
}
blobRs = pStmt.executeQuery();
if (blobRs.next()) {
return blobRs.getLong(1);
}
throw SQLError.createSQLException(
"BLOB data not found! Did primary keys change?",
SQLError.SQL_STATE_GENERAL_ERROR, this.exceptionInterceptor);
} finally {
if (blobRs != null) {
try {
blobRs.close();
} catch (SQLException sqlEx) {
; // do nothing
}
blobRs = null;
}
if (pStmt != null) {
try {
pStmt.close();
} catch (SQLException sqlEx) {
; // do nothing
}
pStmt = null;
}
}
}
/**
* @see Blob#truncate(long)
*/
public void truncate(long length) throws SQLException {
java.sql.PreparedStatement pStmt = null;
// FIXME: Needs to use identifiers for column/table names
StringBuffer query = new StringBuffer("UPDATE ");
query.append(this.tableName);
query.append(" SET ");
query.append(this.blobColumnName);
query.append(" = LEFT(");
query.append(this.blobColumnName);
query.append(", ");
query.append(length);
query.append(") WHERE ");
query.append(this.primaryKeyColumns.get(0));
query.append(" = ?");
for (int i = 1; i < this.numPrimaryKeys; i++) {
query.append(" AND ");
query.append(this.primaryKeyColumns.get(i));
query.append(" = ?");
}
try {
// FIXME: Have this passed in instead
pStmt = this.creatorResultSet.connection.prepareStatement(query
.toString());
for (int i = 0; i < this.numPrimaryKeys; i++) {
pStmt.setString(i + 1, this.primaryKeyValues.get(i));
}
int rowsUpdated = pStmt.executeUpdate();
if (rowsUpdated != 1) {
throw SQLError.createSQLException(
"BLOB data not found! Did primary keys change?",
SQLError.SQL_STATE_GENERAL_ERROR, this.exceptionInterceptor);
}
} finally {
if (pStmt != null) {
try {
pStmt.close();
} catch (SQLException sqlEx) {
; // do nothing
}
pStmt = null;
}
}
}
java.sql.PreparedStatement createGetBytesStatement() throws SQLException {
StringBuffer query = new StringBuffer("SELECT SUBSTRING(");
query.append(this.blobColumnName);
query.append(", ");
query.append("?");
query.append(", ");
query.append("?");
query.append(") FROM ");
query.append(this.tableName);
query.append(" WHERE ");
query.append(this.primaryKeyColumns.get(0));
query.append(" = ?");
for (int i = 1; i < this.numPrimaryKeys; i++) {
query.append(" AND ");
query.append(this.primaryKeyColumns.get(i));
query.append(" = ?");
}
return this.creatorResultSet.connection.prepareStatement(query
.toString());
}
byte[] getBytesInternal(java.sql.PreparedStatement pStmt, long pos,
int length) throws SQLException {
java.sql.ResultSet blobRs = null;
try {
pStmt.setLong(1, pos);
pStmt.setInt(2, length);
for (int i = 0; i < this.numPrimaryKeys; i++) {
pStmt.setString(i + 3, this.primaryKeyValues.get(i));
}
blobRs = pStmt.executeQuery();
if (blobRs.next()) {
return ((com.mysql.jdbc.ResultSetImpl) blobRs).getBytes(1, true);
}
throw SQLError.createSQLException(
"BLOB data not found! Did primary keys change?",
SQLError.SQL_STATE_GENERAL_ERROR, this.exceptionInterceptor);
} finally {
if (blobRs != null) {
try {
blobRs.close();
} catch (SQLException sqlEx) {
; // do nothing
}
blobRs = null;
}
}
}
class LocatorInputStream extends InputStream {
long currentPositionInBlob = 0;
long length = 0;
java.sql.PreparedStatement pStmt = null;
LocatorInputStream() throws SQLException {
length = length();
pStmt = createGetBytesStatement();
}
@SuppressWarnings("synthetic-access")
LocatorInputStream(long pos, long len) throws SQLException {
length = pos + len;
currentPositionInBlob = pos;
long blobLength = length();
if (pos + len > blobLength) {
throw SQLError.createSQLException(
Messages.getString("Blob.invalidStreamLength",
new Object[] {Long.valueOf(blobLength), Long.valueOf(pos), Long.valueOf(len)}),
SQLError.SQL_STATE_ILLEGAL_ARGUMENT, exceptionInterceptor);
}
if (pos < 1) {
throw SQLError.createSQLException(Messages.getString("Blob.invalidStreamPos"),
SQLError.SQL_STATE_ILLEGAL_ARGUMENT, exceptionInterceptor);
}
if (pos > blobLength) {
throw SQLError.createSQLException(Messages.getString("Blob.invalidStreamPos"),
SQLError.SQL_STATE_ILLEGAL_ARGUMENT, exceptionInterceptor);
}
}
public int read() throws IOException {
if (currentPositionInBlob + 1 > length) {
return -1;
}
try {
byte[] asBytes = getBytesInternal(pStmt,
(currentPositionInBlob++) + 1, 1);
if (asBytes == null) {
return -1;
}
return asBytes[0];
} catch (SQLException sqlEx) {
throw new IOException(sqlEx.toString());
}
}
/*
* (non-Javadoc)
*
* @see java.io.InputStream#read(byte[], int, int)
*/
public int read(byte[] b, int off, int len) throws IOException {
if (currentPositionInBlob + 1 > length) {
return -1;
}
try {
byte[] asBytes = getBytesInternal(pStmt,
(currentPositionInBlob) + 1, len);
if (asBytes == null) {
return -1;
}
System.arraycopy(asBytes, 0, b, off, asBytes.length);
currentPositionInBlob += asBytes.length;
return asBytes.length;
} catch (SQLException sqlEx) {
throw new IOException(sqlEx.toString());
}
}
/*
* (non-Javadoc)
*
* @see java.io.InputStream#read(byte[])
*/
public int read(byte[] b) throws IOException {
if (currentPositionInBlob + 1 > length) {
return -1;
}
try {
byte[] asBytes = getBytesInternal(pStmt,
(currentPositionInBlob) + 1, b.length);
if (asBytes == null) {
return -1;
}
System.arraycopy(asBytes, 0, b, 0, asBytes.length);
currentPositionInBlob += asBytes.length;
return asBytes.length;
} catch (SQLException sqlEx) {
throw new IOException(sqlEx.toString());
}
}
/*
* (non-Javadoc)
*
* @see java.io.InputStream#close()
*/
public void close() throws IOException {
if (pStmt != null) {
try {
pStmt.close();
} catch (SQLException sqlEx) {
throw new IOException(sqlEx.toString());
}
}
super.close();
}
}
public void free() throws SQLException {
this.creatorResultSet = null;
this.primaryKeyColumns = null;
this.primaryKeyValues = null;
}
public InputStream getBinaryStream(long pos, long length) throws SQLException {
return new LocatorInputStream(pos, length);
}
}