/*- * See the file LICENSE for redistribution information. * * Copyright (c) 2009 Oracle. All rights reserved. * */ using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; using NUnit.Framework; using BerkeleyDB; namespace CsharpAPITest { [TestFixture] public class CursorTest { private string testFixtureName; private string testFixtureHome; private string testName; private string testHome; private DatabaseEnvironment paramEnv; private BTreeDatabase paramDB; private Transaction readTxn; private Transaction updateTxn; private EventWaitHandle signal; private delegate void CursorMoveFuncDelegate( Cursor cursor, LockingInfo lockingInfo); private CursorMoveFuncDelegate cursorFunc; [TestFixtureSetUp] public void RunBeforeTests() { testFixtureName = "CursorTest"; testFixtureHome = "./TestOut/" + testFixtureName; } [Test] public void TestAdd() { BTreeDatabase db; BTreeCursor cursor; testName = "TestAdd"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); // Open a database and a cursor. GetCursorInBtreeDBWithoutEnv(testHome, testName, out db, out cursor); // Add a record and confirm that it exists. AddOneByCursor(db, cursor); cursor.Close(); db.Close(); } [Test] public void TestCompare() { BTreeDatabase db; BTreeCursor dbc1, dbc2; DatabaseEntry data, key; testName = "TestCompare"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); // Open a database and a cursor. Then close it. GetCursorInBtreeDBWithoutEnv(testHome, testName, out db, out dbc1); dbc2 = db.Cursor(); for (int i = 0; i < 10; i++) { key = new DatabaseEntry(BitConverter.GetBytes(i)); data = new DatabaseEntry(BitConverter.GetBytes(i)); db.Put(key, data); } key = new DatabaseEntry(BitConverter.GetBytes(5)); Assert.IsTrue(dbc1.Move(key, true)); Assert.IsTrue(dbc2.Move(key, true)); Assert.IsTrue(dbc1.Compare(dbc2)); Assert.IsTrue(dbc1.MoveNext()); Assert.IsFalse(dbc1.Compare(dbc2)); dbc1.Close(); dbc2.Close(); db.Close(); } [Test] public void TestClose() { BTreeDatabase db; BTreeCursor cursor; testName = "TestClose"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); // Open a database and a cursor. Then close it. GetCursorInBtreeDBWithoutEnv(testHome, testName, out db, out cursor); cursor.Close(); db.Close(); } [Test] public void TestCount() { BTreeDatabase db; BTreeCursor cursor; testName = "TestCount"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); // Write one record into database with cursor. GetCursorInBtreeDBWithoutEnv(testHome, testName, out db, out cursor); AddOneByCursor(db, cursor); /* * Confirm that that the count operation returns 1 as * the number of records in the database. */ Assert.AreEqual(1, cursor.Count()); cursor.Close(); db.Close(); } [Test] public void TestCurrent() { BTreeDatabase db; BTreeCursor cursor; testName = "TestCurrent"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); // Write a record into database with cursor. GetCursorInBtreeDBWithoutEnv(testHome, testName, out db, out cursor); AddOneByCursor(db, cursor); /* * Confirm the current record that the cursor * points to is the one that just added by the * cursor. */ Assert.IsTrue(cursor.MoveFirst()); Assert.AreEqual( ASCIIEncoding.ASCII.GetBytes("key"), cursor.Current.Key.Data); Assert.AreEqual( ASCIIEncoding.ASCII.GetBytes("data"), cursor.Current.Value.Data); cursor.Close(); db.Close(); } [Test] public void TestDelete() { BTreeDatabase db; BTreeCursor cursor; testName = "TestDelete"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); // Write a record into database with cursor. GetCursorInBtreeDBWithoutEnv(testHome, testName, out db, out cursor); AddOneByCursor(db, cursor); // Delete the current record. cursor.Delete(); // Confirm that the record no longer exists in the db. Assert.AreEqual(0, cursor.Count()); cursor.Close(); db.Close(); } [Test] public void TestDispose() { BTreeDatabase db; BTreeCursor cursor; testName = "TestDispose"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); // Write a record into database with cursor. GetCursorInBtreeDBWithoutEnv(testHome, testName, out db, out cursor); // Dispose the cursor. cursor.Dispose(); db.Close(); } [Test] public void TestIsolationDegree() { BTreeDatabase db; BTreeCursor cursor; CursorConfig cursorConfig; DatabaseEnvironment env; Transaction txn; testName = "TestIsolationDegree"; testHome = testFixtureHome + "/" + testName; Isolation[] isolationDegrees = new Isolation[3]; isolationDegrees[0] = Isolation.DEGREE_ONE; isolationDegrees[1] = Isolation.DEGREE_TWO; isolationDegrees[2] = Isolation.DEGREE_THREE; IsolationDelegate[] delegates = { new IsolationDelegate(CursorReadUncommited), new IsolationDelegate(CursorReadCommited), new IsolationDelegate(CursorRead)}; cursorConfig = new CursorConfig(); for (int i = 0; i < 3; i++) { cursorConfig.IsolationDegree = isolationDegrees[i]; GetCursorInBtreeDBInTDS(testHome + "/" + i.ToString(), testName, cursorConfig, out env, out db, out cursor, out txn); cursor.Close(); db.Close(); txn.Commit(); env.Close(); } } public delegate void IsolationDelegate( DatabaseEnvironment env, BTreeDatabase db, Cursor cursor, Transaction txn); /* * Configure a transactional cursor to have degree 2 * isolation, which permits data read by this cursor * to be deleted prior to the commit of the transaction * for this cursor. */ public void CursorReadCommited( DatabaseEnvironment env, BTreeDatabase db, Cursor cursor, Transaction txn) { Console.WriteLine("CursorReadCommited"); } /* * Configure a transactional cursor to have degree 1 * isolation. The cursor's read operations could return * modified but not yet commited data. */ public void CursorReadUncommited( DatabaseEnvironment env, BTreeDatabase db, Cursor cursor, Transaction txn) { Console.WriteLine("CursorReadUncommited"); } /* * Only return committed data. */ public void CursorRead( DatabaseEnvironment env, BTreeDatabase db, Cursor cursor, Transaction txn) { Console.WriteLine("CursorRead"); } [Test] public void TestMoveToExactKey() { BTreeDatabase db; BTreeCursor cursor; testName = "TestMoveToExactKey"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); GetCursorInBtreeDBWithoutEnv(testHome, testName, out db, out cursor); // Add one record into database. DatabaseEntry key = new DatabaseEntry( BitConverter.GetBytes((int)0)); DatabaseEntry data = new DatabaseEntry( BitConverter.GetBytes((int)0)); KeyValuePair pair = new KeyValuePair(key, data); cursor.Add(pair); // Move the cursor exactly to the specified key. MoveCursor(cursor, false); cursor.Close(); db.Close(); } [Test] public void TestMoveToExactPair() { BTreeDatabase db; BTreeCursor cursor; DatabaseEntry key, data; testName = "TestMoveToExactPair"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); GetCursorInBtreeDBWithoutEnv(testHome, testName, out db, out cursor); // Add one record into database. key = new DatabaseEntry( BitConverter.GetBytes((int)0)); data = new DatabaseEntry( BitConverter.GetBytes((int)0)); KeyValuePair pair = new KeyValuePair(key, data); cursor.Add(pair); // Move the cursor exactly to the specified key/data pair. MoveCursor(cursor, true); cursor.Close(); db.Close(); } [Test] public void TestMoveWithRMW() { testName = "TestMoveWithRMW"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); // Use MoveCursor() as its move function. cursorFunc = new CursorMoveFuncDelegate(MoveCursor); // Move to a specified key and a key/data pair. MoveWithRMW(testHome, testName); } [Test] public void TestMoveFirst() { BTreeDatabase db; BTreeCursor cursor; testName = "TestMoveFirst"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); GetCursorInBtreeDBWithoutEnv(testHome, testName, out db, out cursor); AddOneByCursor(db, cursor); // Move to the first record. MoveCursorToFirst(cursor, null); cursor.Close(); db.Close(); } [Test] public void TestMoveFirstWithRMW() { testName = "TestMoveFirstWithRMW"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); // Use MoveCursorToFirst() as its move function. cursorFunc = new CursorMoveFuncDelegate(MoveCursorToFirst); // Read the first record with write lock. MoveWithRMW(testHome, testName); } [Test] public void TestMoveLast() { BTreeDatabase db; BTreeCursor cursor; testName = "TestMoveLast"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); GetCursorInBtreeDBWithoutEnv(testHome, testName, out db, out cursor); AddOneByCursor(db, cursor); // Move the cursor to the last record. MoveCursorToLast(cursor, null); cursor.Close(); db.Close(); } [Test] public void TestMoveLastWithRMW() { testName = "TestMoveLastWithRMW"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); // Use MoveCursorToLast() as its move function. cursorFunc = new CursorMoveFuncDelegate(MoveCursorToLast); // Read the last recod with write lock. MoveWithRMW(testHome, testName); } [Test] public void TestMoveNext() { BTreeDatabase db; BTreeCursor cursor; testName = "TestMoveCursorToNext"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); GetCursorInBtreeDBWithoutEnv(testHome, testName, out db, out cursor); // Put ten records to the database. for (int i = 0; i < 10; i++) db.Put(new DatabaseEntry(BitConverter.GetBytes(i)), new DatabaseEntry(BitConverter.GetBytes(i))); // Move the cursor from the first record to the fifth. MoveCursorToNext(cursor, null); cursor.Close(); db.Close(); } [Test] public void TestMoveNextWithRMW() { testName = "TestMoveLastWithRMW"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); // Use MoveCursorToNext() as its move function. cursorFunc = new CursorMoveFuncDelegate( MoveCursorToNext); /* * Read the first record to the fifth record with * write lock. */ MoveWithRMW(testHome, testName); } [Test] public void TestMoveNextDuplicate() { BTreeDatabase db; BTreeCursor cursor; testName = "TestMoveNextDuplicate"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); GetCursorInBtreeDBWithoutEnv(testHome, testName, out db, out cursor); // Add ten duplicate records to the database. for (int i = 0; i < 10; i++) db.Put(new DatabaseEntry( ASCIIEncoding.ASCII.GetBytes("key")), new DatabaseEntry( BitConverter.GetBytes(i))); /* * Move the cursor from one duplicate record to * another duplicate one. */ MoveCursorToNextDuplicate(cursor, null); cursor.Close(); db.Close(); } [Test] public void TestMoveNextDuplicateWithRMW() { testName = "TestMoveNextDuplicateWithRMW"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); /* * Use MoveCursorToNextDuplicate() as its * move function. */ cursorFunc = new CursorMoveFuncDelegate( MoveCursorToNextDuplicate); /* * Read the first record to the fifth record with * write lock. */ MoveWithRMW(testHome, testName); } [Test] public void TestMoveNextUnique() { BTreeDatabase db; BTreeCursor cursor; testName = "TestMoveNextUnique"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); GetCursorInBtreeDBWithoutEnv(testHome, testName, out db, out cursor); // Add ten different records to the database. for (int i = 0; i < 10; i++) db.Put(new DatabaseEntry( BitConverter.GetBytes(i)), new DatabaseEntry( BitConverter.GetBytes(i))); // Move to five unique records. MoveCursorToNextUnique(cursor, null); cursor.Close(); db.Close(); } [Test] public void TestMoveNextUniqueWithRMW() { testName = "TestMoveNextUniqueWithRMW"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); /* * Use MoveCursorToNextUnique() as its * move function. */ cursorFunc = new CursorMoveFuncDelegate( MoveCursorToNextUnique); /* * Move to five unique records. */ MoveWithRMW(testHome, testName); } [Test] public void TestMovePrev() { BTreeDatabase db; BTreeCursor cursor; testName = "TestMovePrev"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); GetCursorInBtreeDBWithoutEnv(testHome, testName, out db, out cursor); // Add ten records to the database. for (int i = 0; i < 10; i++) AddOneByCursor(db, cursor); // Move the cursor to previous five records MoveCursorToPrev(cursor, null); cursor.Close(); db.Close(); } [Test] public void TestMovePrevWithRMW() { testName = "TestMovePrevWithRMW"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); /* * Use MoveCursorToNextDuplicate() as its * move function. */ cursorFunc = new CursorMoveFuncDelegate( MoveCursorToPrev); // Read previous record in write lock. MoveWithRMW(testHome, testName); } [Test] public void TestMovePrevDuplicate() { BTreeDatabase db; BTreeCursor cursor; testName = "TestMovePrevDuplicate"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); GetCursorInBtreeDBWithoutEnv(testHome, testName, out db, out cursor); // Add ten records to the database. for (int i = 0; i < 10; i++) AddOneByCursor(db, cursor); // Move the cursor to previous duplicate records. MoveCursorToPrevDuplicate(cursor, null); cursor.Close(); db.Close(); } [Test] public void TestMovePrevDuplicateWithRMW() { testName = "TestMovePrevDuplicateWithRMW"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); /* * Use MoveCursorToNextDuplicate() as its * move function. */ cursorFunc = new CursorMoveFuncDelegate( MoveCursorToPrevDuplicate); // Read the previous duplicate record in write lock. MoveWithRMW(testHome, testName); } [Test] public void TestMovePrevUnique() { BTreeDatabase db; BTreeCursor cursor; testName = "TestMovePrevUnique"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); GetCursorInBtreeDBWithoutEnv(testHome, testName, out db, out cursor); // Add ten records to the database. for (int i = 0; i < 10; i++) db.Put(new DatabaseEntry(BitConverter.GetBytes(i)), new DatabaseEntry(BitConverter.GetBytes(i))); // Move the cursor to previous unique records. MoveCursorToPrevUnique(cursor, null); cursor.Close(); db.Close(); } [Test] public void TestMovePrevUniqueWithRMW() { testName = "TestMovePrevDuplicateWithRMW"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); /* * Use MoveCursorToPrevUnique() as its * move function. */ cursorFunc = new CursorMoveFuncDelegate( MoveCursorToPrevUnique); // Read the previous unique record in write lock. MoveWithRMW(testHome, testName); } [Test] public void TestPriority() { BTreeCursor cursor; BTreeDatabase db; CachePriority[] priorities; CursorConfig cursorConfig; DatabaseEnvironment env; cursorConfig = new CursorConfig(); priorities = new CachePriority[5]; priorities[0] = CachePriority.DEFAULT; priorities[1] = CachePriority.HIGH; priorities[2] = CachePriority.LOW; priorities[3] = CachePriority.VERY_HIGH; priorities[4] = CachePriority.VERY_LOW; testName = "TestPriority"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); for (int i = 0; i < 5; i++) { // Configure the cursor priority. cursorConfig.Priority = priorities[i]; // Open a database to test a specified priority. GetCursorInBtreeDBInCDS(testHome, testName, cursorConfig, out env, out db, out cursor); Assert.AreEqual(priorities[i], cursorConfig.Priority); cursor.Close(); db.Close(); env.Close(); } } [Test] public void TestRefresh() { BTreeDatabase db; BTreeCursor cursor; testName = "TestRefresh"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); GetCursorInBtreeDBWithoutEnv(testHome, testName, out db, out cursor); // Write a record with cursor and Refresh the cursor. MoveCursorToCurrentRec(cursor, null); cursor.Close(); db.Close(); } [Test] public void TestRefreshWithRMW() { testName = "TestRefreshWithRMW"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); cursorFunc = new CursorMoveFuncDelegate( MoveCursorToCurrentRec); // Read the previous unique record in write lock. MoveWithRMW(testHome, testName); } [Test] public void TestSnapshotIsolation() { BTreeDatabaseConfig dbConfig; DatabaseEntry key, data; DatabaseEnvironmentConfig envConfig; Thread readThread, updateThread; Transaction txn; updateTxn = null; readTxn = null; paramDB = null; paramEnv = null; testName = "TestSnapshotIsolation"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); /* * Open environment with DB_MULTIVERSION * which is required by DB_TXN_SNAPSHOT. */ envConfig = new DatabaseEnvironmentConfig(); envConfig.Create = true; envConfig.UseMVCC = true; envConfig.UseTxns = true; envConfig.UseMPool = true; envConfig.UseLocking = true; envConfig.TxnTimeout = 1000; paramEnv = DatabaseEnvironment.Open( testHome, envConfig); paramEnv.DetectDeadlocks(DeadlockPolicy.YOUNGEST); /* * Open a transactional database and put 1000 records * into it within transaction. */ txn = paramEnv.BeginTransaction(); dbConfig = new BTreeDatabaseConfig(); dbConfig.Creation = CreatePolicy.IF_NEEDED; dbConfig.UseMVCC = true; dbConfig.Env = paramEnv; paramDB = BTreeDatabase.Open( testName + ".db", dbConfig, txn); for (int i = 0; i < 256; i++) { key = new DatabaseEntry( BitConverter.GetBytes(i)); data = new DatabaseEntry( BitConverter.GetBytes(i)); paramDB.Put(key, data, txn); } txn.Commit(); /* * Begin two threads, read and update thread. * The update thread runs a update transaction * using full read/write locking. The read thread * set DB_TXN_SNAPSHOT on read-only cursor. */ readThread = new Thread(new ThreadStart(ReadTxn)); updateThread = new Thread(new ThreadStart(UpdateTxn)); updateThread.Start(); Thread.Sleep(1000); readThread.Start(); readThread.Join(); updateThread.Join(); // Commit transacion in both two threads. if (updateTxn != null) updateTxn.Commit(); if (readTxn != null) readTxn.Commit(); /* * Confirm that the overwrite operation works. */ ConfirmOverwrite(); paramDB.Close(); paramEnv.Close(); } public void ReadTxn() { // Get a new transaction for reading the db. TransactionConfig txnConfig = new TransactionConfig(); txnConfig.Snapshot = true; readTxn = paramEnv.BeginTransaction( txnConfig); // Get a new cursor for putting record into db. CursorConfig cursorConfig = new CursorConfig(); cursorConfig.WriteCursor = false; BTreeCursor cursor = paramDB.Cursor( cursorConfig, readTxn); // Continually reading record from db. try { Assert.IsTrue(cursor.MoveFirst()); int i = 0; do { Assert.AreEqual( BitConverter.ToInt32( cursor.Current.Key.Data, 0), BitConverter.ToInt32( cursor.Current.Value.Data, 0)); } while (i <= 1000 && cursor.MoveNext()); } catch (DeadlockException) { } finally { cursor.Close(); } } public void UpdateTxn() { int int32Value; DatabaseEntry data; // Get a new transaction for updating the db. TransactionConfig txnConfig = new TransactionConfig(); txnConfig.IsolationDegree = Isolation.DEGREE_THREE; updateTxn = paramEnv.BeginTransaction(txnConfig); // Continually putting record to db. BTreeCursor cursor = paramDB.Cursor(updateTxn); // Move the cursor to the first record. Assert.IsTrue(cursor.MoveFirst()); int i = 0; try { do { int32Value = BitConverter.ToInt32( cursor.Current.Value.Data, 0); data = new DatabaseEntry( BitConverter.GetBytes(int32Value - 1)); cursor.Overwrite(data); } while (i <= 1000 && cursor.MoveNext()); } catch (DeadlockException) { } finally { cursor.Close(); } } [Test] public void TestWriteCursor() { BTreeCursor cursor; BTreeDatabase db; CursorConfig cursorConfig; DatabaseEnvironment env; testName = "TestWriteCursor"; testHome = testFixtureHome + "/" + testName; Configuration.ClearDir(testHome); cursorConfig = new CursorConfig(); cursorConfig.WriteCursor = true; GetCursorInBtreeDBInCDS(testHome, testName, cursorConfig, out env, out db, out cursor); /* * Add a record by cursor to the database. If the * WriteCursor doesn't work, exception will be * throwed in the environment which is configured * with DB_INIT_CDB. */ try { AddOneByCursor(db, cursor); } catch (DatabaseException) { throw new TestException(); } finally { cursor.Close(); db.Close(); env.Close(); } } public void ConfirmOverwrite() { Transaction confirmTxn = paramEnv.BeginTransaction(); BTreeCursor cursor = paramDB.Cursor(confirmTxn); int i = 0; Assert.IsTrue(cursor.MoveFirst()); do { Assert.AreNotEqual( cursor.Current.Key.Data, cursor.Current.Value.Data); } while (i <= 1000 && cursor.MoveNext()); cursor.Close(); confirmTxn.Commit(); } public static void AddOneByCursor(Database db, Cursor cursor) { DatabaseEntry key, data; KeyValuePair pair; // Add a record to db via cursor. key = new DatabaseEntry(ASCIIEncoding.ASCII.GetBytes("key")); data = new DatabaseEntry(ASCIIEncoding.ASCII.GetBytes("data")); pair = new KeyValuePair(key, data); cursor.Add(pair); // Confirm that the record has been put to the database. Assert.IsTrue(db.Exists(key)); } public static void GetCursorInBtreeDBWithoutEnv( string home, string name, out BTreeDatabase db, out BTreeCursor cursor) { string dbFileName = home + "/" + name + ".db"; BTreeDatabaseConfig dbConfig = new BTreeDatabaseConfig(); dbConfig.Creation = CreatePolicy.IF_NEEDED; dbConfig.Duplicates = DuplicatesPolicy.UNSORTED; db = BTreeDatabase.Open(dbFileName, dbConfig); cursor = db.Cursor(); } public static void GetCursorInBtreeDBInTDS( string home, string name, CursorConfig cursorConfig, out DatabaseEnvironment env, out BTreeDatabase db, out BTreeCursor cursor, out Transaction txn) { string dbFileName = name + ".db"; Configuration.ClearDir(home); // Open an environment. DatabaseEnvironmentConfig envConfig = new DatabaseEnvironmentConfig(); envConfig.Create = true; envConfig.UseMPool = true; envConfig.UseTxns = true; envConfig.NoMMap = false; envConfig.UseLocking = true; env = DatabaseEnvironment.Open(home, envConfig); // Begin a transaction. txn = env.BeginTransaction(); /* * Open an btree database. The underlying database * should be opened with ReadUncommitted if the * cursor's isolation degree will be set to be 1. */ BTreeDatabaseConfig dbConfig = new BTreeDatabaseConfig(); dbConfig.Creation = CreatePolicy.IF_NEEDED; dbConfig.Env = env; if (cursorConfig.IsolationDegree == Isolation.DEGREE_ONE) dbConfig.ReadUncommitted = true; db = BTreeDatabase.Open(dbFileName, dbConfig, txn); // Get a cursor in the transaction. cursor = db.Cursor(cursorConfig, txn); } // Get a cursor in CDS. public static void GetCursorInBtreeDBInCDS( string home, string name, CursorConfig cursorConfig, out DatabaseEnvironment env, out BTreeDatabase db, out BTreeCursor cursor) { string dbFileName = name + ".db"; // Open an environment. DatabaseEnvironmentConfig envConfig = new DatabaseEnvironmentConfig(); envConfig.Create = true; envConfig.UseCDB = true; envConfig.UseMPool = true; env = DatabaseEnvironment.Open(home, envConfig); /* * Open an btree database. The underlying database * should be opened with ReadUncommitted if the * cursor's isolation degree will be set to be 1. */ BTreeDatabaseConfig dbConfig = new BTreeDatabaseConfig(); dbConfig.Creation = CreatePolicy.IF_NEEDED; dbConfig.Env = env; if (cursorConfig.IsolationDegree == Isolation.DEGREE_ONE) dbConfig.ReadUncommitted = true; db = BTreeDatabase.Open(dbFileName, dbConfig); // Get a cursor in the transaction. cursor = db.Cursor(cursorConfig); } public void RdMfWt() { Transaction txn = paramEnv.BeginTransaction(); Cursor dbc = paramDB.Cursor(txn); try { LockingInfo lck = new LockingInfo(); lck.ReadModifyWrite = true; // Read record. cursorFunc(dbc, lck); // Block the current thread until event is set. signal.WaitOne(); // Write new records into database. DatabaseEntry key = new DatabaseEntry( BitConverter.GetBytes(55)); DatabaseEntry data = new DatabaseEntry( BitConverter.GetBytes(55)); dbc.Add(new KeyValuePair(key, data)); dbc.Close(); txn.Commit(); } catch (DeadlockException) { dbc.Close(); txn.Abort(); } } public void MoveWithRMW(string home, string name) { paramEnv = null; paramDB = null; // Open the environment. DatabaseEnvironmentConfig envCfg = new DatabaseEnvironmentConfig(); envCfg.Create = true; envCfg.FreeThreaded = true; envCfg.UseLocking = true; envCfg.UseLogging = true; envCfg.UseMPool = true; envCfg.UseTxns = true; paramEnv = DatabaseEnvironment.Open(home, envCfg); // Open database in transaction. Transaction openTxn = paramEnv.BeginTransaction(); BTreeDatabaseConfig cfg = new BTreeDatabaseConfig(); cfg.Creation = CreatePolicy.ALWAYS; cfg.Env = paramEnv; cfg.FreeThreaded = true; cfg.PageSize = 4096; cfg.Duplicates = DuplicatesPolicy.UNSORTED; paramDB = BTreeDatabase.Open(name + ".db", cfg, openTxn); openTxn.Commit(); /* * Put 10 different, 2 duplicate and another different * records into database. */ Transaction txn = paramEnv.BeginTransaction(); for (int i = 0; i < 13; i++) { DatabaseEntry key, data; if (i == 10 || i == 11) { key = new DatabaseEntry( ASCIIEncoding.ASCII.GetBytes("key")); data = new DatabaseEntry( ASCIIEncoding.ASCII.GetBytes("data")); } else { key = new DatabaseEntry( BitConverter.GetBytes(i)); data = new DatabaseEntry( BitConverter.GetBytes(i)); } paramDB.Put(key, data, txn); } txn.Commit(); // Get a event wait handle. signal = new EventWaitHandle(false, EventResetMode.ManualReset); /* * Start RdMfWt() in two threads. RdMfWt() reads * and writes data into database. */ Thread t1 = new Thread(new ThreadStart(RdMfWt)); Thread t2 = new Thread(new ThreadStart(RdMfWt)); t1.Start(); t2.Start(); /* * Give both threads time to read before signalling * them to write. */ Thread.Sleep(1000); // Invoke the write operation in both threads. signal.Set(); // Return the number of deadlocks. while (t1.IsAlive || t2.IsAlive) { /* * Give both threads time to write before * counting the number of deadlocks. */ Thread.Sleep(1000); uint deadlocks = paramEnv.DetectDeadlocks( DeadlockPolicy.DEFAULT); // Confirm that there won't be any deadlock. Assert.AreEqual(0, deadlocks); } t1.Join(); t2.Join(); paramDB.Close(); paramEnv.Close(); } /* * Move the cursor to an exisiting key or key/data pair. */ public void MoveCursor(Cursor dbc, bool ifPair) { DatabaseEntry key, data; KeyValuePair pair; key = new DatabaseEntry( BitConverter.GetBytes((int)0)); if (ifPair == false) Assert.IsTrue(dbc.Move(key, true)); else { data = new DatabaseEntry( BitConverter.GetBytes((int)0)); pair = new KeyValuePair(key, data); Assert.IsTrue(dbc.Move(pair, true)); } } /* * Move the cursor to an exisiting key and key/data * pair with LockingInfo. */ public void MoveCursor(Cursor dbc, LockingInfo lck) { DatabaseEntry key, data; KeyValuePair pair; key = new DatabaseEntry( BitConverter.GetBytes((int)0)); data = new DatabaseEntry( BitConverter.GetBytes((int)0)); pair = new KeyValuePair(key, data); // Move to an existing key. Assert.IsTrue(dbc.Move(key, true, lck)); // Move to an existing key/data pair. Assert.IsTrue(dbc.Move(pair, true, lck)); } /* * Move the cursor to the first record in a nonempty * database. The returning value should be true. */ public void MoveCursorToFirst(Cursor dbc, LockingInfo lck) { if (lck == null) Assert.IsTrue(dbc.MoveFirst()); else Assert.IsTrue(dbc.MoveFirst(lck)); } /* * Move the cursor to last record in a nonempty * database. The returning value should be true. */ public void MoveCursorToLast(Cursor dbc, LockingInfo lck) { if (lck == null) Assert.IsTrue(dbc.MoveLast()); else Assert.IsTrue(dbc.MoveLast(lck)); } /* * Move the cursor to the next record in the database * with more than five records. The returning values of * every move operation should be true. */ public void MoveCursorToNext(Cursor dbc, LockingInfo lck) { for (int i = 0; i < 5; i++) if (lck == null) Assert.IsTrue(dbc.MoveNext()); else Assert.IsTrue(dbc.MoveNext(lck)); } /* * Move the cursor to the next duplicate record in * the database which has more than 2 duplicate * records. The returning value should be true. */ public void MoveCursorToNextDuplicate(Cursor dbc, LockingInfo lck) { DatabaseEntry key = new DatabaseEntry( ASCIIEncoding.ASCII.GetBytes("key")); /* * The cursor should point to any record in the * database before it move to the next duplicate * record. */ if (lck == null) { dbc.Move(key, true); Assert.IsTrue(dbc.MoveNextDuplicate()); } else { /* * Both the move and move next duplicate * operation should use LockingInfo. If any * one doesn't use LockingInfo, deadlock still * occurs. */ dbc.Move(key, true, lck); Assert.IsTrue(dbc.MoveNextDuplicate(lck)); } } /* * Move the cursor to next unique record in the database. * The returning value should be true. */ public void MoveCursorToNextUnique(Cursor dbc, LockingInfo lck) { for (int i = 0; i < 5; i++) { if (lck == null) Assert.IsTrue(dbc.MoveNextUnique()); else Assert.IsTrue(dbc.MoveNextUnique(lck)); } } /* * Move the cursor to previous record in the database. * The returning value should be true; */ public void MoveCursorToPrev(Cursor dbc, LockingInfo lck) { if (lck == null) { dbc.MoveLast(); for (int i = 0; i < 5; i++) Assert.IsTrue(dbc.MovePrev()); } else { dbc.MoveLast(lck); for (int i = 0; i < 5; i++) Assert.IsTrue(dbc.MovePrev(lck)); } } /* * Move the cursor to a duplicate record and then to * another duplicate one. And finally move to it previous * one. Since the previous duplicate one exist, the return * value of move previous duplicate record should be * true; */ public void MoveCursorToPrevDuplicate(Cursor dbc, LockingInfo lck) { if (lck == null) { dbc.Move(new DatabaseEntry( ASCIIEncoding.ASCII.GetBytes("key")), true); dbc.MoveNextDuplicate(); Assert.IsTrue(dbc.MovePrevDuplicate()); } else { dbc.Move(new DatabaseEntry( ASCIIEncoding.ASCII.GetBytes("key")),true, lck); dbc.MoveNextDuplicate(lck); Assert.IsTrue(dbc.MovePrevDuplicate(lck)); } } /* * Move the cursor to previous unique record in a * database with more than 2 records. The returning * value should be true. */ public void MoveCursorToPrevUnique(Cursor dbc, LockingInfo lck) { for (int i = 0; i < 5; i++) if (lck == null) dbc.MovePrevUnique(); else dbc.MovePrevUnique(lck); } /* * Move the cursor to current existing record. The returning * value should be true. */ public void MoveCursorToCurrentRec(Cursor dbc, LockingInfo lck) { // Add a record to the database. KeyValuePair pair; pair = new KeyValuePair( new DatabaseEntry(ASCIIEncoding.ASCII.GetBytes("key")), new DatabaseEntry(ASCIIEncoding.ASCII.GetBytes("key"))); dbc.Add(pair); if (lck == null) dbc.Refresh(); else dbc.Refresh(lck); Assert.IsNotNull(dbc.Current.Key); } } }