001: /*
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2002,2008 Oracle. All rights reserved.
005: *
006: * $Id: BinDeltaTest.java,v 1.44.2.4 2008/01/07 15:14:33 cwl Exp $
007: */
008: package com.sleepycat.je.tree;
009:
010: import java.io.File;
011: import java.io.IOException;
012:
013: import junit.framework.TestCase;
014:
015: import com.sleepycat.je.Cursor;
016: import com.sleepycat.je.Database;
017: import com.sleepycat.je.DatabaseConfig;
018: import com.sleepycat.je.DatabaseEntry;
019: import com.sleepycat.je.DatabaseException;
020: import com.sleepycat.je.DbInternal;
021: import com.sleepycat.je.Environment;
022: import com.sleepycat.je.EnvironmentConfig;
023: import com.sleepycat.je.LockMode;
024: import com.sleepycat.je.OperationStatus;
025: import com.sleepycat.je.Transaction;
026: import com.sleepycat.je.config.EnvironmentParams;
027: import com.sleepycat.je.dbi.CursorImpl;
028: import com.sleepycat.je.log.LogManager;
029: import com.sleepycat.je.log.FileManager;
030: import com.sleepycat.je.log.entry.LogEntry;
031: import com.sleepycat.je.tree.Key.DumpType;
032: import com.sleepycat.je.txn.BasicLocker;
033: import com.sleepycat.je.txn.Locker;
034: import com.sleepycat.je.util.TestUtils;
035: import com.sleepycat.je.utilint.DbLsn;
036:
037: /**
038: * Exercise the delta based BIN logging.
039: */
040: public class BinDeltaTest extends TestCase {
041: private static final String DB_NAME = "test";
042: private static final boolean DEBUG = false;
043: private Environment env;
044: private File envHome;
045: private Database db;
046: private LogManager logManager;
047:
048: public BinDeltaTest() throws DatabaseException {
049: envHome = new File(System.getProperty(TestUtils.DEST_DIR));
050:
051: /* Print keys as numbers */
052: Key.DUMP_TYPE = DumpType.BINARY;
053: }
054:
055: public void setUp() throws IOException, DatabaseException {
056: TestUtils.removeFiles("Setup", envHome, FileManager.JE_SUFFIX);
057:
058: /*
059: * Properties for creating an environment. Disable the evictor for
060: * this test, use larger BINS.
061: */
062: EnvironmentConfig envConfig = TestUtils.initEnvConfig();
063: envConfig.setTransactional(true);
064: envConfig.setConfigParam(EnvironmentParams.ENV_RUN_EVICTOR
065: .getName(), "true");
066: envConfig.setConfigParam(EnvironmentParams.NODE_MAX.getName(),
067: "50");
068: envConfig.setConfigParam(EnvironmentParams.BIN_DELTA_PERCENT
069: .getName(), "50");
070: envConfig.setAllowCreate(true);
071: env = new Environment(envHome, envConfig);
072: logManager = DbInternal.envGetEnvironmentImpl(env)
073: .getLogManager();
074: }
075:
076: public void tearDown() throws IOException, DatabaseException {
077: if (env != null) {
078: try {
079: env.close();
080: } catch (DatabaseException E) {
081: }
082: }
083: TestUtils.removeFiles("TearDown", envHome,
084: FileManager.JE_SUFFIX, true);
085: }
086:
087: /**
088: * Create a db, fill with numRecords, return the first BIN.
089: * @param numRecords
090: */
091: private BIN initDb(int start, int end) throws DatabaseException {
092: DatabaseConfig dbConfig = new DatabaseConfig();
093: dbConfig.setTransactional(true);
094: dbConfig.setAllowCreate(true);
095: db = env.openDatabase(null, DB_NAME, dbConfig);
096:
097: addRecords(start, end);
098:
099: /* Now reach into the tree and get the first BIN */
100: Locker txn = new BasicLocker(DbInternal
101: .envGetEnvironmentImpl(env));
102: CursorImpl internalCursor = new CursorImpl(DbInternal
103: .dbGetDatabaseImpl(db), txn);
104: assertTrue(internalCursor.positionFirstOrLast(true, null));
105: BIN firstBIN = internalCursor.getBIN();
106: firstBIN.releaseLatch();
107: internalCursor.close();
108: txn.operationEnd();
109: return firstBIN;
110: }
111:
112: /**
113: * Modify the data, just to dirty the BIN.
114: */
115: private void modifyRecords(int start, int end, int increment)
116: throws DatabaseException {
117:
118: Transaction txn = env.beginTransaction(null, null);
119: Cursor cursor = db.openCursor(txn, null);
120: DatabaseEntry searchKey = new DatabaseEntry();
121: DatabaseEntry foundData = new DatabaseEntry();
122: DatabaseEntry newData = new DatabaseEntry();
123:
124: for (int i = start; i <= end; i++) {
125: searchKey.setData(TestUtils.getTestArray(i));
126: assertEquals(OperationStatus.SUCCESS, cursor.getSearchKey(
127: searchKey, foundData, LockMode.DEFAULT));
128: newData.setData(TestUtils.getTestArray(i + increment));
129: cursor.putCurrent(newData);
130: }
131: cursor.close();
132: txn.commit();
133: }
134:
135: /*
136: * Add the specified records.
137: */
138: private void addRecords(int start, int end)
139: throws DatabaseException {
140:
141: DatabaseEntry key = new DatabaseEntry();
142: DatabaseEntry data = new DatabaseEntry();
143: for (int i = start; i < end; i++) {
144: byte[] keyData = TestUtils.getTestArray(i);
145: byte[] dataData = TestUtils.byteArrayCopy(keyData);
146: key.setData(keyData);
147: data.setData(dataData);
148: db.put(null, key, data);
149: }
150: }
151:
152: /**
153: * Simple test, delta a BIN several times, reconstruct.
154: */
155: public void testSimple() throws Throwable {
156:
157: try {
158: /* Create a db, insert records value 10 - 30, get the first BIN */
159: BIN bin = initDb(10, 30);
160:
161: /* Log a full version. */
162: bin.latch();
163: long fullLsn = bin.log(logManager, true, false, false,
164: false, null);
165: bin.releaseLatch();
166: assertTrue(fullLsn != DbLsn.NULL_LSN);
167:
168: if (DEBUG) {
169: System.out.println("Start");
170: System.out.println(bin.dumpString(0, true));
171: }
172:
173: /* Modify some of the data, add data so the BIN is changed. */
174: modifyRecords(11, 13, 10);
175: addRecords(1, 3);
176: logAndCheck(bin);
177:
178: /* Modify more of the data, so the BIN is changed. */
179: modifyRecords(14, 15, 10);
180: logAndCheck(bin);
181: } catch (Throwable t) {
182: t.printStackTrace();
183: throw t;
184: } finally {
185: db.close();
186: }
187: }
188:
189: /**
190: * Test that a delta is correctly generated when there are entries
191: * that have been aborted and rolled back.
192: *
193: * The case we're trying to test, (that was in error before)
194: * - a record is deleted
195: * - a full version of BIN x is written to the log, reflecting that
196: * deletion.
197: * - the deleting txn is aborted, so the record is restored. Now the
198: * BIN has an entry where the child LSN is less than the last full
199: * BIN version LSN.
200: * - generate a delta, make sure that the restoration of the record is
201: * present.
202: */
203: public void testUndo() throws Throwable {
204:
205: try {
206: /* Create a db, insert records value 10 - 30, get the first BIN */
207: BIN bin = initDb(10, 30);
208:
209: /* Delete the first record, then abort the delete. */
210: Transaction txn = env.beginTransaction(null, null);
211: Cursor cursor = db.openCursor(txn, null);
212: DatabaseEntry firstKey = new DatabaseEntry();
213: DatabaseEntry foundData = new DatabaseEntry();
214: OperationStatus status = cursor.getFirst(firstKey,
215: foundData, LockMode.DEFAULT);
216: assertEquals(OperationStatus.SUCCESS, status);
217: status = cursor.delete();
218: assertEquals(OperationStatus.SUCCESS, status);
219: cursor.close();
220:
221: /* Log a full version. This will reflect the delete. */
222: bin.latch();
223: long fullLsn = bin.log(logManager, true, false, false,
224: false, null);
225: bin.releaseLatch();
226: assertTrue(fullLsn != DbLsn.NULL_LSN);
227:
228: /*
229: * Roll back the deletion. Now the full version of the LSN is out
230: * of date.
231: */
232: txn.abort();
233:
234: /*
235: * Make sure a delta reflect the abort, even though the abort
236: * returns an older LSN back into the BIN.
237: */
238: logAndCheck(bin);
239: } catch (Throwable t) {
240: t.printStackTrace();
241: throw t;
242: } finally {
243: db.close();
244: }
245: }
246:
247: /* Check if full is logged when percent > max */
248: /* Check that max deltas works. */
249: /* check knownDelete. */
250:
251: /**
252: * Log the targetBIN, then read it back from the log and make sure
253: * the recreated BIN matches the in memory BIN.
254: */
255: private void logAndCheck(BIN targetBIN) throws DatabaseException {
256:
257: /*
258: * Log it as a delta. If the logging was done as a delta, this method
259: * returns null, so we expect null
260: */
261: assertTrue(targetBIN.log(logManager, true, false, false, false,
262: null) == DbLsn.NULL_LSN);
263:
264: /* Read the delta back. */
265: LogEntry partial = logManager.getLogEntry(targetBIN
266: .getLastDeltaVersion());
267:
268: /* Make sure that this is was a delta entry. */
269: assertTrue(partial.getMainItem() instanceof BINDelta);
270: BINDelta delta = (BINDelta) partial.getMainItem();
271:
272: /* Compare to the current version. */
273: BIN createdBIN = delta.reconstituteBIN(DbInternal
274: .envGetEnvironmentImpl(env));
275: if (DEBUG) {
276: System.out.println("created");
277: System.out.println(createdBIN.dumpString(0, true));
278: }
279:
280: assertEquals(targetBIN.getClass().getName(), createdBIN
281: .getClass().getName());
282: assertEquals(targetBIN.getNEntries(), createdBIN.getNEntries());
283:
284: for (int i = 0; i < createdBIN.getNEntries(); i++) {
285: assertEquals("LSN " + i, targetBIN.getLsn(i), createdBIN
286: .getLsn(i));
287: }
288: assertEquals(true, createdBIN.getDirty());
289: assertEquals(true, targetBIN.getDirty());
290: }
291: }
|