001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (license2)
004: * Initial Developer: H2 Group
005: */
006: package org.h2.test.db;
007:
008: import java.sql.Connection;
009: import java.sql.DatabaseMetaData;
010: import java.sql.ResultSet;
011: import java.sql.SQLException;
012: import java.sql.Statement;
013: import java.util.ArrayList;
014: import java.util.Random;
015:
016: import org.h2.constant.SysProperties;
017: import org.h2.engine.Constants;
018: import org.h2.engine.Database;
019: import org.h2.jdbc.JdbcConnection;
020: import org.h2.store.FileLister;
021: import org.h2.test.TestBase;
022: import org.h2.util.FileUtils;
023: import org.h2.util.JdbcUtils;
024:
025: /**
026: * Tests simulated power off conditions.
027: */
028: public class TestPowerOff extends TestBase {
029:
030: private String dbName = "powerOff";
031: private String dir, url;
032:
033: private int maxPowerOffCount;
034:
035: public void test() throws Exception {
036: if (config.memory || config.logMode == 0) {
037: return;
038: }
039: if (config.big) {
040: dir = baseDir;
041: } else {
042: dir = "memFS:";
043: }
044: url = dir + "/" + dbName + ";file_lock=no";
045: testSummaryCrash();
046: testCrash();
047: testShutdown();
048: testNoIndexFile();
049: testMemoryTables();
050: testPersistentTables();
051: }
052:
053: private void testSummaryCrash() throws Exception {
054: if (config.networked) {
055: return;
056: }
057: deleteDb(dir, dbName);
058: Connection conn = getConnection(url);
059: Statement stat = conn.createStatement();
060: for (int i = 0; i < 10; i++) {
061: stat.execute("CREATE TABLE TEST" + i
062: + "(ID INT PRIMARY KEY, NAME VARCHAR)");
063: for (int j = 0; j < 10; j++) {
064: stat.execute("INSERT INTO TEST" + i + " VALUES(" + j
065: + ", 'Hello')");
066: }
067: }
068: for (int i = 0; i < 10; i += 2) {
069: stat.execute("DROP TABLE TEST" + i);
070: }
071: stat.execute("SET WRITE_DELAY 0");
072: stat.execute("CHECKPOINT");
073: for (int j = 0; j < 10; j++) {
074: stat.execute("INSERT INTO TEST1 VALUES(" + (10 + j)
075: + ", 'World')");
076: }
077: stat.execute("SHUTDOWN IMMEDIATELY");
078: JdbcUtils.closeSilently(conn);
079: conn = getConnection(url);
080: stat = conn.createStatement();
081: for (int i = 1; i < 10; i += 2) {
082: ResultSet rs = stat.executeQuery("SELECT * FROM TEST" + i
083: + " ORDER BY ID");
084: for (int j = 0; j < 10; j++) {
085: rs.next();
086: check(rs.getInt(1), j);
087: check(rs.getString(2), "Hello");
088: }
089: if (i == 1) {
090: for (int j = 0; j < 10; j++) {
091: rs.next();
092: check(rs.getInt(1), j + 10);
093: check(rs.getString(2), "World");
094: }
095: }
096: checkFalse(rs.next());
097: }
098: conn.close();
099: }
100:
101: private void testCrash() throws Exception {
102: if (config.networked) {
103: return;
104: }
105: deleteDb(dir, dbName);
106: Random random = new Random(1);
107: SysProperties.runFinalize = false;
108: int repeat = getSize(1, 20);
109: for (int i = 0; i < repeat; i++) {
110: Connection conn = getConnection(url);
111: conn.close();
112: conn = getConnection(url);
113: Statement stat = conn.createStatement();
114: stat.execute("SET WRITE_DELAY 0");
115: ((JdbcConnection) conn).setPowerOffCount(random
116: .nextInt(100));
117: try {
118: stat.execute("DROP TABLE IF EXISTS TEST");
119: stat
120: .execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))");
121: conn.setAutoCommit(false);
122: int len = getSize(3, 100);
123: for (int j = 0; j < len; j++) {
124: stat.execute("INSERT INTO TEST VALUES(" + j
125: + ", 'Hello')");
126: if (random.nextInt(5) == 0) {
127: conn.commit();
128: }
129: if (random.nextInt(10) == 0) {
130: stat.execute("DROP TABLE IF EXISTS TEST");
131: stat
132: .execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))");
133: }
134: }
135: stat.execute("DROP TABLE IF EXISTS TEST");
136: conn.close();
137: } catch (SQLException e) {
138: if (!e.getSQLState().equals("90098")) {
139: TestBase.logError("power", e);
140: }
141: }
142: }
143: }
144:
145: private void testShutdown() throws Exception {
146: deleteDb(dir, dbName);
147: Connection conn = getConnection(url);
148: Statement stat = conn.createStatement();
149: stat
150: .execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))");
151: stat.execute("INSERT INTO TEST VALUES(1, 'Hello')");
152: stat.execute("SHUTDOWN");
153: conn.close();
154:
155: conn = getConnection(url);
156: stat = conn.createStatement();
157: ResultSet rs = stat.executeQuery("SELECT * FROM TEST");
158: check(rs.next());
159: checkFalse(rs.next());
160: conn.close();
161: }
162:
163: private void testNoIndexFile() throws Exception {
164: if (config.networked) {
165: return;
166: }
167: deleteDb(dir, dbName);
168: Connection conn = getConnection(url);
169: Statement stat = conn.createStatement();
170: stat
171: .execute("CREATE MEMORY TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))");
172: stat.execute("INSERT INTO TEST VALUES(1, 'Hello')");
173: ((JdbcConnection) conn).setPowerOffCount(1);
174: try {
175: stat.execute("INSERT INTO TEST VALUES(2, 'Hello')");
176: stat.execute("CHECKPOINT");
177: error();
178: } catch (SQLException e) {
179: checkNotGeneralException(e);
180: }
181: boolean deleted = false;
182: ArrayList files = FileLister.getDatabaseFiles(dir, dbName,
183: false);
184: for (int i = 0; i < files.size(); i++) {
185: String fileName = (String) files.get(i);
186: if (fileName.endsWith(Constants.SUFFIX_INDEX_FILE)) {
187: FileUtils.delete(fileName);
188: deleted = true;
189: }
190: }
191: check(deleted);
192: conn = getConnection(url);
193: conn.close();
194: }
195:
196: private void testMemoryTables() throws Exception {
197: if (config.networked) {
198: return;
199: }
200: deleteDb(dir, dbName);
201:
202: Connection conn = getConnection(url);
203: Statement stat = conn.createStatement();
204: stat
205: .execute("CREATE MEMORY TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))");
206: stat.execute("INSERT INTO TEST VALUES(1, 'Hello')");
207: stat.execute("CHECKPOINT");
208: ((JdbcConnection) conn).setPowerOffCount(1);
209: try {
210: stat.execute("INSERT INTO TEST VALUES(2, 'Hello')");
211: stat.execute("INSERT INTO TEST VALUES(3, 'Hello')");
212: stat.execute("CHECKPOINT");
213: error();
214: } catch (SQLException e) {
215: checkNotGeneralException(e);
216: }
217:
218: ((JdbcConnection) conn).setPowerOffCount(0);
219: conn = getConnection(url);
220: stat = conn.createStatement();
221: ResultSet rs = stat.executeQuery("SELECT COUNT(*) FROM TEST");
222: rs.next();
223: check(rs.getInt(1), 1);
224: conn.close();
225: }
226:
227: private void testPersistentTables() throws Exception {
228: if (config.networked) {
229: return;
230: }
231: if (config.cipher != null) {
232: // this would take too long (setLength uses
233: // individual writes, many thousand operations)
234: return;
235: }
236: deleteDb(dir, dbName);
237:
238: // ((JdbcConnection)conn).setPowerOffCount(Integer.MAX_VALUE);
239: testRun(true);
240: int max = maxPowerOffCount;
241: trace("max=" + max);
242: runTest(0, max, true);
243: recoverAndCheckConsistency();
244: runTest(0, max, false);
245: recoverAndCheckConsistency();
246: }
247:
248: void runTest(int min, int max, boolean withConsistencyCheck)
249: throws Exception {
250: for (int i = min; i < max; i++) {
251: deleteDb(dir, dbName);
252: Database.setInitialPowerOffCount(i);
253: int expect = testRun(false);
254: if (withConsistencyCheck) {
255: int got = recoverAndCheckConsistency();
256: trace("test " + i + " of " + max + " expect=" + expect
257: + " got=" + got);
258: } else {
259: trace("test " + i + " of " + max + " expect=" + expect);
260: }
261: }
262: Database.setInitialPowerOffCount(0);
263: }
264:
265: int testRun(boolean init) throws Exception {
266: if (init) {
267: Database.setInitialPowerOffCount(Integer.MAX_VALUE);
268: }
269: int state = 0;
270: try {
271: Connection conn = getConnection(url);
272: Statement stat = conn.createStatement();
273: stat.execute("SET WRITE_DELAY 0");
274: stat
275: .execute("CREATE TABLE IF NOT EXISTS TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))");
276: state = 1;
277: conn.setAutoCommit(false);
278: stat.execute("INSERT INTO TEST VALUES(1, 'Hello')");
279: stat.execute("INSERT INTO TEST VALUES(2, 'World')");
280: conn.commit();
281: state = 2;
282: stat.execute("UPDATE TEST SET NAME='Hallo' WHERE ID=1");
283: stat.execute("UPDATE TEST SET NAME='Welt' WHERE ID=2");
284: conn.commit();
285: state = 3;
286: stat.execute("DELETE FROM TEST WHERE ID=1");
287: stat.execute("DELETE FROM TEST WHERE ID=2");
288: conn.commit();
289: state = 1;
290: stat.execute("DROP TABLE TEST");
291: state = 0;
292: if (init) {
293: maxPowerOffCount = Integer.MAX_VALUE
294: - ((JdbcConnection) conn).getPowerOffCount();
295: }
296: conn.close();
297: } catch (SQLException e) {
298: if (e.getSQLState().equals("90098")) {
299: // this is ok
300: } else {
301: throw e;
302: }
303: }
304: return state;
305: }
306:
307: int recoverAndCheckConsistency() throws Exception {
308: int state;
309: Database.setInitialPowerOffCount(0);
310: Connection conn = getConnection(url);
311: if (((JdbcConnection) conn).getPowerOffCount() != 0) {
312: error("power off count is not 0");
313: }
314: Statement stat = conn.createStatement();
315: DatabaseMetaData meta = conn.getMetaData();
316: ResultSet rs = meta.getTables(null, null, "TEST", null);
317: if (!rs.next()) {
318: state = 0;
319: } else {
320: // table does not exist
321: rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID");
322: if (!rs.next()) {
323: state = 1;
324: } else {
325: check(rs.getInt(1), 1);
326: String name1 = rs.getString(2);
327: check(rs.next());
328: check(rs.getInt(1), 2);
329: String name2 = rs.getString(2);
330: checkFalse(rs.next());
331: if ("Hello".equals(name1)) {
332: check(name2, "World");
333: state = 2;
334: } else {
335: check(name1, "Hallo");
336: check(name2, "Welt");
337: state = 3;
338: }
339: }
340: }
341: conn.close();
342: return state;
343: }
344:
345: }
|