// Copyright (c) 2001 Dustin Sallings <dustin@spy.net> package net.spy.db; import java.math.BigDecimal; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import junit.framework.TestCase; import net.spy.util.SpyConfig; import org.jmock.Mock; import org.jmock.core.constraint.IsEqual; import org.jmock.core.matcher.InvokeAtLeastOnceMatcher; import org.jmock.core.matcher.InvokeOnceMatcher; import org.jmock.core.stub.ReturnStub; import org.jmock.core.stub.StubSequence; /** * Test the primary key implementation. */ public class PKTest extends TestCase { @Override protected void tearDown() { GetPK.setInstance(null); } /** * Test the singleton operation. */ public void testSingleton() { GetPK ref=GetPK.getInstance(); assertSame(ref, GetPK.getInstance()); GetPK.setInstance(null); assertNotSame(ref, GetPK.getInstance()); } /** * Test basic primary key functionality. */ public void testPrimaryKeyByConf() throws Exception { SpyConfig conf=new SpyConfig(); conf.put("dbConnectionSource", SuccessConnectionSource.class.getName()); GetPK getpk=GetPK.getInstance(); BigDecimal previous=getpk.getPrimaryKey(conf, "test_table"); BigDecimal one=new BigDecimal(1); for(int i=0; i<1000; i++) { BigDecimal newKey=getpk.getPrimaryKey(conf, "test_table"); assertEquals("Keys not in sequence", previous.add(one), newKey); previous=newKey; } ConnectionSourceFactory cnf=ConnectionSourceFactory.getInstance(); MockConnectionSource mockSource= (MockConnectionSource)cnf.getConnectionSource(conf); mockSource.verifyConnections(); } /** * Test a PK with a missing key (no update). */ public void testPrimaryKeyMissingKey() throws Exception { SpyConfig conf=new SpyConfig(); conf.put("dbConnectionSource", MissingKeySource.class.getName()); GetPK getpk=GetPK.getInstance(); try { BigDecimal val=getpk.getPrimaryKey(conf, "test_table"); fail("Expected a missing key, got " + val); } catch(SQLException e) { assertEquals("Incorrect row count for" + " test_table (got 0) - " + "This usually means the primary key table does not have " + "test_table or there is a case mismatch.", e.getMessage()); } ConnectionSourceFactory cnf=ConnectionSourceFactory.getInstance(); MockConnectionSource mockSource= (MockConnectionSource)cnf.getConnectionSource(conf); mockSource.verifyConnections(); } abstract static class BaseConnectionSource extends MockConnectionSource { protected static final String UPDATE= "update primary_key\n" + "\tset primary_key=primary_key+incr_value\n" + "\twhere table_name=?\n"; protected static final String SELECT= "select\n" + "\ttable_name,\n" + "\t(primary_key - (incr_value-1)) as first_key,\n" + "\tprimary_key as last_key\n" + "from\n" + "\tprimary_key\n" + "where\n" + "\ttable_name=?\n"; protected PreparedStatement getUpdateStatementToReturn(int what) { // the prepared statement that will update the record Mock updatePst=new Mock(PreparedStatement.class); updatePst.expects(new InvokeOnceMatcher()).method("setQueryTimeout") .with(new IsEqual(new Integer(0))); updatePst.expects(new InvokeOnceMatcher()).method("setMaxRows") .with(new IsEqual(new Integer(0))); updatePst.expects(new InvokeOnceMatcher()).method("setString") .with(new IsEqual(new Integer(1)), new IsEqual("test_table")); updatePst.expects(new InvokeOnceMatcher()).method("executeUpdate") .will(new ReturnStub(new Integer(what))); updatePst.expects(new InvokeOnceMatcher()).method("close"); registerMock(updatePst); return((PreparedStatement)updatePst.proxy()); } protected ResultSet getResultSetWithRange( BigDecimal from, BigDecimal to) { Mock rsmd=new Mock(ResultSetMetaData.class); rsmd.expects(new InvokeAtLeastOnceMatcher()) .method("getColumnCount") .will(new ReturnStub(new Integer(2))); rsmd.expects(new InvokeAtLeastOnceMatcher()) .method("getColumnName").with(new IsEqual(new Integer(1))) .will(new ReturnStub("first_key")); rsmd.expects(new InvokeAtLeastOnceMatcher()) .method("getColumnName").with(new IsEqual(new Integer(2))) .will(new ReturnStub("last_key")); rsmd.expects(new InvokeAtLeastOnceMatcher()) .method("getColumnTypeName").with(new IsEqual(new Integer(1))) .will(new ReturnStub("BIGINT")); rsmd.expects(new InvokeAtLeastOnceMatcher()) .method("getColumnTypeName").with(new IsEqual(new Integer(2))) .will(new ReturnStub("BIGINT")); registerMock(rsmd); Mock rs=new Mock(ResultSet.class); rs.expects(new InvokeOnceMatcher()).method("getMetaData") .will(new ReturnStub(rsmd.proxy())); ArrayList<ReturnStub> ofResults=new ArrayList<ReturnStub>(); ofResults.add(new ReturnStub(Boolean.TRUE)); ofResults.add(new ReturnStub(Boolean.FALSE)); rs.expects(new InvokeAtLeastOnceMatcher()).method("next") .will(new StubSequence(ofResults)); rs.expects(new InvokeOnceMatcher()).method("getBigDecimal") .with(new IsEqual("first_key")) .will(new ReturnStub(from)); rs.expects(new InvokeOnceMatcher()).method("getBigDecimal") .with(new IsEqual("last_key")) .will(new ReturnStub(to)); rs.expects(new InvokeOnceMatcher()).method("close"); registerMock(rs); return((ResultSet)rs.proxy()); } protected PreparedStatement getSelectStatementWithRange( BigDecimal from, BigDecimal to) { // the prepared statement that will update the record Mock selectPst=new Mock(PreparedStatement.class); selectPst.expects(new InvokeOnceMatcher()).method("setQueryTimeout") .with(new IsEqual(new Integer(0))); selectPst.expects(new InvokeOnceMatcher()).method("setMaxRows") .with(new IsEqual(new Integer(0))); selectPst.expects(new InvokeOnceMatcher()).method("setString") .with(new IsEqual(new Integer(1)), new IsEqual("test_table")); selectPst.expects(new InvokeOnceMatcher()).method("executeQuery") .will(new ReturnStub(getResultSetWithRange(from, to))); selectPst.expects(new InvokeOnceMatcher()).method("close"); registerMock(selectPst); return((PreparedStatement)selectPst.proxy()); } protected void setupSuccesfulGetPK(Mock connMock, BigDecimal first, BigDecimal last) { connMock.expects(new InvokeOnceMatcher()).method("prepareStatement") .with(new IsEqual(UPDATE)) .will(new ReturnStub(getUpdateStatementToReturn(1))); connMock.expects(new InvokeOnceMatcher()).method("prepareStatement") .with(new IsEqual(SELECT)) .will(new ReturnStub(getSelectStatementWithRange(first, last))); } protected void setupDBMD(Mock connMock) { Mock dbMdMock=new Mock(DatabaseMetaData.class); dbMdMock.expects(new InvokeAtLeastOnceMatcher()) .method("getDatabaseProductName") .will(new ReturnStub("UnknownProduct")); connMock.expects(new InvokeAtLeastOnceMatcher()) .method("getMetaData") .will(new ReturnStub(dbMdMock.proxy())); } } public static class SuccessConnectionSource extends BaseConnectionSource { private static final int INCR=100; private static int startPoint=0; @Override protected void setupMock(Mock connMock, SpyConfig conf) { // autocommit will be enabled, and then disabled connMock.expects(new InvokeOnceMatcher()).method("setAutoCommit") .with(new IsEqual(Boolean.FALSE)).id("disableAutocommit"); setupSuccesfulGetPK(connMock, new BigDecimal(startPoint), new BigDecimal((INCR+startPoint) - 1)); // Increment the start point startPoint+=INCR; connMock.expects(new InvokeOnceMatcher()).method("commit"); connMock.expects(new InvokeOnceMatcher()).method("setAutoCommit") .with(new IsEqual(Boolean.TRUE)).id("enableAutocommit"); connMock.expects(new InvokeOnceMatcher()).method("close"); setupDBMD(connMock); } } public static class MissingKeySource extends BaseConnectionSource { @Override protected void setupMock(Mock connMock, SpyConfig conf) { // autocommit will be enabled, and then disabled connMock.expects(new InvokeOnceMatcher()).method("setAutoCommit") .with(new IsEqual(Boolean.FALSE)).id("disableAutocommit"); connMock.expects(new InvokeOnceMatcher()).method("prepareStatement") .with(new IsEqual(UPDATE)) .will(new ReturnStub(getUpdateStatementToReturn(0))); connMock.expects(new InvokeOnceMatcher()).method("rollback"); connMock.expects(new InvokeOnceMatcher()).method("setAutoCommit") .with(new IsEqual(Boolean.TRUE)).id("enableAutocommit"); connMock.expects(new InvokeOnceMatcher()).method("close"); setupDBMD(connMock); } } }