/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 2009, 2013 Oracle and/or its affiliates.  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 : CSharpTestFixture
	{
		private DatabaseEnvironment paramEnv;
		private BTreeDatabase paramDB;
		private Transaction readTxn;
		private Transaction updateTxn;

		private EventWaitHandle signal;

		private delegate void CursorMoveFuncDelegate(
		    Cursor cursor, uint kOffset, uint kLen, 
		    uint dOffset, uint dLen, LockingInfo lockingInfo);
		private CursorMoveFuncDelegate cursorFunc;

		[TestFixtureSetUp]
		public void SetUpTestFixture()
		{
			testFixtureName = "CursorTest";
			base.SetUpTestfixture();
		}

		[Test]
		public void TestAdd()
		{
			BTreeDatabase db;
			BTreeCursor cursor;

			testName = "TestAdd";
			SetUpTest(true);

			// Open a database and a cursor.
			GetCursorInBtreeDBWithoutEnv(testHome, testName,
			    out db, out cursor);

			// Add a record and confirm that it exists.
			AddOneByCursor(db, cursor);

			// Intend to insert pair to a wrong position.
			KeyValuePair<DatabaseEntry, DatabaseEntry> pair =
			    new KeyValuePair<DatabaseEntry, DatabaseEntry>(
			    new DatabaseEntry(ASCIIEncoding.ASCII.GetBytes("key")),
			    new DatabaseEntry(ASCIIEncoding.ASCII.GetBytes("data")));
			try {
				try {
					cursor.Add(pair, Cursor.InsertLocation.AFTER);
					throw new TestException();
				} catch (ArgumentException)
				{}

				try {
					cursor.Add(pair, Cursor.InsertLocation.BEFORE);
					throw new TestException();
				} catch (ArgumentException)
				{}
			} finally {
				cursor.Close();
				db.Close();
			}	
		}

        [Test]
        public void TestCompare() {
            BTreeDatabase db;
            BTreeCursor dbc1, dbc2;
            DatabaseEntry data, key;

            testName = "TestCompare";
            SetUpTest(true);

            // 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";
			SetUpTest(true);

			// Open a database and a cursor. Then close it.
			GetCursorInBtreeDBWithoutEnv(testHome, testName,
			    out db, out cursor);
			cursor.Close();
			db.Close();
		}

		[Test]
		public void TestCloseResmgr()
		{
			testName = "TestCloseResmgr";
			SetUpTest(true);

			TestCloseResmgr_int(true);
			TestCloseResmgr_int(false);
		}
		void TestCloseResmgr_int(bool havetxn)
		{
			BTreeDatabase db;
			BTreeCursor cursor, csr;
			CursorConfig cursorConfig;
			DatabaseEnvironment env;
			Transaction txn;

			DatabaseEntry key, data;
			KeyValuePair<DatabaseEntry, DatabaseEntry> pair;

			txn = null;

			cursorConfig = new CursorConfig();
			// Open an environment, a database and a cursor.
			if (havetxn)
				GetCursorInBtreeDBInTDS(testHome, testName, 
				    cursorConfig, out env, out db, 
				    out cursor, out txn);
			else
				GetCursorInBtreeDB(testHome, testName, 
				    cursorConfig, out env, out db, 
				    out cursor);

			// Add a record to db via cursor.
			key = new DatabaseEntry(
			    ASCIIEncoding.ASCII.GetBytes("key"));
			data = new DatabaseEntry(
			    ASCIIEncoding.ASCII.GetBytes("data"));
			pair = new KeyValuePair<DatabaseEntry,DatabaseEntry>
			    (key, data);
			byte []kbytes;
			byte []dbytes;

			for (int i = 0; i < 100; i++) {
				kbytes = ASCIIEncoding.ASCII.
				    GetBytes("key" + i);
				dbytes = ASCIIEncoding.ASCII.
				    GetBytes("data" + i);
				key.Data = kbytes;
				
				data.Data = dbytes;
				
				cursor.Add(pair);
			}
			// Do not close cursor.

			csr = db.Cursor(cursorConfig, txn);

			while (csr.MoveNext()) {
				// Do nothing for now.
			}
			// Do not close csr.
			if (havetxn && txn != null)
				txn.Commit();
			env.CloseForceSync();
			
		}

		[Test]
		public void TestCount()
		{
			BTreeDatabase db;
			BTreeCursor cursor;

			testName = "TestCount";
			SetUpTest(true);

			// 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";
			SetUpTest(true);

			// 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";
			SetUpTest(true);

			// 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";
			SetUpTest(true);

			// Write a record into database with cursor.
			GetCursorInBtreeDBWithoutEnv(testHome,
			    testName, out db, out cursor);

			// Dispose the cursor.
			cursor.Dispose();

			db.Close();
		}

		[Test]
		public void TestDuplicate()
		{
			DatabaseEnvironment env;
			BTreeDatabase db;
			BTreeCursor cursor;
			Transaction txn;
			testName = "TestDuplicate";
			SetUpTest(true);

			GetCursorInBtreeDBInTDS(testHome, testName,
			    new CursorConfig(), out env, out db, out cursor,
			    out txn);
			for (int i = 0; i < 10; i++)
				cursor.Add(new KeyValuePair<
				    DatabaseEntry, DatabaseEntry>(
				    new DatabaseEntry(BitConverter.GetBytes(i)),
				    new DatabaseEntry(BitConverter.GetBytes(i))));
			// Duplicate the cursor and keep its position.
			BTreeCursor newCursor = cursor.Duplicate(true);
			Assert.AreEqual(newCursor.Current.Key, 
			    cursor.Current.Key);
			Assert.AreEqual(newCursor.Current.Key,
			    cursor.Current.Key);
			cursor.Close();
			newCursor.Close();
			db.Close();
			txn.Commit();
			env.Close();
		}

		[Test]
		public void TestIsolationDegree()
		{
			BTreeDatabase db;
			BTreeCursor cursor;
			CursorConfig cursorConfig;
			DatabaseEnvironment env;
			Transaction txn;

			testName = "TestIsolationDegree";
			SetUpTest(true);

			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";
			SetUpTest(true);

			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<DatabaseEntry, DatabaseEntry> pair =
			    new KeyValuePair<DatabaseEntry,DatabaseEntry>(
			    key, data);
			cursor.Add(pair);

			// Move the cursor exactly to the specified key.
			MoveCursor(cursor, 1, 1, 2, 2, null);
			cursor.Close();
			db.Close();	
		}

		[Test]
		public void TestMoveToExactPair()
		{
			BTreeDatabase db;
			BTreeCursor cursor;
			DatabaseEntry key, data;

			testName = "TestMoveToExactPair";
			SetUpTest(true);

			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<DatabaseEntry, DatabaseEntry> pair =
			    new KeyValuePair<DatabaseEntry, DatabaseEntry>(key, data);
			cursor.Add(pair);

			// Move the cursor exactly to the specified key/data pair.
			MoveCursor(cursor, 1, 1, 2, 2, null);
			cursor.Close();
			db.Close();	
			
		}

		[Test]
		public void TestMoveWithRMW()
		{
			testName = "TestMoveWithRMW";
			SetUpTest(true);

			// 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";
			SetUpTest(true);

			GetCursorInBtreeDBWithoutEnv(testHome, testName,
			    out db, out cursor);
			AddOneByCursor(db, cursor);

			// Move to the first record.
			MoveCursorToFirst(cursor, 1, 1, 2, 2, null);

			cursor.Close();
			db.Close();
		}

		[Test]
		public void TestMoveFirstWithRMW()
		{
			testName = "TestMoveFirstWithRMW";
			SetUpTest(true);

			// 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";
			SetUpTest(true);

			GetCursorInBtreeDBWithoutEnv(testHome, testName,
			    out db, out cursor);
			AddOneByCursor(db, cursor);

			// Move the cursor to the last record.
			MoveCursorToLast(cursor, 1, 1, 2, 2, null);

			cursor.Close();
			db.Close();
		}

		[Test]
		public void TestMoveLastWithRMW()
		{
			testName = "TestMoveLastWithRMW";
			SetUpTest(true);

			// 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 = "TestMoveNext";
			SetUpTest(true);

			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, 1, 1, 2, 2, null);

			cursor.Close();
			db.Close();
		}

		[Test]
		public void TestMoveNextWithRMW()
		{
			testName = "TestMoveLastWithRMW";
			SetUpTest(true);

			// 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";
			SetUpTest(true);

			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, 1, 1, 2, 2, null);

			cursor.Close();
			db.Close();
		}

		[Test]
		public void TestMoveNextDuplicateWithRMW()
		{
			testName = "TestMoveNextDuplicateWithRMW";
			SetUpTest(true);

			/*
			 * 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";
			SetUpTest(true);

			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, 1, 1, 2, 2, null);

			cursor.Close();
			db.Close();
		}

		[Test]
		public void TestMoveNextUniqueWithRMW()
		{
			testName = "TestMoveNextUniqueWithRMW";
			SetUpTest(true);

			/*
			 * 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";
			SetUpTest(true);

			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, 1, 1, 2, 2, null);

			cursor.Close();
			db.Close();
		}

		[Test]
		public void TestMovePrevWithRMW()
		{
			testName = "TestMovePrevWithRMW";
			SetUpTest(true);

			/*
			 * 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";
			SetUpTest(true);

			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, 1, 1, 2, 2, null);

			cursor.Close();
			db.Close();
		}

		[Test]
		public void TestMovePrevDuplicateWithRMW()
		{
			testName = "TestMovePrevDuplicateWithRMW";
			SetUpTest(true);

			/*
			 * 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";
			SetUpTest(true);

			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, 1, 1, 2, 2, null);

			cursor.Close();
			db.Close();
		}

		[Test]
		public void TestMovePrevUniqueWithRMW()
		{
			testName = "TestMovePrevDuplicateWithRMW";
			SetUpTest(true);

			/*
			 * Use MoveCursorToPrevUnique() as its 
			 * move function.
			 */
			cursorFunc = new CursorMoveFuncDelegate(
			    MoveCursorToPrevUnique);

			// Read the previous unique record in write lock.
			MoveWithRMW(testHome, testName);
		}

		[Test]
		public void TestPartialPut() 
		{
			testName = "TestPartialPut";
			SetUpTest(true);

			BTreeDatabaseConfig cfg = new BTreeDatabaseConfig();
			cfg.Creation = CreatePolicy.IF_NEEDED;
			BTreeDatabase db = BTreeDatabase.Open(
			    testHome + "/" + testName, cfg);
			PopulateDb(ref db);

			/*
			 * Partially replace the first byte in the current
			 * data with 'a', and verify that the first byte in 
			 * the retrieved current data is 'a'.
			 */
			BTreeCursor cursor = db.Cursor();
			byte[] bytes = new byte[1];
			bytes[0] = (byte)'a';
			DatabaseEntry data = new DatabaseEntry(bytes, 0, 1);
			KeyValuePair<DatabaseEntry, DatabaseEntry> pair;
			while(cursor.MoveNext()) {
				cursor.Overwrite(data);
				Assert.IsTrue(cursor.Refresh());
				pair = cursor.Current;
				Assert.AreEqual((byte)'a', pair.Value.Data[0]);
			}

			cursor.Close();
			db.Close();
		}

		private void PopulateDb(ref BTreeDatabase db) 
		{
			if (db == null) {
				BTreeDatabaseConfig cfg =
				    new BTreeDatabaseConfig();
				cfg.Creation = CreatePolicy.IF_NEEDED;
				db = BTreeDatabase.Open(
				    testHome + "/" + testName, cfg);
			}
			for (int i = 0; i < 100; i++)
				db.Put(
				    new DatabaseEntry(BitConverter.GetBytes(i)), 
				    new DatabaseEntry(BitConverter.GetBytes(i)));
		}

		[Test]
		public void TestPriority()
		{
			CachePriority[] priorities;
			CursorConfig cursorConfig;

			testName = "TestPriority";
			SetUpTest(true);
			cursorConfig = new CursorConfig();
			priorities = new CachePriority[6];
			priorities[0] = CachePriority.DEFAULT;
			priorities[1] = CachePriority.HIGH;
			priorities[2] = CachePriority.LOW;
			priorities[3] = CachePriority.VERY_HIGH;
			priorities[4] = CachePriority.VERY_LOW;
			priorities[5] = null;

			for (int i = 0; i < 6; i++) {
				if (priorities[i] != null) {
					cursorConfig.Priority = priorities[i];
					Assert.AreEqual(priorities[i], cursorConfig.Priority);
				}
				GetCursorWithConfig(testHome + "/" + testName + ".db", 
				    DatabaseType.BTREE.ToString(), cursorConfig, DatabaseType.BTREE);
				GetCursorWithConfig(testHome + "/" + testName + ".db", 
				    DatabaseType.HASH.ToString(), cursorConfig, DatabaseType.HASH);
				GetCursorWithConfig(testHome + "/" + testName + ".db", 
				    DatabaseType.QUEUE.ToString(), cursorConfig, DatabaseType.QUEUE);
				GetCursorWithConfig(testHome + "/" + testName + ".db", 
				    DatabaseType.RECNO.ToString(), cursorConfig, DatabaseType.RECNO);
			}
		}

		private void GetCursorWithConfig(string dbFile, string dbName, 
		    CursorConfig cfg, DatabaseType type)
		{
			Database db;
			Cursor cursor;
			Configuration.ClearDir(testHome);
			if (type == DatabaseType.BTREE) {
				BTreeDatabaseConfig dbConfig = new BTreeDatabaseConfig();
				dbConfig.Creation = CreatePolicy.IF_NEEDED;
				db = BTreeDatabase.Open(dbFile, dbName, dbConfig);
				cursor = ((BTreeDatabase)db).Cursor(cfg);
			} else if (type == DatabaseType.HASH) {
				HashDatabaseConfig dbConfig = new HashDatabaseConfig();
				dbConfig.Creation = CreatePolicy.IF_NEEDED;
				db = (HashDatabase)HashDatabase.Open(dbFile, dbName, dbConfig);
				cursor = ((HashDatabase)db).Cursor(cfg);
			} else if (type == DatabaseType.QUEUE) {
				QueueDatabaseConfig dbConfig = new QueueDatabaseConfig();
				dbConfig.Creation = CreatePolicy.IF_NEEDED;
				dbConfig.Length = 100;
				db = QueueDatabase.Open(dbFile, dbConfig);
				cursor = ((QueueDatabase)db).Cursor(cfg);
			} else if (type == DatabaseType.RECNO) {
				RecnoDatabaseConfig dbConfig = new RecnoDatabaseConfig();
				dbConfig.Creation = CreatePolicy.IF_NEEDED;
				db = RecnoDatabase.Open(dbFile, dbName, dbConfig);
				cursor = ((RecnoDatabase)db).Cursor(cfg);
			} else
				throw new TestException();

			if (cfg.Priority != null)
				Assert.AreEqual(cursor.Priority, cfg.Priority);
			else
				Assert.AreEqual(CachePriority.DEFAULT, cursor.Priority);

			Cursor dupCursor = cursor.Duplicate(false);
			Assert.AreEqual(cursor.Priority, dupCursor.Priority);
			cursor.Close();
			db.Close();
		}

		[Test]
		public void TestRefresh()
		{
			BTreeDatabase db;
			BTreeCursor cursor;
			DatabaseEnvironment env;
			Transaction txn;

			testName = "TestRefresh";
			SetUpTest(true);

			GetCursorInBtreeDBWithoutEnv(testHome, testName,
			    out db, out cursor);
			// Write a record with cursor and Refresh the cursor.
			MoveCursorToCurrentRec(cursor, 1, 1, 2, 2, null);
			cursor.Dispose();
			db.Dispose();

			Configuration.ClearDir(testHome);
			GetCursorInBtreeDBInTDS(testHome, testName, null, out env, 
			    out db, out cursor, out txn);
			LockingInfo lockingInfo = new LockingInfo();
			lockingInfo.IsolationDegree = Isolation.DEGREE_ONE;
			MoveCursorToCurrentRec(cursor, 1, 1, 2, 2, lockingInfo);
			cursor.Close();
			txn.Commit();
			db.Close();
			env.Close();
		}

		[Test]
		public void TestRefreshWithRMW()
		{
			testName = "TestRefreshWithRMW";
			SetUpTest(true);

			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";
			SetUpTest(true);

			/*
			 * 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";
			SetUpTest(true);

			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<DatabaseEntry, DatabaseEntry> 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<DatabaseEntry,DatabaseEntry>(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 != null && 
			    cursorConfig.IsolationDegree == Isolation.DEGREE_ONE)
				dbConfig.ReadUncommitted = true;

			db = BTreeDatabase.Open(dbFileName, dbConfig, txn);

			// Get a cursor in the transaction.
			if (cursorConfig != null)
				cursor = db.Cursor(cursorConfig, txn);
			else
				cursor = db.Cursor(txn);
		}

		public static void GetCursorInBtreeDB(
		    string home, string name,
		    CursorConfig cursorConfig,
		    out DatabaseEnvironment env, out BTreeDatabase db,
		    out BTreeCursor cursor)
		{
			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);

			/*
			 * 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, null);

			// Get a cursor in the transaction.
			cursor = db.Cursor(cursorConfig, null);
		}
		// 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";
			Configuration.ClearDir(home);

			// 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, 1, 1, 2, 2, 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<DatabaseEntry,
				     DatabaseEntry>(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 < 14; i++)
			{
				DatabaseEntry key, data;
				if (i == 10 || i == 11 || i == 12)
				{
					key = new DatabaseEntry(
					    ASCIIEncoding.ASCII.GetBytes("key"));
					data = new DatabaseEntry(
					    BitConverter.GetBytes(i));
				}
				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 and key/data 
		 * pair with LockingInfo.
		 */
		public void MoveCursor(Cursor dbc,
		    uint kOffset, uint kLen,
		    uint dOffset, uint dLen,
		    LockingInfo lck)
		{
			DatabaseEntry key, data;
			KeyValuePair<DatabaseEntry, DatabaseEntry> pair;

			key = new DatabaseEntry(
			    BitConverter.GetBytes((int)0));
			data = new DatabaseEntry(
			    BitConverter.GetBytes((int)0));
			pair = new KeyValuePair<DatabaseEntry,
			    DatabaseEntry>(key, data);

			if (lck == null) {
				// Move to an existing key.
				Assert.IsTrue(dbc.Move(key, true));

				// Move to an existing key/data pair.
				Assert.IsTrue(dbc.Move(pair, true));

				/* Move to an existing key, and return 
				 * partial data.
				 */
				data = new DatabaseEntry(dOffset, dLen);
				Assert.IsTrue(dbc.Move(key, data, true));
				CheckPartial(null, 0, 0, 
				    dbc.Current.Value, dOffset, dLen);

				// Partially key match.
				byte[] partbytes = new byte[kLen];
				for (int i = 0; i < kLen; i++)
				partbytes[i] = BitConverter.GetBytes(
				    (int)1)[kOffset + i];
				key = new DatabaseEntry(partbytes, kOffset, kLen);
				Assert.IsTrue(dbc.Move(key, false));
				Assert.AreEqual(kLen, dbc.Current.Key.Data.Length);
				CheckPartial(dbc.Current.Key, kOffset, kLen,
				    null, 0, 0);
			} else {
				// 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 to an existing key, and return
				 * partial data.
				 */
				data = new DatabaseEntry(dOffset, dLen);
				Assert.IsTrue(dbc.Move(key, data, true, lck));
				CheckPartial(null, 0, 0,
				    dbc.Current.Value, dOffset, dLen);

				// Partially key match.
				byte[] partbytes = new byte[kLen];
				for (int i = 0; i < kLen; i++)
					partbytes[i] = BitConverter.GetBytes(
					    (int)1)[kOffset + i];
				key = new DatabaseEntry(partbytes, kOffset, kLen);
				Assert.IsTrue(dbc.Move(key, false, lck));
				Assert.AreEqual(kLen, dbc.Current.Key.Data.Length);
				CheckPartial(dbc.Current.Key, kOffset, kLen,
				    null, 0, 0);
			}
		}

		/*
		 * Move the cursor to the first record in a nonempty 
		 * database. The returning value should be true.
		 */
		public void MoveCursorToFirst(Cursor dbc, 
		    uint kOffset, uint kLen,
		    uint dOffset, uint dLen, LockingInfo lck)
		{
			DatabaseEntry key = new DatabaseEntry(kOffset, kLen);
			DatabaseEntry data = new DatabaseEntry(dOffset, dLen);
			if (lck == null) {
				Assert.IsTrue(dbc.MoveFirst());
				Assert.IsTrue(dbc.MoveFirst(key, data));
			} else {
				Assert.IsTrue(dbc.MoveFirst(lck));
				Assert.IsTrue(dbc.MoveFirst(key, data, lck));
			}

			CheckPartial(dbc.Current.Key, kOffset, kLen,
			    dbc.Current.Value, dOffset, dLen);
		}

		/*
		 * Move the cursor to last record in a nonempty 
		 * database. The returning value should be true.
		 */
		public void MoveCursorToLast(Cursor dbc,
		    uint kOffset, uint kLen,
		    uint dOffset, uint dLen, LockingInfo lck)
		{
			DatabaseEntry key = new DatabaseEntry(kOffset, kLen);
			DatabaseEntry data = new DatabaseEntry(dOffset, dLen);
			if (lck == null) {
				Assert.IsTrue(dbc.MoveLast());
				Assert.IsTrue(dbc.MoveLast(key, data));
			} else {
				Assert.IsTrue(dbc.MoveLast(lck));
				Assert.IsTrue(dbc.MoveLast(key, data, lck));
			}
			CheckPartial(dbc.Current.Key, kOffset, kLen,
			    dbc.Current.Value, dOffset, dLen);
		}

		/*
		 * 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,
		    uint kOffset, uint kLen,
		    uint dOffset, uint dLen, LockingInfo lck)
		{
			DatabaseEntry key = new DatabaseEntry(kOffset, kLen);
			DatabaseEntry data = new DatabaseEntry(dOffset, dLen);
			for (int i = 0; i < 5; i++) {
				if (lck == null) {
					Assert.IsTrue(dbc.MoveNext());
					Assert.IsTrue(dbc.MoveNext(key, data));
				} else {
					Assert.IsTrue(dbc.MoveNext(lck));
					Assert.IsTrue(
					    dbc.MoveNext(key, data, lck));
				}
				CheckPartial(dbc.Current.Key, kOffset, kLen,
				    dbc.Current.Value, dOffset, dLen);
			}
		}

		/*
		 * 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,
		    uint kOffset, uint kLen,
		    uint dOffset, uint dLen, 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());
				Assert.IsTrue(dbc.MoveNextDuplicate(
				    new DatabaseEntry(kOffset, kLen),
				    new DatabaseEntry(dOffset, dLen)));
			} 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));
				Assert.IsTrue(dbc.MoveNextDuplicate(
				    new DatabaseEntry(kOffset, kLen),
				    new DatabaseEntry(dOffset, dLen), lck));
			}
		}

		/*
		 * Move the cursor to next unique record in the database.
		 * The returning value should be true.
		 */
		public void MoveCursorToNextUnique(Cursor dbc,
		uint kOffset, uint kLen,
		uint dOffset, uint dLen, LockingInfo lck)
		{
			for (int i = 0; i < 5; i++) {
				if (lck == null) {
					Assert.IsTrue(dbc.MoveNextUnique());
					Assert.IsTrue(dbc.MoveNextUnique(
					    new DatabaseEntry(kOffset, kLen),
					    new DatabaseEntry(dOffset, dLen)));
				} else {
					Assert.IsTrue(dbc.MoveNextUnique(lck));
					Assert.IsTrue(dbc.MoveNextUnique(
					    new DatabaseEntry(kOffset, kLen),
					    new DatabaseEntry(dOffset, dLen),
					    lck));
				}
				CheckPartial(dbc.Current.Key, kOffset, kLen,
				    dbc.Current.Value, dOffset, dLen);
			}
		}

		/*
		 * Move the cursor to previous record in the database.
		 * The returning value should be true;
		 */ 
		public void MoveCursorToPrev(Cursor dbc,
		uint kOffset, uint kLen,
		uint dOffset, uint dLen, LockingInfo lck)
		{
			if (lck == null) {
				dbc.MoveLast();
				for (int i = 0; i < 5; i++) {
					Assert.IsTrue(dbc.MovePrev());
					dbc.MoveNext();
					Assert.IsTrue(dbc.MovePrev(
					    new DatabaseEntry(kOffset, kLen),
					    new DatabaseEntry(dOffset, dLen)));
					CheckPartial(
					    dbc.Current.Key, kOffset, kLen,
					    dbc.Current.Value, dOffset, dLen);
				}
			} else {
				dbc.MoveLast(lck);
				for (int i = 0; i < 5; i++) {
					Assert.IsTrue(dbc.MovePrev(lck));
					dbc.MoveNext();
					Assert.IsTrue(dbc.MovePrev(
					    new DatabaseEntry(kOffset, kLen),
					    new DatabaseEntry(dOffset, dLen),
					    lck));
					CheckPartial(
					    dbc.Current.Key, kOffset, kLen,
					    dbc.Current.Value, dOffset, dLen);
				}
			}
		}

		/*
		 * 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,
		uint kOffset, uint kLen,
		uint dOffset, uint dLen, LockingInfo lck)
		{
			if (lck == null) {
				dbc.Move(new DatabaseEntry(
				    ASCIIEncoding.ASCII.GetBytes("key")), true);
				dbc.MoveNextDuplicate();
				Assert.IsTrue(dbc.MovePrevDuplicate());

				dbc.Move(new DatabaseEntry(
				    ASCIIEncoding.ASCII.GetBytes("key")), true);
				dbc.MoveNextDuplicate();
				Assert.IsTrue(dbc.MovePrevDuplicate(
				    new DatabaseEntry(kOffset, kLen),
				    new DatabaseEntry(dOffset, dLen)));
			} else {
				dbc.Move(new DatabaseEntry(
				    ASCIIEncoding.ASCII.GetBytes("key")),
				    true, lck);
				dbc.MoveNextDuplicate(lck);
				Assert.IsTrue(dbc.MovePrevDuplicate(lck));

				dbc.Move(new DatabaseEntry(
				    ASCIIEncoding.ASCII.GetBytes("key")), 
				    true, lck);
				dbc.MoveNextDuplicate(lck);
				Assert.IsTrue(dbc.MovePrevDuplicate(
				    new DatabaseEntry(kOffset, kLen),
				    new DatabaseEntry(dOffset, dLen), lck));
			}

		CheckPartial(dbc.Current.Key, kOffset, kLen,
		    dbc.Current.Value, dOffset, dLen);
		}

		/*
		 * 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,
		uint kOffset, uint kLen,
		uint dOffset, uint dLen, LockingInfo lck)
		{
			DatabaseEntry key = new DatabaseEntry(kOffset, kLen);
			DatabaseEntry data = new DatabaseEntry(dOffset, dLen);
			for (int i = 0; i < 5; i++) {
				if (lck == null) {
					Assert.IsTrue(dbc.MovePrevUnique());
					Assert.IsTrue(
					    dbc.MovePrevUnique(key, data));
				} else {
					Assert.IsTrue(dbc.MovePrevUnique(lck));
					Assert.IsTrue(
					    dbc.MovePrevUnique(key, data, lck));
				}
				CheckPartial(dbc.Current.Key, kOffset, kLen,
				    dbc.Current.Value, dOffset, dLen);
			}
		}

		/*
		 * Move the cursor to current existing record. The returning 
		 * value should be true.
		 */ 
		public void MoveCursorToCurrentRec(Cursor dbc,
		    uint kOffset, uint kLen,
		    uint dOffset, uint dLen, LockingInfo lck)
		{
			DatabaseEntry key, data;

			// Add a record to the database.
			KeyValuePair<DatabaseEntry, DatabaseEntry> pair;
			key = new DatabaseEntry(
			    ASCIIEncoding.ASCII.GetBytes("key"));
			data = new DatabaseEntry(
			    ASCIIEncoding.ASCII.GetBytes("data"));
			pair = new KeyValuePair<DatabaseEntry,
			    DatabaseEntry>(key, data);
			dbc.Add(pair);

			if (lck == null)
				Assert.IsTrue(dbc.Refresh());
			else
			Assert.IsTrue(dbc.Refresh(lck));
			Assert.AreEqual(key.Data, dbc.Current.Key.Data);
			Assert.AreEqual(data.Data, dbc.Current.Value.Data);

			key = new DatabaseEntry(kOffset, kLen);
			data = new DatabaseEntry(dOffset, dLen);
			if (lck == null)
				Assert.IsTrue(dbc.Refresh(key, data));
			else
				Assert.IsTrue(dbc.Refresh(key, data, lck));
			CheckPartial(dbc.Current.Key, kOffset, kLen,
			    dbc.Current.Value, dOffset, dLen);
		}

		public void CheckPartial(
		    DatabaseEntry key, uint kOffset, uint kLen,
		    DatabaseEntry data, uint dOffset, uint dLen) 
		{
			if (key != null) {
			    Assert.IsTrue(key.Partial);
			    Assert.AreEqual(kOffset, key.PartialOffset);
			    Assert.AreEqual(kLen, key.PartialLen);
			    Assert.AreEqual(kLen, (uint)key.Data.Length);
			}

			if (data != null) {
			    Assert.IsTrue(data.Partial);
			    Assert.AreEqual(dOffset, data.PartialOffset);
			    Assert.AreEqual(dLen, data.PartialLen);
			    Assert.AreEqual(dLen, (uint)data.Data.Length);
			}
		}
	}
}
