Bug #5706 getString on a varchar(64) field cause ClassCastException in debuggers
Submitted: 22 Sep 2004 18:47 Modified: 24 Sep 2004 14:38
Reporter: Quartz 12h Email Updates:
Status: Closed Impact on me:
None 
Category:Connector / J Severity:S3 (Non-critical)
Version:3.1.4beta OS:Windows (win xp)
Assigned to: Mark Matthews CPU Architecture:Any

[22 Sep 2004 18:47] Quartz 12h
Description:
whenever using default connections (no special props) from 3.1.4b
it crashes. when adding "?useServerPrepStmts=false" to my JDBC url,
it works.
So, server side perp. statements are causing classcast exception on varchar fields.

It actually try to cast to a string, but the object is a byte[] (with the ascii bytes of the supposed string to be returned).

The code SHOULH check for "instanceof" instead of throwing exception which is expensive and cause monitoring tool to trigger.

java.lang.ClassCastException
	com.mysql.jdbc.ResultSet.getNativeString(int) line: 4794
	com.mysql.jdbc.ResultSet.getString(int) line: 2111
	org.apache.catalina.realm.JDBCRealm.authenticate(java.sql.Connection, 

            try {
                return (String) this.thisRow[columnIndex - 1];
            } catch (ClassCastException cce) {
                ; // ignore, we're not really a String yet
            }

How to repeat:
connector/j 3.1.4 beta
server 4.1.5 gamma

CREATE TABLE IF NOT EXISTS `users` (
  `user_name` varchar(64) UNIQUE NOT NULL,
  `user_pass` varchar(64) NOT NULL,
  PRIMARY KEY(`id`, `user_name`)
) TYPE=InnoDB;

Suggested fix:
use:
Object cell = this.thisRow[columnIndex - 1];
if(cell instanceof String)
   return (String) cell;
[23 Sep 2004 7:59] Mark Matthews
Unfortunately, benchmarking shows that switching to instanceof, even on modern JVMs (1.4/1.5) is approximately 20% slower for this use case in a very 'hot' portion of code for most users.
[24 Sep 2004 14:17] Quartz 12h
Those benchmarks are certainly not hitting the case where the classcast exception will be created and thrown.

Here is a clean proof of this cost.
Not only casting is not expensive at all, it beraly show even with a million invocation (code below uses 100'000 only, because it would be too long with exception!)

Second observation, if the stacktrace is deep, it is even slower.
Creating an exception is trashing menory anyway.
Bad pratice.
Thanks for considering the suggested code fix.

public class TestExceptionCost {
	public static void main(String[] args) {
		System.out.println("----------Small stack--------------");
		test(0);
		
		System.out.println("----------Big stack--------------");
		test(100);
	}
	
	static final int LOOP = 100000;
	
	static void test(int n) {
		
		if(n>=0) {
			test(n-1);
		} else {
			long t, d;
			int good, bad;
			Object obj;
			
			//relax jvm time:
			try {Thread.sleep(1000); } catch(InterruptedException e){}
			
			//----------test positive cases-------------
			obj = "toto";
			good = bad = 0;
			t = System.currentTimeMillis();
			for(int i=0; i<LOOP; i++) {
				try {
					String s = (String)obj;
					good++;
				} catch(ClassCastException e) {
					bad++;
				}
			}
			d = System.currentTimeMillis() - t;
			System.out.println("by exception, no bad cast  : "+d+" ms, good="+good+", bad="+bad);
			
			//----------test negative cases-------------
			obj = new byte[4];
			good = bad = 0;
			t = System.currentTimeMillis();
			for(int i=0; i<LOOP; i++) {
				try {
					String s = (String)obj;
					good++;
				} catch(ClassCastException e) {
					bad++;
				}
			}
			d = System.currentTimeMillis() - t;
			System.out.println("by exception, bad cast     : "+d+" ms, good="+good+", bad="+bad);
			
			
			
			//----------test positive cases-------------
			obj = "toto";
			good = bad = 0;
			t = System.currentTimeMillis();
			for(int i=0; i<LOOP; i++) {
				if(obj instanceof String) {
					String s = (String)obj;
					good++;
				} else {
					bad++;
				}
			}
			d = System.currentTimeMillis() - t;
			System.out.println("by instanceof, with cast   : "+d+" ms, good="+good+", bad="+bad);
			
			//----------test negative cases-------------
			obj = new byte[4];
			good = bad = 0;
			t = System.currentTimeMillis();
			for(int i=0; i<LOOP; i++) {
				if(obj instanceof String) {
					String s = (String)obj;
					good++;
				} else {
					bad++;
				}
			}
			d = System.currentTimeMillis() - t;
			System.out.println("by instanceof, without cast: "+d+" ms, good="+good+", bad="+bad);
			
			
		}
	}
}
[24 Sep 2004 14:38] Mark Matthews
The benchmark did cause the class-cast exceptions to be thrown, however it didn't show the same results as yours.

The point about trashing memory is taken, so we've fixed it to use instanceof.

This will be in tonight's nightly snapshot build.