Bug #15735 SubString is altering my data
Submitted: 14 Dec 2005 13:27 Modified: 13 Feb 2006 3:23
Reporter: Chris Robinson
Status: Can't repeat
Category:Connector/J Severity:S2 (Serious)
Version:3.1.11 OS:Microsoft Windows (win xp)
Assigned to: Target Version:

[14 Dec 2005 13:27] Chris Robinson
Description:
To get around the large blob retrieval problem where java runs out of memory I have
implemented a version that uses the substring function in mysql to retrieve chunks of
data as suggested on the site. What I have stored in the table is binary data from files.
The problem I run into I frist noticed on my test run for a picture. Just to take an aside
first...

When a text file is removed from the blob field in substring chunks and then saved to the
hard disk there are absolutely no problems.

When an image or binary file is removed there are major problems. It seems that the
substring function is changing bytes of data. My first test that removed an image had so
many changes the image would not even display anymore. to note that the problem is not in
the database itself I removed the same image without the substring function and it was
fine for vieweing. However, removing it with the substring function changes the data.

I was wondering if you have any other work-arounds (because at the moment I am thinking
of just hexing and then unhexing the data but that would create files that are twice as
large in the database and java does not seem to have a built in hex function.

Thanks

How to repeat:
insert a binary file as normal into the database (image for example)

use the substring idea to repeatedly remove the data and save it into a file. Following
is a small example excerpt... BID is the blob's unique ID handled by auto_increment.
stuff is the blob column.

db is the connection
pstmnt=database prepared statement
args=variable from the main that tells me the name of the file I am saving to.

I know there are no try/catch blocks though there are in my code, it is to simplify it.

-------------------------
int chunkSize=1024;
int pos=1;

db.prepareStatement("select substring(stuff,?,?) from blobTest where BID=?");

pstmnt.setInt(2,chunkSize);
pstmnt.setInt(3,BID);

RandomAccessFile raf = new RandomAccessFile(args,"rw");	

while(true)
{

    ResultSet rs = pstmnt.executeQuery();
    pstmnt.setInt(1,pos);

    if (rs.next)
    {
        Blob blob = rs.getBlob(1);
        int length=(int)blob.length();

        if(length==0)
            break;

        byte[] _blob=blob.getBytes(1,length);
        raf.write(_blob);
    }
    else
        break;

}

raf.close();
-------------------------

Suggested fix:
I was wondering if you have any other work-arounds (because at the moment I am thinking
of just hexing and then unhexing the data but that would create files that are twice as
large in the database and java does not seem to have a built in hex function.

Otherwise, is it possible to request an equivalent to substring for a later release of
mySQL that will remove unmodified binary data in chunks?
[14 Dec 2005 15:04] Aleksey Kishkin
Chris, do you change 'pos' in the loop?
[16 Dec 2005 12:43] Chris Robinson
Yes, sorry, I thought the e-mail update would tell me when this got updated. Sorry for the
delay...

yes, forgot as I was writing it out in the forum...

pos has the bytes read added to it so that you get the next chunk of data
[16 Dec 2005 12:44] Chris Robinson
ie: from above in the loop 

pos+=length;
[16 Dec 2005 13:19] Valeriy Kravchuk
Can you, please, send the results of the following statement

show variables like 'character%';

executed in your Java program? It should return a result set, just like select.

Why do you think it is Server problem, not Connector/J one?
[17 Dec 2005 13:24] Chris Robinson
For the statement you would like me to run I need to wait until monday.

The reason I think its a server and not connector J problem is because if I retrieve the
whole data chunk without using substr then I end up getting my binary file exactly as it
is in the db. the second I use substr it changes the binary data I am retrieving and the
binary file is then corrupt. Even if I use a substr that is the length of the data itself
it comes back changed.
[19 Dec 2005 10:24] Chris Robinson
For the statement you would like me to run I need to wait until monday. (have not run
yet.... too many meetings at the moment)

The reason I think its a server and not connector J problem is because if I
retrieve the whole data chunk without using substr then I end up getting my
binary file exactly as it is in the db. the second I use substr it changes the
binary data I am retrieving and the binary file is then corrupt. Even if I use a
substr that is the length of the data itself it comes back changed.

-----

Also, I have found a temporary way around, when I store the information I hex it and then
in the java program itself I unhex it (would have done it in mysql but unhex of substr
also translates the characters. Alot of my characters become 3F... 

But making the change in mysql by taking a 2 character hex string and using
Integer.Convert(str,16) I get a byte that works.
[19 Dec 2005 10:44] Valeriy Kravchuk
Please, send the results of 

show variables like 'character%';

from Java when you'll have time. substring, as other string manipulation functions, may
work differently with differtent character sets. And your workaround (with hex
representation, always containing digits and English letters) only proves my idea about
the real reason for the problem.
[19 Dec 2005 13:37] Chris Robinson
character_set_client|latin1
character_set_connection|latin1
character_set_database|latin1
character_set_results|
character_set_server|latin1
character_set_system|utf8
character_sets_dir|C:\Program Files\MySQL\MySQL Server 5.0\share\charsets/

----
I output the two fields as a seperated set with the seperator being the '|' character.
Also, each result row is on a seperate line
[21 Dec 2005 15:40] Valeriy Kravchuk
To complete the information, send, please, the results of "SHOW CREATE TABLE blobTest"
statement.
[21 Dec 2005 15:44] Chris Robinson
Here is the result:
----------
CREATE TABLE `blobtest` (
  `BID` int(11) NOT NULL auto_increment,
  `stuff` longblob,
  `fileName` varchar(255) default NULL,
  `fileType` varchar(255) default NULL,
  PRIMARY KEY  (`BID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
[26 Dec 2005 16:02] Valeriy Kravchuk
That empty character_set_results looks strange for me. Can you, please, execute "set names
latin1" statement in your code before fetching blob value and check the results?

The complete but simple Java code to repeat the problem will be useful too.
[29 Dec 2005 2:26] Chris Robinson
That did not make a difference unfortunately. It still alters data...

To retrieve the file
-----------------------------
	public static void main(int BID,String args)
	{
		try
		{
		  	Connection db = null;

			try 
			{
				db = DbRegistry.getDbConnection();
			} catch (Exception e) 
			{
				System.err.println(e);
				throw e;
			}
			
			PreparedStatement pstmnt=db.prepareStatement("select fileType,fileName from blobTest
where BID=?");
			pstmnt.setInt(1,BID);
			ResultSet rs = pstmnt.executeQuery();
			
			if (rs.next())
			{
				System.out.println("File Name:"+rs.getString("fileName"));
				System.out.println("File Type:"+rs.getString("fileType"));
				
				int chunkSize=1048576;
				int pos=1;
				int count=0;
				
				pstmnt=db.prepareStatement("select substr(stuff,?,?) from blobTest where BID=?");
				pstmnt.execute("set names latin1");
				pstmnt.setInt(2,chunkSize);
				pstmnt.setInt(3,BID);

				File file=new File(args);
				
				if (file.exists())
				{
					file.delete();
				}

				System.out.println(file.getAbsolutePath());
				
				FileOutputStream fos=new FileOutputStream(file);
				BufferedOutputStream bos=new BufferedOutputStream(fos);
				
//				RandomAccessFile raf = new RandomAccessFile(args,"rw");
				
				while(true)
				{
					pstmnt.setInt(1,pos);
					ResultSet blobData=pstmnt.executeQuery();
					
					if (blobData.next())
					{
						Blob blob = blobData.getBlob(1);
						int length=(int)blob.length();
						
						if(length==0)
						{
							break;
						}
						
						byte[] _blob=blob.getBytes(1,length);

						bos.write(_blob);
						
//						raf.write(_blob);
						
						pos+=length;
					}
					else
						break;
					
				}
				
				bos.close();
//				raf.close();
			}
			else
			{
				System.err.println("No file found");
			}
			
			System.out.println("Completed...");

		} catch (Exception e) 
		{
			System.err.println(e);
		}
	}
-------------------------------
to store the file
-------------------------------
	public static void main(String fileName, String fileType, File file)
	{
		try
		{
		  	Connection db = null;

			try 
			{
				db = DbRegistry.getDbConnection();
			} catch (Exception e) 
			{
				System.err.println(e);
				throw e;
			}
			
			PreparedStatement pstmnt=db.prepareStatement("insert into
blobTest(stuff,filename,filetype) values(?,?,?)");
						
			InputStream is = new FileInputStream(file);
			pstmnt.setBinaryStream(1,is,(int)file.length());
			pstmnt.setString(2,fileName);
			pstmnt.setString(3,fileType);
			
			pstmnt.executeUpdate();
			is.close();
			
			System.out.println("Completed...");

		} catch (Exception e) 
		{
			System.err.println(e);
		}
		
	}
---------------------------------------------

Hope this helps.... The retrieve is to file but same concept used for http instead...

cheers
[27 Jan 2006 15:31] Larry Hill
I'm experiencing the same problem with .wav data albeit with ColdFusion MX 7 through
MyODBC 3.51. Every byte with a value of 80h through FFh is changed to 3Fh (the question
mark) leading me to believe something is trying to convert the data to readable text
rather than leaving it as is. Could the blame be placed on the MyODBC connector?

Note that retrieving the entire blob in one shot, rather than in SubString chunks, works
fine (provided you have the RAM to store it).
[28 Jan 2006 14:39] Valeriy Kravchuk
Sorry, but I was not able to repeat the problem using server size prepared statements:

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1 to server version: 5.0.15-nt

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> CREATE TABLE `blobtest` (
    ->   `BID` int(11) NOT NULL auto_increment,
    ->   `stuff` longblob,
    ->   `fileName` varchar(255) default NULL,
    ->   `fileType` varchar(255) default NULL,
    ->   PRIMARY KEY  (`BID`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Query OK, 0 rows affected (1.18 sec)

mysql> insert into blobtest(stuff) values (0x21222365808182FF);
Query OK, 1 row affected (0.04 sec)

mysql> select * from blobtest;
+-----+----------+----------+----------+
| BID | stuff    | fileName | fileType |
+-----+----------+----------+----------+
|   1 | !"#eАБВ      | NULL     | NULL     |
+-----+----------+----------+----------+
1 row in set (0.01 sec)

mysql> select collation(stuff) from blobtest;
+------------------+
| collation(stuff) |
+------------------+
| binary           |
+------------------+
1 row in set (0.00 sec)

mysql> select collation(substring(stuff,1,8)), substring(stuff,1,8) from blobtes
t;
+---------------------------------+----------------------+
| collation(substring(stuff,1,8)) | substring(stuff,1,8) |
+---------------------------------+----------------------+
| binary                          | !"#eАБВ                  |
+---------------------------------+----------------------+
1 row in set (0.02 sec)

mysql> prepare stmt1 from 'select collation(substring(stuff,?,?)), substring(stu
ff,?,?) from blobtest where BID=?';
Query OK, 0 rows affected (0.02 sec)
Statement prepared

mysql> set @pos1=1;
Query OK, 0 rows affected (0.00 sec)

mysql> set @pos2=8;
Query OK, 0 rows affected (0.00 sec)

mysql> set @id=1;
Query OK, 0 rows affected (0.00 sec)

mysql> execute stmt1 using @pos1, @pos2, @pos1, @pos2, @id;
+---------------------------------+----------------------+
| collation(substring(stuff,?,?)) | substring(stuff,?,?) |
+---------------------------------+----------------------+
| binary                          | !"#eАБВ                  |
+---------------------------------+----------------------+
1 row in set (0.00 sec)

mysql> select hex(substring(stuff,1,8)), hex(stuff) from blobtest;
+---------------------------+------------------+
| hex(substring(stuff,1,8)) | hex(stuff)       |
+---------------------------+------------------+
| 21222365808182FF          | 21222365808182FF |
+---------------------------+------------------+
1 row in set (0.02 sec)

mysql> prepare stmt2 from 'select hex(substring(stuff,?,?)), hex(stuff) from blobtest
where BID=?';
Query OK, 0 rows affected (0.00 sec)
Statement prepared

mysql> execute stmt2 using @pos1, @pos2, @id;
+---------------------------+------------------+
| hex(substring(stuff,?,?)) | hex(stuff)       |
+---------------------------+------------------+
| 21222365808182FF          | 21222365808182FF |
+---------------------------+------------------+
1 row in set (0.00 sec)

So, the problem seems Connector-related. So, please, specify the version of Connector/J
(and, Larry - Connector/ODBC) used. Any ideas on how to demonstrate the problem on
server, similary to my test case above, will be also useful.
[28 Jan 2006 19:04] Chris Robinson
If it is connectorJ related then you need only input a binary file like a jpeg and then
output it again via substring and you will see that the bytes mentioned by Larry are
changed. If you look at the beginning of the jpeg files you start with FF and this will
be changed... Using the above mentioned code
[29 Jan 2006 3:18] Larry Hill
I think Valeriy wisely changed the Category of this bug from Server to Connector/J because
he/she demonstrated that the server is doing its job.

Again, I'm using ColdFusion MX 7 through MyODBC 3.51 (more precisely,
MyODBC-3.51.11-2-win.msi) so my so-called higher level language is 2 layers away from the
server, thus, may not help Chris much.

Here's something interesting: When an entire BLOB is retrieved, it arrives as an
unaltered ByteArray (footnote [1]). But retrieving the SubString of a BLOB returns a
String data type[2] perhaps subject to modification by an encoding transform by the
connector and/or ColdFusion (latin1, Java UCS-2, UTF-8/16, Klingon, etc.). It's all Greek
to me, I just want to manipulate the raw data saved in the BLOB.

While I'm dreaming: Is SubString() an ANSI standard? How about a SubByteArray() query
function for MySQL 6?

Footnotes for ColdFusion folks who might be following along:

[1] Whole BLOBs arrive as ByteArrays.
<cfquery name="WhateverQueryName" datasource="WhateverDataSource">
SELECT WhateverBLOBfield FROM WhateverTableName
</cfquery>
<cfset buffer=WhateverQueryName.WhateverBLOBfield>
This statement...
<cfoutput>#buffer#</cfoutput>
...crashes coldfusion.runtime.Cast$ComplexByteArrayException: with
ComplexByteArrayException: ByteArray objects cannot be converted to strings.

This statement...
<cfoutput>#ToString(buffer)#</cfoutput>
...results in an ugly web page but works.

Use...
<cfprocessingdirective suppresswhitespace="yes">
<cfheader name="Content-Disposition" value="inline; filename=Whatever.wav">
<cfcontent type="audio/wav" reset="yes">
<cfquery name="WhateverQueryName" datasource="WhateverDataSource">
SELECT WhateverBLOBfield FROM WhateverTableName
</cfquery>
<cfoutput>#ToString(WhateverQueryName.WhateverBLOBfield)#</cfoutput>
</cfprocessingdirective>
...to push a BLOB (in this case, .wav data) directly to the browser.

[2] SubStrings of BLOBs arrive as Strings.
<!--- get first 1000 bytes --->
<cfquery name="WhateverQueryName" datasource="WhateverDataSource">
SELECT SubString(WhateverBLOBfield,1,1000) AS Chunk FROM WhateverTableName
</cfquery>
<cfset buffer=WhateverQueryName.Chunk>
This statement...
<cfoutput>#ArrayLen(buffer)#</cfoutput>
...crashes coldfusion.runtime.NonArrayException: with
Object of type class java.lang.String cannot be used as an array

It's not an array, it's a string. But save it to a file, push it to a browser, or Asc()
each byte and you will probably get altered data.
[2 Feb 2006 6:57] Larry Hill
Duh. Sorry, make that mysql-connector-java-3.1.10-bin.jar.
[2 Feb 2006 20:14] Mark Matthews
Unfortunately, a complex issue, as the function is clobbering the information that
Connector/J uses to determine if a value actually is of a binary information or not.

Certain "optimizations" in the server can cause different combinations of character sets,
metadata flags, etc. to represent values as BINARY data when they're not, and vice versa,
so there's quite a bit of logic in the JDBC driver to try and feret these out.
Unfortunately, the return from a function, even when the underlying column is a string,
is very often returned looking like BINARY data, when in fact it is not, so the driver
checks for no table name (which indicates result of a function call) for the column-level
metadata when it _looks_ like something is binary.

For your particular case, this causes the driver to think your actual binary data is a
string, and it treats it as such when one calls .getObject().

The workaround is to use ResultSet.getBytes(), which does not try to do such detective
work.
[2 Feb 2006 20:35] Mark Matthews
The following code snippet shows that at least for the range of values in the byte type,
the JDBC driver stores and retrieves them correctly, even when substring() is used when I
run it. Is there a particular sequence of bytes that causes this problem for you?

	public static void main(String[] args) throws Exception {
		Class.forName("com.mysql.jdbc.Driver");

		Connection conn = DriverManager
				.getConnection(
						"jdbc:mysql:///test?user=...&password=....",
						null);
		Statement stmt = conn.createStatement();
		stmt.executeUpdate("DROP TABLE IF EXISTS bytesTest;");
		stmt.executeUpdate("CREATE TABLE bytesTest(field1 blob)");

		byte counter = Byte.MIN_VALUE;
		
		byte[] allBytesOriginal = new byte[256];
		
		for (int i = 0; i < 256; i++) {
			allBytesOriginal[i] = counter++;
		}
		PreparedStatement pStmt = conn
				.prepareStatement("INSERT INTO bytesTest VALUES (?)");
		pStmt.setBytes(1, allBytesOriginal);
		pStmt.executeUpdate();

		ResultSet rs = stmt
				.executeQuery("SELECT SUBSTRING(field1,1, 300) FROM bytesTest");

		rs.next();

		byte[] retrBytes = rs.getBytes(1);

		if (retrBytes.length != allBytesOriginal.length) {
			System.out.println("Bytes not same length");
			System.exit(1);
		}

		for (int i = 0; i < retrBytes.length; i++) {
			if (retrBytes[i] != allBytesOriginal[i]) {
				System.out.println("Bytes differed at position " + i
						+ ", retrieved "
						+ Integer.toHexString(retrBytes[i] & 0xff) + " , sent "
						+ Integer.toHexString(allBytesOriginal[i] & 0xff));
			}
		}
		
		Blob asBlob = rs.getBlob(1);
		byte[] blobBytes = asBlob.getBytes(1, 256);
		
		if (blobBytes.length != allBytesOriginal.length) {
			System.out.println("Bytes not same length");
			System.exit(1);
		}

		for (int i = 0; i < blobBytes.length; i++) {
			if (blobBytes[i] != allBytesOriginal[i]) {
				System.out.println("Bytes differed at position " + i
						+ ", retrieved "
						+ Integer.toHexString(blobBytes[i] & 0xff) + " , sent "
						+ Integer.toHexString(allBytesOriginal[i] & 0xff));
			}
		}
	}
[2 Feb 2006 22:15] Larry Hill
The string your constructing, storing, then retrieving looks like it starts at 00 hex and
runs up to FF hex. Maybe Connector/J is looking at the first byte, which is obviously
non-printable "non-text" and interprets the string as binary.

Please try it again starting with some text characters then sprinkle in some binary stuff
(both above 80 hex and below 20 hex). e.g. "abcdefg" + 80h + FFh + 00h + 08h + "hijklmn".
[3 Feb 2006 0:30] Larry Hill
Forgive my ignorance about the inner working of MySQL and Connector/J. But if C/J can
solicit the name of a column from which the data is extracted, why can't it also know the
column type?

By definition, (Tiny/Medium/Long)BLOBs are binary, (Tiny/Medium/Long)Texts are supposedly
readable text strings subject to encoding.

Are you saying that "optimizations" can hide the real intended (developer-specified)
purpose, thus column type, of certain fields from even C/J?

In the month I've been working with MySQL, sure, I've already seen char(X)'s magically
turn into varchars. But do efficiency and benchmark speeds trump predictability?
[13 Feb 2006 3:13] Chris Robinson
Allright, I figured it out... The problem is with prepared statements!!

If you take a clearly harmless

select substring(filedata,1,102400) from files where fid=1

then everything goes well and the file is returned properly. 

However if you use the string:

String query="select substring(filedata,1,102400) from files where fid=?"

and then make a preparedStatement

statement=conn.prepareStatement(query);

statement.setInt(1,1);

ResultSet rs=statement.executeQuery();

Then all fails...

Don't know why but I made a workaround by taking the toString on the preparedstatement
and then executing the sql myself so all the placeholders are fixed and I am a happy
camper as the file comes out unchanged...
[13 Feb 2006 3:23] Chris Robinson
Hey Mark... I changed the code to highlight the error I was receiving and here ya go... It
now has errors with bytes being changed... All I did was make the query a
PreparedStatement.

Cheers

Chris

==================================================
	public static void main2(String[] args) throws Exception {

		Connection db = DbRegistry.getDbConnection();
		Statement stmt = db.createStatement();
		stmt.executeUpdate("DROP TABLE IF EXISTS bytesTest;");
		stmt.executeUpdate("CREATE TABLE bytesTest(field1 blob)");

		byte counter = Byte.MIN_VALUE;
		
		byte[] allBytesOriginal = new byte[256];
		
		for (int i = 0; i < 256; i++) {
			allBytesOriginal[i] = counter++;
		}
		PreparedStatement pStmt = db
				.prepareStatement("INSERT INTO bytesTest VALUES (?)");
		pStmt.setBytes(1, allBytesOriginal);
		pStmt.executeUpdate();

		pStmt = db
				.prepareStatement("SELECT SUBSTRING(field1,1, ?) FROM bytesTest");
		
		pStmt.setInt(1,300);
		
		ResultSet rs= pStmt.executeQuery();
		
//		ResultSet rs = stmt
//				.executeQuery("SELECT SUBSTRING(field1,1, 300) FROM bytesTest");

		rs.next();

		byte[] retrBytes = rs.getBytes(1);

		if (retrBytes.length != allBytesOriginal.length) {
			System.out.println("Bytes not same length");
			System.exit(1);
		}

		for (int i = 0; i < retrBytes.length; i++) {
			if (retrBytes[i] != allBytesOriginal[i]) {
				System.out.println("Bytes differed at position " + i
						+ ", retrieved "
						+ Integer.toHexString(retrBytes[i] & 0xff) + " , sent "
						+ Integer.toHexString(allBytesOriginal[i] & 0xff));
			}
		}
		
		Blob asBlob = rs.getBlob(1);
		byte[] blobBytes = asBlob.getBytes(1, 256);
		
		if (blobBytes.length != allBytesOriginal.length) {
			System.out.println("Bytes not same length");
			System.exit(1);
		}

		for (int i = 0; i < blobBytes.length; i++) {
			if (blobBytes[i] != allBytesOriginal[i]) {
				System.out.println("Bytes differed at position " + i
						+ ", retrieved "
						+ Integer.toHexString(blobBytes[i] & 0xff) + " , sent "
						+ Integer.toHexString(allBytesOriginal[i] & 0xff));
			}
		}
	}
==================================================
[7 Feb 2007 1:30] Michael Springmann
Experiencing exactly same strange behavior with MySQL Connector/J 5.04 and 3.1.14 (both
using the precompiled -bin.jar files) and MySQL 4.1.11 as sever. 
When using prepared statements, I get back replaced characters. E.g. byte of (int) values
141, 135, 136, 164, 180, 184, 199, 230, 231, 240, 241 (and most likely many others) in
images and other binary content stored in a table with the following schema

CREATE TABLE `Raw_Object_Content` (
  `Raw_Object_ID` varchar(255) NOT NULL default '',
  `Raw_Content` longblob NOT NULL,
  PRIMARY KEY  (`Raw_Object_ID`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 MAX_ROWS=4294967295 AVG_ROW_LENGTH=5000

get all replaced with byte of (int) value 63 (= 0x3F = ? in ASCII) with Connector/J 5, or
byte of (int) value 157 with Connector/J 3.1.

For me it seems like using a non-prepared statement solves the problem.

Piece of code to illustrate:

// DOES NOT WORK CORRECTLY DUE TO REPLACED BYTES

				PreparedStatement ps = connection.prepareStatement("SELECT SUBSTRING(Raw_Content FROM
? FOR ?) FROM Raw_Object_Content WHERE Raw_Object_ID = ?");
				ps.setLong(1, blobPos); // set offset to start reading from 
				ps.setLong(2, maxBytesToRead);	// set length
				ps.setString(3, contentID);
				ResultSet rs = ps.executeQuery();

				if (rs.next()) {
					newBufferContent = rs.getBytes(1);
 				} else {
					log.warn(contentID + " does not exist.");
					throw new FileNotFoundException("Raw content does not exist: " + contentID);
				}
				

// WORKS:

				Statement s = connection.createStatement();
				String sql = "SELECT SUBSTRING(Raw_Content FROM " + blobPos + " FOR " +
maxBytesToRead + ") FROM Raw_Object_Content WHERE Raw_Object_ID = '" + contentID + "'";
				ResultSet rs = s.executeQuery(sql);
				if (rs.next()) {
					newBufferContent = rs.getBytes(1);
 				} else {
					log.warn(contentID + " does not exist.");
					throw new FileNotFoundException("Raw content does not exist: " + contentID);
				}				

Best regards,
Michael
[23 Jun 2008 6:58] Dan Zhou
I might not be an issue with Connector/J, because I had a similar issue with substring but
with MySQL C API. I have a sample test case, in C, as follows.

When USE_BOUND_VARIABLE is not defined, the query succeeds and I can read 100 bytes from
the blob.
But when USE_BOUND_VARIABLE is defined, no data had been read from the blob, and no error
from any of those mysql api calls.

        std::string read_sql = "SELECT SUBSTRING(blobfield, ";
#ifdef USE_BOUND_VARIABLE
        read_sql += ",?,?) FROM ";
#else
	read_sql += ",1,100) FROM ";
#end
        read_sql + "mytable where id = 1";
        
	MYSQL_STMT      *mysql_stmt = mysql_stmt_init(conn->mysql);
        assert(mysql_stmt);
        if(!mysql_stmt_prepare(mysql_stmt, (const char*)buf, buflen)) {
                MYSQL_RES* meta = mysql_stmt_result_metadata(mysql_stmt);
                assert(meta);
                num_fields = mysql_num_fields(meta);
                assert(num_fields == 1);
                MYSQL_FIELD *fields = mysql_fetch_fields(meta);
		int ret;
#ifdef USE_BOUND_VARIABLE
                MYSQL_BIND bound_params[2];
                for(int i = 0; i < 2; ++i) {
                        MYSQL_BIND bind = {0};
                        bind.buffer_type = MYSQL_TYPE_DECIMAL;
                        bind.buffer = new char[10];
                        bind.buffer_length = 1000;
                        bind.length = new unsigned long;
                        bind.error = new my_bool;
                        bind.is_null = new my_bool;
                        *bind.length = 0;
                        *bind.error = 0;
                        *bind.is_null = 0;
                        memcpy(&bound_params[i], &bind, sizeof(MYSQL_BIND));
                }
                strcpy((char*)bound_params[0].buffer, "1");
                strcpy((char*)bound_params[1].buffer, "100");
                ret = mysql_stmt_bind_param(mysql_stmt, bound_params);
                assert(ret == 0);
#end
                ret = mysql_stmt_execute(mysql_stmt);
                assert(ret == 0);
                MYSQL_BIND bind = {0};
                bind.buffer = new char[1000];
                bind.buffer_length = 1000;
                bind.buffer_type = fields[0].type;
                bind.length = new unsigned long;
                bind.error = new my_bool;
                bind.is_null = new my_bool;
                *bind.length = 0;
                *bind.error = 0;
                *bind.is_null = 0;
                ret = mysql_stmt_bind_result(mysql_stmt, &bind);
                assert(ret == 0);
                ret = mysql_stmt_fetch(mysql_stmt);
                assert(ret == 0);
                ret = mysql_stmt_fetch_column(mysql_stmt, &bind, 0, 0);
                assert(ret == 0);
                pbuf((char*)bind.buffer, (int)(*bind.length));
                mysql_stmt_free_result(mysql_stmt);
                mysql_stmt_close(mysql_stmt);
		.....
        }
[23 Jun 2008 7:02] Dan Zhou
forget to mention, 
The client library version is:
mysql-5.0.51a-0.rhel5.i386
The server version is 5.0.32 on a debian linux.