001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2004,2008 Oracle. All rights reserved.
005: *
006: * $Id: CheckBINDeltaTest.java,v 1.13.2.4 2008/01/07 15:14:30 cwl Exp $
007: */
008: package com.sleepycat.je.recovery;
009:
010: import com.sleepycat.bind.tuple.IntegerBinding;
011: import com.sleepycat.je.CheckpointConfig;
012: import com.sleepycat.je.Database;
013: import com.sleepycat.je.DatabaseConfig;
014: import com.sleepycat.je.DatabaseEntry;
015: import com.sleepycat.je.DatabaseException;
016: import com.sleepycat.je.DbInternal;
017: import com.sleepycat.je.EnvironmentConfig;
018: import com.sleepycat.je.OperationStatus;
019: import com.sleepycat.je.config.EnvironmentParams;
020: import com.sleepycat.je.dbi.EnvironmentImpl;
021: import com.sleepycat.je.tree.BIN;
022: import com.sleepycat.je.tree.Tree;
023: import com.sleepycat.je.util.TestUtils;
024:
025: public class CheckBINDeltaTest extends CheckBase {
026:
027: private static final String DB_NAME = "simpleDB";
028: private static final boolean DEBUG = false;
029:
030: /**
031: * SR #11123
032: * Make sure that BINDeltas are applied only to non-deleted nodes.
033: */
034: public void testBINDelta() throws Throwable {
035:
036: EnvironmentConfig envConfig = TestUtils.initEnvConfig();
037: turnOffEnvDaemons(envConfig);
038: envConfig.setConfigParam(EnvironmentParams.NODE_MAX.getName(),
039: "4");
040: envConfig.setConfigParam(EnvironmentParams.BIN_DELTA_PERCENT
041: .getName(), "75");
042: envConfig.setAllowCreate(true);
043:
044: DatabaseConfig dbConfig = new DatabaseConfig();
045: dbConfig.setAllowCreate(true);
046:
047: EnvironmentConfig restartConfig = TestUtils.initEnvConfig();
048: turnOffEnvDaemons(restartConfig);
049: envConfig.setConfigParam(EnvironmentParams.NODE_MAX.getName(),
050: "4");
051:
052: testOneCase(DB_NAME, envConfig, dbConfig, new TestGenerator() {
053: void generateData(Database db) throws DatabaseException {
054: addData(db);
055: }
056: }, restartConfig, new DatabaseConfig());
057: }
058:
059: /**
060: * This test checks for the bug described in SR11123. If an IN and its
061: * child-subtree is deleted, an INDeleteInfo is written to the
062: * log. If there is a BINDelta in the log for a BIN-child of the
063: * removed subtree (i.e. compressed), then recovery will apply it to the
064: * compressed IN. Since the IN has no data in * it, that is not
065: * necessarily a problem. However, reinstantiating the obsolete IN
066: * may cause a parent IN to split which is not allowed during IN
067: * recovery.
068: *
069: * Here's the case:
070: *
071: * |
072: * IN1
073: * +---------------------------------+
074: * | |
075: * IN2 IN6
076: * / | / | \
077: * BIN3 BIN4 BIN7 BIN8 BIN9
078: *
079: * IN2 and the subtree below are compressed away. During recovery
080: * replay, after the pass where INs and INDeleteINfos are
081: * processed, the in-memory tree looks like this:
082: *
083: * IN1
084: * |
085: * IN6
086: * / | \
087: * BIN7 BIN8 BIN9
088: *
089: * However, let's assume that BINDeltas were written for
090: * BIN3, BIN4, BIN5 within the recovery part of the log, before the
091: * subtree was compressed. We'll replay those BINDeltas in the
092: * following pass, and in the faulty implementation, they cause
093: * the ghosts of BIN3, BIN4 to be resurrected and applied to
094: * IN6. Let's assume that the max node size is 4 -- we won't be
095: * able to connect BIN3, BIN4 because IN6 doesn't have the
096: * capacity, and we don't expect to have to do splits.
097: */
098: private void addData(Database db) throws DatabaseException {
099:
100: DatabaseEntry key = new DatabaseEntry();
101: DatabaseEntry data = new DatabaseEntry();
102:
103: /* Populate a tree so there are 3 levels. */
104: for (int i = 0; i < 140; i += 10) {
105: IntegerBinding.intToEntry(i, key);
106: IntegerBinding.intToEntry(i, data);
107: assertEquals(OperationStatus.SUCCESS, db.put(null, key,
108: data));
109: }
110:
111: CheckpointConfig ckptConfig = new CheckpointConfig();
112: ckptConfig.setForce(true);
113: env.checkpoint(ckptConfig);
114:
115: Tree tree = DbInternal.dbGetDatabaseImpl(db).getTree();
116: com.sleepycat.je.tree.Key.DUMP_TYPE = com.sleepycat.je.tree.Key.DumpType.BINARY;
117: com.sleepycat.je.tree.Key.DUMP_INT_BINDING = true;
118: if (DEBUG) {
119: tree.dump();
120: }
121:
122: /*
123: * Update a key on the BIN3 and a key on BIN4, to create reason for
124: * a BINDelta. Force a BINDelta for BIN3 and BIN4 out to the log.
125: */
126: IntegerBinding.intToEntry(0, key);
127: IntegerBinding.intToEntry(100, data);
128: assertEquals(OperationStatus.SUCCESS, db.put(null, key, data));
129: IntegerBinding.intToEntry(20, key);
130: assertEquals(OperationStatus.SUCCESS, db.put(null, key, data));
131:
132: EnvironmentImpl envImpl = DbInternal.envGetEnvironmentImpl(env);
133: BIN bin = (BIN) tree.getFirstNode();
134: bin.log(envImpl.getLogManager(), true, false, false, false,
135: null);
136: bin = tree.getNextBin(bin, false /* traverseWithinDupTree */);
137: bin.log(envImpl.getLogManager(), true, false, false, false,
138: null);
139: bin.releaseLatch();
140:
141: /*
142: * Delete all of left hand side of the tree, so that the subtree root
143: * headed by IN2 is compressed.
144: */
145: for (int i = 0; i < 50; i += 10) {
146: IntegerBinding.intToEntry(i, key);
147: assertEquals(OperationStatus.SUCCESS, db.delete(null, key));
148: }
149:
150: /* force a compression */
151: env.compress();
152: if (DEBUG) {
153: tree.dump();
154: }
155: }
156: }
|