package org.briarproject.db; import org.briarproject.BriarTestCase; import org.briarproject.TestUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.File; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import java.util.Random; import static java.sql.Types.BINARY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; public class BasicH2Test extends BriarTestCase { private static final String CREATE_TABLE = "CREATE TABLE foo (uniqueId BINARY(32), name VARCHAR NOT NULL)"; private static final int BATCH_SIZE = 100; private final File testDir = TestUtils.getTestDirectory(); private final File db = new File(testDir, "db"); private final String url = "jdbc:h2:" + db.getAbsolutePath(); private Connection connection = null; @Before public void setUp() throws Exception { testDir.mkdirs(); Class.forName("org.h2.Driver"); connection = DriverManager.getConnection(url); } @Test public void testInsertUpdateAndDelete() throws Exception { // Create the table createTable(connection); // Generate an ID and two names byte[] id = new byte[32]; new Random().nextBytes(id); String oldName = TestUtils.createRandomString(50); String newName = TestUtils.createRandomString(50); // Insert the ID and old name into the table insertRow(id, oldName); // Check that the old name can be retrieved using the ID assertTrue(rowExists(id)); assertEquals(oldName, getName(id)); // Update the name updateRow(id, newName); // Check that the new name can be retrieved using the ID assertTrue(rowExists(id)); assertEquals(newName, getName(id)); // Delete the row from the table assertTrue(deleteRow(id)); // Check that the row no longer exists assertFalse(rowExists(id)); // Deleting the row again should have no effect assertFalse(deleteRow(id)); } @Test public void testBatchInsertUpdateAndDelete() throws Exception { // Create the table createTable(connection); // Generate some IDs and two sets of names byte[][] ids = new byte[BATCH_SIZE][32]; String[] oldNames = new String[BATCH_SIZE]; String[] newNames = new String[BATCH_SIZE]; Random random = new Random(); for (int i = 0; i < BATCH_SIZE; i++) { random.nextBytes(ids[i]); oldNames[i] = TestUtils.createRandomString(50); newNames[i] = TestUtils.createRandomString(50); } // Insert the IDs and old names into the table as a batch insertBatch(ids, oldNames); // Update the names as a batch updateBatch(ids, newNames); // Check that the new names can be retrieved using the IDs for (int i = 0; i < BATCH_SIZE; i++) { assertTrue(rowExists(ids[i])); assertEquals(newNames[i], getName(ids[i])); } // Delete the rows as a batch boolean[] deleted = deleteBatch(ids); // Check that the rows no longer exist for (int i = 0; i < BATCH_SIZE; i++) { assertTrue(deleted[i]); assertFalse(rowExists(ids[i])); } // Deleting the rows again should have no effect deleted = deleteBatch(ids); for (int i = 0; i < BATCH_SIZE; i++) assertFalse(deleted[i]); } @Test public void testSortOrder() throws Exception { byte[] first = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; byte[] second = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127 }; byte[] third = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 255 }; // Create the table createTable(connection); // Insert the rows insertRow(first, "first"); insertRow(second, "second"); insertRow(third, "third"); insertRow(null, "null"); // Check the ordering of the < operator: the null ID is not comparable assertNull(getPredecessor(first)); assertEquals("first", getPredecessor(second)); assertEquals("second", getPredecessor(third)); assertNull(getPredecessor(null)); // Check the ordering of ORDER BY: nulls come first List names = getNames(); assertEquals(4, names.size()); assertEquals("null", names.get(0)); assertEquals("first", names.get(1)); assertEquals("second", names.get(2)); assertEquals("third", names.get(3)); } private void createTable(Connection connection) throws SQLException { try { Statement s = connection.createStatement(); s.executeUpdate(CREATE_TABLE); s.close(); } catch (SQLException e) { connection.close(); throw e; } } private void insertRow(byte[] id, String name) throws SQLException { String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)"; try { PreparedStatement ps = connection.prepareStatement(sql); if (id == null) ps.setNull(1, BINARY); else ps.setBytes(1, id); ps.setString(2, name); int affected = ps.executeUpdate(); assertEquals(1, affected); ps.close(); } catch (SQLException e) { connection.close(); throw e; } } private boolean rowExists(byte[] id) throws SQLException { assertNotNull(id); String sql = "SELECT NULL FROM foo WHERE uniqueID = ?"; try { PreparedStatement ps = connection.prepareStatement(sql); ps.setBytes(1, id); ResultSet rs = ps.executeQuery(); boolean found = rs.next(); assertFalse(rs.next()); rs.close(); ps.close(); return found; } catch (SQLException e) { connection.close(); throw e; } } private String getName(byte[] id) throws SQLException { assertNotNull(id); String sql = "SELECT name FROM foo WHERE uniqueID = ?"; try { PreparedStatement ps = connection.prepareStatement(sql); ps.setBytes(1, id); ResultSet rs = ps.executeQuery(); assertTrue(rs.next()); String name = rs.getString(1); assertFalse(rs.next()); rs.close(); ps.close(); return name; } catch (SQLException e) { connection.close(); throw e; } } private void updateRow(byte[] id, String name) throws SQLException { String sql = "UPDATE foo SET name = ? WHERE uniqueId = ?"; try { PreparedStatement ps = connection.prepareStatement(sql); if (id == null) ps.setNull(2, BINARY); else ps.setBytes(2, id); ps.setString(1, name); assertEquals(1, ps.executeUpdate()); ps.close(); } catch (SQLException e) { connection.close(); throw e; } } private boolean deleteRow(byte[] id) throws SQLException { String sql = "DELETE FROM foo WHERE uniqueId = ?"; try { PreparedStatement ps = connection.prepareStatement(sql); if (id == null) ps.setNull(1, BINARY); else ps.setBytes(1, id); int affected = ps.executeUpdate(); ps.close(); return affected == 1; } catch (SQLException e) { connection.close(); throw e; } } private void insertBatch(byte[][] ids, String[] names) throws SQLException { assertEquals(ids.length, names.length); String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)"; try { PreparedStatement ps = connection.prepareStatement(sql); for (int i = 0; i < ids.length; i++) { if (ids[i] == null) ps.setNull(1, BINARY); else ps.setBytes(1, ids[i]); ps.setString(2, names[i]); ps.addBatch(); } int[] batchAffected = ps.executeBatch(); assertEquals(ids.length, batchAffected.length); for (int i = 0; i < batchAffected.length; i++) { assertEquals(1, batchAffected[i]); } ps.close(); } catch (SQLException e) { connection.close(); throw e; } } private void updateBatch(byte[][] ids, String[] names) throws SQLException { assertEquals(ids.length, names.length); String sql = "UPDATE foo SET name = ? WHERE uniqueId = ?"; try { PreparedStatement ps = connection.prepareStatement(sql); for (int i = 0; i < ids.length; i++) { if (ids[i] == null) ps.setNull(2, BINARY); else ps.setBytes(2, ids[i]); ps.setString(1, names[i]); ps.addBatch(); } int[] batchAffected = ps.executeBatch(); assertEquals(ids.length, batchAffected.length); for (int i = 0; i < batchAffected.length; i++) assertEquals(1, batchAffected[i]); ps.close(); } catch (SQLException e) { connection.close(); throw e; } } private boolean[] deleteBatch(byte[][] ids) throws SQLException { String sql = "DELETE FROM foo WHERE uniqueId = ?"; try { PreparedStatement ps = connection.prepareStatement(sql); for (int i = 0; i < ids.length; i++) { if (ids[i] == null) ps.setNull(1, BINARY); else ps.setBytes(1, ids[i]); ps.addBatch(); } int[] batchAffected = ps.executeBatch(); assertEquals(ids.length, batchAffected.length); boolean[] ret = new boolean[ids.length]; for (int i = 0; i < batchAffected.length; i++) ret[i] = batchAffected[i] == 1; ps.close(); return ret; } catch (SQLException e) { connection.close(); throw e; } } private String getPredecessor(byte[] id) throws SQLException { String sql = "SELECT name FROM foo WHERE uniqueId < ?" + " ORDER BY uniqueId DESC LIMIT ?"; try { PreparedStatement ps = connection.prepareStatement(sql); ps.setBytes(1, id); ps.setInt(2, 1); ResultSet rs = ps.executeQuery(); String name = rs.next() ? rs.getString(1) : null; assertFalse(rs.next()); rs.close(); ps.close(); return name; } catch (SQLException e) { connection.close(); throw e; } } private List getNames() throws SQLException { String sql = "SELECT name FROM foo ORDER BY uniqueId"; List names = new ArrayList(); try { PreparedStatement ps = connection.prepareStatement(sql); ResultSet rs = ps.executeQuery(); while (rs.next()) names.add(rs.getString(1)); rs.close(); ps.close(); return names; } catch (SQLException e) { connection.close(); throw e; } } @After public void tearDown() throws Exception { if (connection != null) connection.close(); TestUtils.deleteTestDirectory(testDir); } }