/* * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, * Version 1.0, and under the Eclipse Public License, Version 1.0 * (http://h2database.com/html/license.html). * Initial Developer: H2 Group */ package org.h2.test.db; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Random; import org.h2.constant.ErrorCode; import org.h2.constant.SysProperties; import org.h2.engine.Database; import org.h2.jdbc.JdbcConnection; import org.h2.test.TestBase; import org.h2.util.JdbcUtils; /** * Tests simulated power off conditions. */ public class TestPowerOff extends TestBase { private String dbName = "powerOff"; private String dir, url; private int maxPowerOffCount; /** * Run just this test. * * @param a ignored */ public static void main(String... a) throws Exception { TestBase.createCaller().init().test(); } public void test() throws SQLException { if (config.memory) { return; } if (config.big || config.googleAppEngine) { dir = getBaseDir(); url = dbName; } else { dir = "memFS:"; url = "memFS:/" + dbName; } url += ";FILE_LOCK=NO;TRACE_LEVEL_FILE=0"; testLobCrash(); testSummaryCrash(); testCrash(); testShutdown(); testMemoryTables(); testPersistentTables(); deleteDb(dir, dbName); } private void testLobCrash() throws SQLException { if (config.networked) { return; } deleteDb(dir, dbName); Connection conn = getConnection(url); Statement stat = conn.createStatement(); stat.execute("create table test(id identity, data clob)"); conn.close(); conn = getConnection(url); stat = conn.createStatement(); stat.execute("set write_delay 0"); ((JdbcConnection) conn).setPowerOffCount(Integer.MAX_VALUE); stat.execute("insert into test values(null, space(11000))"); int max = Integer.MAX_VALUE - ((JdbcConnection) conn).getPowerOffCount(); for (int i = 0; i < max + 10; i++) { conn = getConnection(url); stat = conn.createStatement(); stat.execute("insert into test values(null, space(11000))"); stat.execute("set write_delay 0"); ((JdbcConnection) conn).setPowerOffCount(i); try { stat.execute("insert into test values(null, space(11000))"); } catch (SQLException e) { // ignore } try { conn.close(); } catch (SQLException e) { // ignore } } } private void testSummaryCrash() throws SQLException { if (config.networked) { return; } deleteDb(dir, dbName); Connection conn = getConnection(url); Statement stat = conn.createStatement(); for (int i = 0; i < 10; i++) { stat.execute("CREATE TABLE TEST" + i + "(ID INT PRIMARY KEY, NAME VARCHAR)"); for (int j = 0; j < 10; j++) { stat.execute("INSERT INTO TEST" + i + " VALUES(" + j + ", 'Hello')"); } } for (int i = 0; i < 10; i += 2) { stat.execute("DROP TABLE TEST" + i); } stat.execute("SET WRITE_DELAY 0"); stat.execute("CHECKPOINT"); for (int j = 0; j < 10; j++) { stat.execute("INSERT INTO TEST1 VALUES(" + (10 + j) + ", 'World')"); } stat.execute("SHUTDOWN IMMEDIATELY"); JdbcUtils.closeSilently(conn); conn = getConnection(url); stat = conn.createStatement(); for (int i = 1; i < 10; i += 2) { ResultSet rs = stat.executeQuery("SELECT * FROM TEST" + i + " ORDER BY ID"); for (int j = 0; j < 10; j++) { rs.next(); assertEquals(j, rs.getInt(1)); assertEquals("Hello", rs.getString(2)); } if (i == 1) { for (int j = 0; j < 10; j++) { rs.next(); assertEquals(j + 10, rs.getInt(1)); assertEquals("World", rs.getString(2)); } } assertFalse(rs.next()); } conn.close(); } private void testCrash() throws SQLException { if (config.networked) { return; } deleteDb(dir, dbName); Random random = new Random(1); SysProperties.runFinalize = false; int repeat = getSize(1, 20); for (int i = 0; i < repeat; i++) { Connection conn = getConnection(url); conn.close(); conn = getConnection(url); Statement stat = conn.createStatement(); stat.execute("SET WRITE_DELAY 0"); ((JdbcConnection) conn).setPowerOffCount(random.nextInt(100)); try { stat.execute("DROP TABLE IF EXISTS TEST"); stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); conn.setAutoCommit(false); int len = getSize(3, 100); for (int j = 0; j < len; j++) { stat.execute("INSERT INTO TEST VALUES(" + j + ", 'Hello')"); if (random.nextInt(5) == 0) { conn.commit(); } if (random.nextInt(10) == 0) { stat.execute("DROP TABLE IF EXISTS TEST"); stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); } } stat.execute("DROP TABLE IF EXISTS TEST"); conn.close(); } catch (SQLException e) { if (!e.getSQLState().equals("90098")) { TestBase.logError("power", e); } } } SysProperties.runFinalize = true; } private void testShutdown() throws SQLException { deleteDb(dir, dbName); Connection conn = getConnection(url); Statement stat = conn.createStatement(); stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); stat.execute("INSERT INTO TEST VALUES(1, 'Hello')"); stat.execute("SHUTDOWN"); conn.close(); conn = getConnection(url); stat = conn.createStatement(); ResultSet rs = stat.executeQuery("SELECT * FROM TEST"); assertTrue(rs.next()); assertFalse(rs.next()); conn.close(); } private void testMemoryTables() throws SQLException { if (config.networked) { return; } deleteDb(dir, dbName); Connection conn = getConnection(url); Statement stat = conn.createStatement(); stat.execute("CREATE MEMORY TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); stat.execute("INSERT INTO TEST VALUES(1, 'Hello')"); stat.execute("CHECKPOINT"); ((JdbcConnection) conn).setPowerOffCount(1); try { stat.execute("INSERT INTO TEST VALUES(2, 'Hello')"); stat.execute("INSERT INTO TEST VALUES(3, 'Hello')"); stat.execute("CHECKPOINT"); fail(); } catch (SQLException e) { assertKnownException(e); } ((JdbcConnection) conn).setPowerOffCount(0); try { conn.close(); } catch (SQLException e) { // ignore } conn = getConnection(url); stat = conn.createStatement(); ResultSet rs = stat.executeQuery("SELECT COUNT(*) FROM TEST"); rs.next(); assertEquals(1, rs.getInt(1)); conn.close(); } private void testPersistentTables() throws SQLException { if (config.networked) { return; } if (config.cipher != null) { // this would take too long (setLength uses // individual writes, many thousand operations) return; } deleteDb(dir, dbName); // ((JdbcConnection)conn).setPowerOffCount(Integer.MAX_VALUE); testRun(true); int max = maxPowerOffCount; trace("max=" + max); runTest(0, max, true); recoverAndCheckConsistency(); runTest(0, max, false); recoverAndCheckConsistency(); } private void runTest(int min, int max, boolean withConsistencyCheck) throws SQLException { for (int i = min; i < max; i++) { deleteDb(dir, dbName); Database.setInitialPowerOffCount(i); int expect = testRun(false); if (withConsistencyCheck) { int got = recoverAndCheckConsistency(); trace("test " + i + " of " + max + " expect=" + expect + " got=" + got); } else { trace("test " + i + " of " + max + " expect=" + expect); } } Database.setInitialPowerOffCount(0); } private int testRun(boolean init) throws SQLException { if (init) { Database.setInitialPowerOffCount(Integer.MAX_VALUE); } int state = 0; try { Connection conn = getConnection(url); Statement stat = conn.createStatement(); stat.execute("SET WRITE_DELAY 0"); stat.execute("CREATE TABLE IF NOT EXISTS TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); state = 1; conn.setAutoCommit(false); stat.execute("INSERT INTO TEST VALUES(1, 'Hello')"); stat.execute("INSERT INTO TEST VALUES(2, 'World')"); conn.commit(); state = 2; stat.execute("UPDATE TEST SET NAME='Hallo' WHERE ID=1"); stat.execute("UPDATE TEST SET NAME='Welt' WHERE ID=2"); conn.commit(); state = 3; stat.execute("DELETE FROM TEST WHERE ID=1"); stat.execute("DELETE FROM TEST WHERE ID=2"); conn.commit(); state = 1; stat.execute("DROP TABLE TEST"); state = 0; if (init) { maxPowerOffCount = Integer.MAX_VALUE - ((JdbcConnection) conn).getPowerOffCount(); } conn.close(); } catch (SQLException e) { if (e.getSQLState().equals("" + ErrorCode.DATABASE_IS_CLOSED)) { // this is ok } else { throw e; } } return state; } private int recoverAndCheckConsistency() throws SQLException { int state; Database.setInitialPowerOffCount(0); Connection conn = getConnection(url); assertEquals(0, ((JdbcConnection) conn).getPowerOffCount()); Statement stat = conn.createStatement(); DatabaseMetaData meta = conn.getMetaData(); ResultSet rs = meta.getTables(null, null, "TEST", null); if (!rs.next()) { state = 0; } else { // table does not exist rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); if (!rs.next()) { state = 1; } else { assertEquals(1, rs.getInt(1)); String name1 = rs.getString(2); assertTrue(rs.next()); assertEquals(2, rs.getInt(1)); String name2 = rs.getString(2); assertFalse(rs.next()); if ("Hello".equals(name1)) { assertEquals("World", name2); state = 2; } else { assertEquals("Hallo", name1); assertEquals("Welt", name2); state = 3; } } } conn.close(); return state; } }