Bug #25606 getUpdateCount() unreliable?
Submitted: 13 Jan 2007 2:22 Modified: 14 Feb 2007 18:02
Reporter: P Eger Email Updates:
Status: Can't repeat Impact on me:
None 
Category:Connector / J Severity:S2 (Serious)
Version:3.1.14 OS:Windows (Windows Server 2003 x64)
Assigned to: Mark Matthews CPU Architecture:Any

[13 Jan 2007 2:22] P Eger
Description:
Running against Mysql 5.0.24a-community-nt-log and 5.1.12-beta both, running the below test program excepts unexpectedly. This is a result of getUpdateCount() being seemingly unreliable. Note that this bug seems to be data dependant, as changing the below constant from "3646" to "10000" makes the exception go away.

output log for below program
----------------------------------------
added 1000
added 2000
added 3000
added 4000
added 5000
added 6000
added 7000
added 8000
added 9000
added 10000
added 1000
added 2000
added 3000
deleted 100 between 0 and 100
deleted 400 between 100 and 500
deleted 1600 between 500 and 2100
deleted 1546 between 2100 and 3646
Exception in thread "main" java.sql.SQLException: nrows_changed==-1
	at com.automotive.app.content.search.TestMysql.main(TestMysql.java:103)

How to repeat:
package com.automotive.app.content.search;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class TestMysql {
	
	public static void main(String[] args) throws Exception
	{
		com.mysql.jdbc.jdbc2.optional.MysqlDataSource nds = new com.mysql.jdbc.jdbc2.optional.MysqlDataSource();
		
		nds = new com.mysql.jdbc.jdbc2.optional.MysqlDataSource();
		nds.setServerName(args[0]);
		nds.setDatabaseName(args[1]);
		nds.setUser(args[2]);
    	nds.setPassword(args[3]);
    	nds.setCharacterEncoding("ascii");
    	nds.setAllowMultiQueries(true);
    	
    	Connection conn = nds.getConnection(args[2],args[3]);
		
		conn.createStatement().execute("drop table if exists testtable");
		conn.createStatement().execute("create table testtable(c0 int not null) engine=myisam");
		conn.createStatement().execute("alter table testtable add unique index testtable_i(c0)");
		
		conn.createStatement().execute("create temporary table testtable_td(_batch int not null,c0 int not null)");
		conn.createStatement().execute("create unique index testtable_td_b on testtable_td(_batch)");
		
		
		PreparedStatement ps;
		int num_changes = 0;
		
		ps = conn.prepareStatement("insert into testtable(c0) values(?)");
		while(num_changes<10000)
		{
			ps.setInt(1, num_changes);
			
			ps.addBatch();
			
			num_changes++;
			
			if((num_changes%1000) == 0)
			{
				ps.executeBatch();
				
				System.out.println("added "+num_changes);
			}
		}
		
		ps.close();
		
		int num_deletes = 3646;
		
		int i=0;
		ps = conn.prepareStatement("insert into testtable_td(_batch,c0) values(?,?)");
		while(i<num_deletes)
		{
			ps.setInt(1, i);
			ps.setInt(2, i);
			
			ps.addBatch();
			
			i++;
			
			if((i%1000) == 0)
			{
				ps.executeBatch();
				
				System.out.println("added "+i);
			}
		}
		
		ps.executeBatch();
		
		ps.close();
		
		
		
		String sql = "lock tables testtable write;\n"+
			"delete testtable from testtable_td force index(testtable_td_b) straight_join testtable on testtable.c0=testtable_td.c0 where testtable_td._batch>=? and testtable_td._batch<?;\n"+
			"unlock tables;";
			    		
		ps = conn.prepareStatement(sql);
		
		int bsize=100;
		
		for(int start_index=0, end_index=Math.min(bsize,num_deletes); start_index<num_changes; start_index=end_index, end_index=Math.min(start_index+bsize,num_deletes))
		{
			ps.clearParameters();
			ps.setInt(1, start_index);
			ps.setInt(2, end_index);
			ps.execute();
		
		    //ignore the results from the "lock_tables"
		    int c1 = ps.getUpdateCount();
		    boolean b1 = ps.getMoreResults();
		    
		    //should always be the results from the "delete"
		    int nrows_changed = ps.getUpdateCount();
		    
		    if(nrows_changed==-1)
				throw new SQLException("nrows_changed==-1"); //????
		    
			System.out.println("deleted "+nrows_changed+" between "+start_index+" and "+end_index);
		    
			bsize*=4;
		}
		
		conn.close();
	}
}

Suggested fix:
???
[19 Jan 2007 10:57] Tonci Grgin
Hi and thanks for your report.
Please provide me with:
  - JDK version
  - Results of the same test run in mysql command line client

Are you by any chance using "update ignore" syntax anywhere?
[19 Jan 2007 16:05] Tonci Grgin
I must admit I don't quite get it...

    /**
     *  Retrieves the current result as an update count;
     *  if the result is a <code>ResultSet</code> object or there are no more results, -1
     *  is returned. This method should be called only once per result.
     */
    int getUpdateCount() throws SQLException; 

Last SQL to be issued before *you* raise an exception is:
... where _batch>=3646 and _batch<3646; 
thus satisfying condition "or there are no more results". I would have called it a bug if it returned anything *but* -1.
[19 Jan 2007 18:02] P Eger
1) Testest the same behaviour in 2 JDK versions:

1.6.0-rc-b104
1.5.0_10-b03

2) Yes, "update ignore" is used elsewhere in the program, but not in this test case.

3) I think i see an error in my logic in that "start_index<num_changes" should be "start_index<num_deletes", but regardless of what the _batch numbers are, shouldn't "DELETE ... where _batch>=3646 and _batch<3646" produce a getUpdateCount() of 0 (zero)? IE it was a valid SQL statement that i am expecting a result from, + the fact that "DELETE ... where _batch>=0 and _batch<100" always does return >=0?
[21 Jan 2007 20:59] Tonci Grgin
Hi.

"3) I think i see an error in my logic in that "start_index<num_changes"
should be "start_index<num_deletes", but regardless of what the _batch
numbers are, shouldn't "DELETE ... where _batch>=3646 and _batch<3646"
produce a getUpdateCount() of 0 (zero)?"

I dissagree, the second condition, "no more result", is satisfied thus it should return -1. Otherwise you would not be able to tell that you reached the end of data.
[22 Jan 2007 17:29] P Eger
Respectfully, I must disagee. I think there is a fundamental difference between "no more results" and zero length results, that with the current behaviour, i am unable to distinguish. The *real* problem, IMO is that the same sequence of "simple" SQL statements produces very different output for getUpdateCount(), depending on what data is passed in. This is very counterintuitive/surprising and i would argue incorrect. There *was* an update that happened, it just also happened to not effect any rows. No?
[22 Jan 2007 17:56] Tonci Grgin
Hi. You might have a point here. Reopening.
[14 Feb 2007 1:13] Mark Matthews
I can't repeat this with C/J 5.0 head. I run your test program, and once you reach the "block" of deletes that terminates at 3646, and go one "block" past that, the program loops endlessly deleting (and reporting as such) 0 rows deleted.

Would you mind testing with a nightly snapshot of 5.0 from http://downloads.mysql.com/snapshots.php#connector-j

There's been at least one bug fix related to multistatement results, so I'd like to know if that fixes your issue too.

Thanks.

  -Mark
[14 Feb 2007 18:02] P Eger
Results: 

mysql-connector-java-3.1.14-bin.jar: broken
mysql-connector-java-5.0.4-bin.jar: broken
mysql-connector-java-5.0-nightly-20070214-bin.jar: working

P.S. This test code was distilled (improperly, hence the infinite looping) from the *real* code, i will give it a shot with the real code as well & report back. Any idea when next 5.0.X stable will be out?