/** * Copyright (C) 2009-2014 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.server.test.it.isolation; import com.foundationdb.sql.embedded.JDBCConnection; import com.foundationdb.server.error.ErrorCode; import java.sql.*; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; import java.util.HashMap; import java.util.Map; public class ReadCommittedIsolationIT extends IsolationITBase { private static final int NROWS = 10; @Override public Map<String,String> startupConfigProperties() { Map<String,String> props = new HashMap<>(); props.putAll(super.startupConfigProperties()); props.put("fdbsql.fdb.periodically_commit.scan_limit", "2"); // With the ordinary amount of lookahead, won't see changes made in // the middle because the scans will have already been done. props.put("fdbsql.pipeline.groupLookup.lookaheadQuantum", "2"); return props; } @Before public void populate() { int tid = createTable(SCHEMA_NAME, "t1", "id INT PRIMARY KEY, n INT"); for (int i = 0; i < NROWS; i++) { writeRow(tid, i, i); } } @Test @Isolation(JDBCConnection.TRANSACTION_READ_COMMITTED_NO_SNAPSHOT) public void scanIdle() throws SQLException { try (Connection conn = getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT id, n FROM t1")) { for (int i = 0; i < NROWS; i++) { assertTrue("more rows", rs.next()); assertEquals("id", i, rs.getInt(1)); assertEquals("n", i, rs.getInt(2)); } } } @Test @Isolation(JDBCConnection.TRANSACTION_READ_COMMITTED_NO_SNAPSHOT) public void scanWhileUpdate() throws SQLException { try (Connection conn = getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT id, n FROM t1")) { for (int i = 0; i < NROWS; i++) { if (i == 3) { try (Connection conn2 = getAutoCommitConnection(); Statement stmt2 = conn2.createStatement()) { assertEquals(1, stmt2.executeUpdate("UPDATE t1 SET n = n + 1 WHERE id = " + NROWS / 2)); } } assertTrue("more rows", rs.next()); assertEquals("id", i, rs.getInt(1)); assertEquals("n", (i == NROWS / 2) ? i + 1 : i, rs.getInt(2)); } } } @Test @Isolation(JDBCConnection.TRANSACTION_READ_COMMITTED_NO_SNAPSHOT) public void scanWhileDelete() throws SQLException { try (Connection conn = getConnection(); Statement stmt = conn.createStatement(); // A plan that will have a nested scan. ResultSet rs = stmt.executeQuery("SELECT id, n FROM t1 WHERE id > 0")) { for (int i = 1; i < NROWS; i++) { if (i == 2) { try (Connection conn2 = getAutoCommitConnection(); Statement stmt2 = conn2.createStatement()) { assertEquals(1, stmt2.executeUpdate("DELETE FROM t1 WHERE id = " + NROWS / 2)); } } if (i == NROWS / 2) continue; assertTrue("more rows", rs.next()); assertEquals("id", i, rs.getInt(1)); assertEquals("n", i, rs.getInt(2)); } } } @Test @Isolation(JDBCConnection.TRANSACTION_READ_COMMITTED_NO_SNAPSHOT) public void scanWhileAlter() throws SQLException { try (Connection conn = getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM t1")) { for (int i = 0; i < NROWS; i++) { if (i == 2) { try (Connection conn2 = getAutoCommitConnection(); Statement stmt2 = conn2.createStatement()) { stmt2.execute("ALTER TABLE t1 DROP COLUMN n"); } } if (!rs.next()) break; // Only check that it vanished. assertEquals("id", i, rs.getInt(1)); assertEquals("n", i, rs.getInt(2)); } } } @Test @Isolation(Connection.TRANSACTION_SERIALIZABLE) public void updateThenReadOnly() throws SQLException { try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) { assertEquals(1, stmt.executeUpdate("UPDATE t1 SET n = n + 1 WHERE id = 1")); conn.setTransactionIsolation(JDBCConnection.TRANSACTION_READ_COMMITTED_NO_SNAPSHOT); assumeTrue(conn.getTransactionIsolation() == JDBCConnection.TRANSACTION_READ_COMMITTED_NO_SNAPSHOT); String sqlState = null; try { assertEquals(1, stmt.executeUpdate("UPDATE t1 SET n = n + 1 WHERE id = 2")); } catch (SQLException ex) { sqlState = ex.getSQLState(); } assertEquals("Expected read only", ErrorCode.TRANSACTION_READ_ONLY.getFormattedValue(), sqlState); // See updated results in new read-only transaction. try (ResultSet rs = stmt.executeQuery("SELECT id, n FROM t1 WHERE id IN (1,2) ORDER BY 1")) { assertTrue(rs.next()); assertEquals(1, rs.getInt(1)); assertEquals(2, rs.getInt(2)); assertTrue(rs.next()); assertEquals(2, rs.getInt(1)); assertEquals(2, rs.getInt(2)); } conn.rollback(); } // Results committed before rollback. try (Connection conn = getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT id, n FROM t1 WHERE id IN (1,2) ORDER BY 1")) { assertTrue(rs.next()); assertEquals(1, rs.getInt(1)); assertEquals(2, rs.getInt(2)); assertTrue(rs.next()); assertEquals(2, rs.getInt(1)); assertEquals(2, rs.getInt(2)); } } }