| // Copyright (C) 2011 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gwtorm.jdbc; |
| |
| |
| import static java.util.Arrays.asList; |
| import static org.junit.Assert.assertSame; |
| import static org.junit.Assert.fail; |
| import static org.mockito.Mockito.CALLS_REAL_METHODS; |
| import static org.mockito.Mockito.atLeastOnce; |
| import static org.mockito.Mockito.doNothing; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.verifyZeroInteractions; |
| import static org.mockito.Mockito.when; |
| |
| import com.google.gwtorm.client.Key; |
| import com.google.gwtorm.schema.sql.SqlDialect; |
| import com.google.gwtorm.server.Access; |
| import com.google.gwtorm.server.OrmConcurrencyException; |
| import com.google.gwtorm.server.OrmException; |
| import com.google.gwtorm.server.ResultSet; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.mockito.stubbing.OngoingStubbing; |
| |
| import java.sql.BatchUpdateException; |
| import java.sql.Connection; |
| import java.sql.PreparedStatement; |
| import java.sql.SQLException; |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| public class TestJdbcAccess { |
| |
| private static final String INSERT = "insert"; |
| private static final String UPDATE = "update"; |
| private static final String DELETE = "delete"; |
| |
| private static final SqlDialect DIALECT = mock(SqlDialect.class, |
| CALLS_REAL_METHODS); |
| |
| private static final Iterable<Data> NO_DATA = asList(); |
| private static Iterable<Data> ONE_ROW = asList(new Data(1)); |
| private static Iterable<Data> TWO_ROWS = asList(new Data(1), new Data(2)); |
| private Connection conn; |
| |
| |
| private PreparedStatement stubStatementWithUpdateCounts(String command, |
| final int... updateCounts) throws SQLException { |
| PreparedStatement ps = mock(PreparedStatement.class); |
| |
| // batching |
| doNothing().when(ps).addBatch(); |
| when(ps.executeBatch()).thenReturn(updateCounts); |
| |
| // non-batching |
| if (updateCounts != null && updateCounts.length > 0) { |
| OngoingStubbing<Integer> stubber = when(ps.executeUpdate()); |
| for (int updateCount : updateCounts) { |
| stubber = stubber.thenReturn(updateCount); |
| } |
| } |
| |
| when(conn.prepareStatement(command)).thenReturn(ps); |
| return ps; |
| } |
| |
| private PreparedStatement stubStatementThrowExceptionOnExecute( |
| String command, SQLException exception) throws SQLException { |
| PreparedStatement ps = mock(PreparedStatement.class); |
| doNothing().when(ps).addBatch(); |
| when(ps.executeBatch()).thenThrow(exception); |
| when(ps.executeUpdate()).thenThrow(exception); |
| when(conn.prepareStatement(command)).thenReturn(ps); |
| return ps; |
| } |
| |
| private JdbcAccess<Data, Data.DataKey> createJdbcAccess( |
| final SqlDialect dialect) { |
| JdbcSchema schema = setupSchema(dialect); |
| |
| JdbcAccess<Data, Data.DataKey> classUnderTest = new DataJdbcAccess(schema); |
| return classUnderTest; |
| } |
| |
| private JdbcAccess<Data, Data.DataKey> createClassUnderTest() { |
| return createJdbcAccess(DIALECT); |
| } |
| |
| private JdbcSchema setupSchema(final SqlDialect dialect) { |
| @SuppressWarnings("rawtypes") |
| Database db = mock(Database.class); |
| try { |
| when(db.getDialect()).thenReturn(dialect); |
| |
| when(db.newConnection()).thenReturn(conn); |
| |
| JdbcSchema schema = new Schema(db); |
| return schema; |
| } catch (OrmException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| private static void assertNotUsed(PreparedStatement insert) { |
| verifyZeroInteractions(insert); |
| } |
| |
| private static void assertUsedBatchingOnly(PreparedStatement statement) |
| throws SQLException { |
| verify(statement, atLeastOnce()).addBatch(); |
| verify(statement).executeBatch(); |
| verify(statement, never()).executeUpdate(); |
| } |
| |
| private static void assertExpectedIdsUsed(PreparedStatement statement, |
| int... ids) throws SQLException { |
| |
| Set<Integer> notSet = new HashSet<Integer>(2); |
| notSet.add(1); |
| notSet.add(2); |
| |
| for (int id : ids) { |
| verify(statement).setInt(1, id); |
| notSet.remove(Integer.valueOf(id)); |
| } |
| |
| for (Integer id : notSet) { |
| verify(statement, never()).setInt(1, id); |
| } |
| } |
| |
| @Before |
| public void setup() { |
| conn = mock(Connection.class); |
| } |
| |
| @Test |
| public void testInsertNothing() throws OrmException { |
| setup(); |
| createClassUnderTest().insert(NO_DATA); |
| } |
| |
| @Test |
| public void testInsertOne() throws OrmException, SQLException { |
| PreparedStatement insert = stubStatementWithUpdateCounts(INSERT, 1); |
| |
| createClassUnderTest().insert(ONE_ROW); |
| |
| assertUsedBatchingOnly(insert); |
| } |
| |
| @Test |
| public void testInsertOneDBException() throws OrmException, SQLException { |
| SQLException exception = new BatchUpdateException(); |
| PreparedStatement insert = |
| stubStatementThrowExceptionOnExecute(INSERT, exception); |
| JdbcAccess<Data, Data.DataKey> classUnderTest = createClassUnderTest(); |
| try { |
| classUnderTest.insert(ONE_ROW); |
| fail("missingException"); |
| } catch (OrmException e) { |
| // expected |
| assertSame(e.getCause(), exception); |
| } |
| |
| assertUsedBatchingOnly(insert); |
| } |
| |
| @Test |
| public void testUpdateNothing() throws OrmException { |
| createClassUnderTest().update(NO_DATA); |
| } |
| |
| @Test |
| public void testUpdateOne() throws OrmException, SQLException { |
| PreparedStatement update = stubStatementWithUpdateCounts(UPDATE, 1); |
| |
| createClassUnderTest().update(ONE_ROW); |
| |
| assertUsedBatchingOnly(update); |
| } |
| |
| @Test |
| public void testUpdateOneDBException() throws OrmException, SQLException { |
| SQLException exception = new BatchUpdateException(); |
| PreparedStatement update = |
| stubStatementThrowExceptionOnExecute(UPDATE, exception); |
| JdbcAccess<Data, Data.DataKey> classUnderTest = createClassUnderTest(); |
| try { |
| classUnderTest.update(ONE_ROW); |
| fail("missingException"); |
| } catch (OrmException e) { |
| // expected |
| assertSame(e.getCause(), exception); |
| } |
| |
| assertUsedBatchingOnly(update); |
| } |
| |
| @Test |
| public void testUpdateOneConcurrentlyModifiedException() throws SQLException, |
| OrmException { |
| PreparedStatement update = stubStatementWithUpdateCounts(UPDATE, 0); |
| JdbcAccess<Data, Data.DataKey> classUnderTest = createClassUnderTest(); |
| try { |
| classUnderTest.update(ONE_ROW); |
| fail("missing OrmConcurrencyException"); |
| } catch (OrmConcurrencyException e) { |
| // expected |
| } |
| assertUsedBatchingOnly(update); |
| } |
| |
| @Test |
| public void testUpsertNothing() throws OrmException, SQLException { |
| createClassUnderTest().upsert(NO_DATA); |
| } |
| |
| @Test |
| public void testUpsertOneExisting() throws OrmException, SQLException { |
| PreparedStatement update = stubStatementWithUpdateCounts(UPDATE, 1); |
| PreparedStatement insert = stubStatementWithUpdateCounts(INSERT); |
| |
| createClassUnderTest().upsert(ONE_ROW); |
| |
| assertUsedBatchingOnly(update); |
| assertNotUsed(insert); |
| } |
| |
| @Test |
| public void testUpsertOneException() throws OrmException, SQLException { |
| SQLException exception = new BatchUpdateException(); |
| PreparedStatement update = |
| stubStatementThrowExceptionOnExecute(UPDATE, exception); |
| JdbcAccess<Data, Data.DataKey> classUnderTest = createClassUnderTest(); |
| try { |
| classUnderTest.upsert(ONE_ROW); |
| fail("missingException"); |
| } catch (OrmException e) { |
| // expected |
| assertSame(e.getCause(), exception); |
| } |
| |
| assertUsedBatchingOnly(update); |
| } |
| |
| @Test |
| public void testUpsertOneNotExisting() throws OrmException, SQLException { |
| PreparedStatement update = stubStatementWithUpdateCounts(UPDATE); |
| PreparedStatement insert = stubStatementWithUpdateCounts(INSERT, 1); |
| |
| createClassUnderTest().upsert(ONE_ROW); |
| |
| assertUsedBatchingOnly(update); |
| assertUsedBatchingOnly(insert); |
| assertExpectedIdsUsed(insert, 1); |
| } |
| |
| @Test |
| public void testUpsertTwoNotExistingZeroLengthArray() throws SQLException, |
| OrmException { |
| PreparedStatement update = stubStatementWithUpdateCounts(UPDATE); |
| PreparedStatement insert = stubStatementWithUpdateCounts(INSERT, 1, 1); |
| |
| createClassUnderTest().upsert(TWO_ROWS); |
| |
| assertUsedBatchingOnly(update); |
| assertUsedBatchingOnly(insert); |
| assertExpectedIdsUsed(insert, 1, 2); |
| } |
| |
| @Test |
| public void testUpsertTwoNotExisting() throws SQLException, OrmException { |
| PreparedStatement update = stubStatementWithUpdateCounts(UPDATE, 0, 0); |
| PreparedStatement insert = stubStatementWithUpdateCounts(INSERT, 1, 1); |
| |
| createClassUnderTest().upsert(TWO_ROWS); |
| |
| assertUsedBatchingOnly(update); |
| assertUsedBatchingOnly(insert); |
| assertExpectedIdsUsed(insert, 1, 2); |
| } |
| |
| @Test |
| public void testUpsertTwoBothExisting() throws SQLException, OrmException { |
| PreparedStatement update = stubStatementWithUpdateCounts(UPDATE, 1, 1); |
| PreparedStatement insert = stubStatementWithUpdateCounts(INSERT); |
| |
| createClassUnderTest().upsert(TWO_ROWS); |
| |
| assertUsedBatchingOnly(update); |
| assertNotUsed(insert); |
| } |
| |
| @Test |
| public void testUpsertTwoFirstExisting() throws SQLException, OrmException { |
| PreparedStatement update = stubStatementWithUpdateCounts(UPDATE, 1, 0); |
| PreparedStatement insert = stubStatementWithUpdateCounts(INSERT, 1); |
| |
| createClassUnderTest().upsert(TWO_ROWS); |
| |
| assertUsedBatchingOnly(update); |
| assertUsedBatchingOnly(insert); |
| assertExpectedIdsUsed(insert, 2); |
| } |
| |
| @Test |
| public void testUpsertTwoSecondExisting() throws SQLException, OrmException { |
| PreparedStatement update = stubStatementWithUpdateCounts(UPDATE, 0, 1); |
| PreparedStatement insert = stubStatementWithUpdateCounts(INSERT, 1); |
| |
| createClassUnderTest().upsert(TWO_ROWS); |
| |
| assertUsedBatchingOnly(update); |
| assertUsedBatchingOnly(insert); |
| assertExpectedIdsUsed(insert, 1); |
| } |
| |
| @Test |
| public void testUpsertTwoUpdateCountsAreNull() throws SQLException, |
| OrmException { |
| PreparedStatement update = stubStatementWithUpdateCounts(UPDATE, null); |
| PreparedStatement insert = stubStatementWithUpdateCounts(INSERT, 1, 1); |
| |
| createClassUnderTest().upsert(TWO_ROWS); |
| |
| assertUsedBatchingOnly(update); |
| assertUsedBatchingOnly(insert); |
| assertExpectedIdsUsed(insert, 1, 2); |
| } |
| |
| @Test |
| public void testDeleteOneExisting() throws SQLException, OrmException { |
| PreparedStatement delete = stubStatementWithUpdateCounts(DELETE, 1); |
| |
| createClassUnderTest().delete(ONE_ROW); |
| |
| assertUsedBatchingOnly(delete); |
| } |
| |
| @Test |
| public void testDeleteOneNotExisting() throws SQLException, OrmException { |
| PreparedStatement delete = stubStatementWithUpdateCounts(DELETE, 0); |
| JdbcAccess<Data, Data.DataKey> classUnderTest = createClassUnderTest(); |
| try { |
| classUnderTest.delete(ONE_ROW); |
| fail("missing OrmConcurrencyException"); |
| } catch (OrmConcurrencyException e) { |
| // expected |
| } |
| |
| assertUsedBatchingOnly(delete); |
| } |
| |
| private class Schema extends JdbcSchema { |
| |
| protected Schema(Database<?> d) throws OrmException { |
| super(d); |
| } |
| |
| @Override |
| public Access<?, ?>[] allRelations() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| } |
| |
| private static class Data { |
| |
| private final int id; |
| |
| Data(int anId) { |
| id = anId; |
| } |
| |
| private static class DataKey implements Key<Key<?>> { |
| |
| @Override |
| public com.google.gwtorm.client.Key<?> getParentKey() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public void fromString(String in) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| } |
| |
| } |
| |
| private static class DataJdbcAccess extends JdbcAccess<Data, Data.DataKey> { |
| |
| protected DataJdbcAccess(JdbcSchema s) { |
| super(s); |
| } |
| |
| public String getRelationName() { |
| return "Data"; |
| } |
| |
| @Override |
| public com.google.gwtorm.jdbc.TestJdbcAccess.Data.DataKey primaryKey( |
| Data entity) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public Data get(com.google.gwtorm.jdbc.TestJdbcAccess.Data.DataKey key) |
| throws OrmException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| protected Data newEntityInstance() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| protected String getInsertOneSql() { |
| return INSERT; |
| } |
| |
| @Override |
| protected String getUpdateOneSql() { |
| return UPDATE; |
| } |
| |
| @Override |
| protected String getDeleteOneSql() { |
| return DELETE; |
| } |
| |
| @Override |
| protected void bindOneInsert(PreparedStatement ps, Data entity) |
| throws SQLException { |
| ps.setInt(1, entity.id); |
| } |
| |
| @Override |
| protected void bindOneUpdate(PreparedStatement ps, Data entity) |
| throws SQLException { |
| ps.setInt(1, entity.id); |
| } |
| |
| @Override |
| protected void bindOneDelete(PreparedStatement ps, Data entity) |
| throws SQLException { |
| ps.setInt(1, entity.id); |
| } |
| |
| @Override |
| protected void bindOneFetch(java.sql.ResultSet rs, Data entity) |
| throws SQLException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public int getRelationID() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public ResultSet<Data> iterateAllEntities() throws OrmException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| } |
| } |