1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2009 Oracle.  All rights reserved.
5 *
6 */
7using System;
8using System.Collections;
9using System.Collections.Generic;
10using System.IO;
11using System.Text;
12using System.Threading;
13using System.Xml;
14using NUnit.Framework;
15using BerkeleyDB;
16
17namespace CsharpAPITest
18{
19	[TestFixture]
20	public class TransactionTest
21	{
22		private string testFixtureHome;
23		private string testFixtureName;
24		private string testName;
25		private string testHome;
26
27		private DatabaseEnvironment deadLockEnv;
28
29		[TestFixtureSetUp]
30		public void RunBeforeTests()
31		{
32			testFixtureName = "TransactionTest";
33			testFixtureHome = "./TestOut/" + testFixtureName;
34
35			DatabaseEnvironment.Remove(testFixtureHome);
36		}
37
38		[Test, ExpectedException(typeof(ExpectedTestException))]
39		public void TestAbort()
40		{
41			testName = "TestAbort";
42			testHome = testFixtureHome + "/" + testName;
43
44			Configuration.ClearDir(testHome);
45
46			DatabaseEnvironment env;
47			Transaction txn;
48			BTreeDatabase db;
49
50			/*
51			 * Open an environment and begin a transaction. Open
52			 * a db and write a record the db within this transaction.
53			 */
54			PutRecordWithTxn(out env, testHome, testName, out txn);
55
56			// Abort the transaction.
57			txn.Abort();
58
59			/*
60			 * Undo all operations in the transaction so the
61			 * database couldn't be reopened.
62			 */
63			try
64			{
65				OpenBtreeDBInEnv(testName + ".db", env,
66				    out db, false, null);
67			}
68			catch (DatabaseException)
69			{
70				throw new ExpectedTestException();
71			}
72			finally
73			{
74				env.Close();
75			}
76		}
77
78		[Test]
79		public void TestCommit()
80		{
81			testName = "TestCommit";
82			testHome = testFixtureHome + "/" + testName;
83
84			Configuration.ClearDir(testHome);
85
86			DatabaseEnvironment env;
87			Transaction txn;
88			BTreeDatabase db;
89
90			/*
91			 * Open an environment and begin a transaction. Open
92			 * a db and write a record the db within this transaction.
93			 */
94			PutRecordWithTxn(out env, testHome, testName, out txn);
95
96			// Commit the transaction.
97			txn.Commit();
98
99			// Reopen the database.
100			OpenBtreeDBInEnv(testName + ".db", env,
101			    out db, false, null);
102
103			/*
104			 * Confirm that the record("key", "data") exists in the
105			 * database.
106			 */
107			try
108			{
109				db.GetBoth(new DatabaseEntry(
110				    ASCIIEncoding.ASCII.GetBytes("key")),
111				    new DatabaseEntry(
112				    ASCIIEncoding.ASCII.GetBytes("data")));
113			}
114			catch (DatabaseException)
115			{
116				throw new TestException();
117			}
118			finally
119			{
120				db.Close();
121				env.Close();
122			}
123		}
124
125		[Test, ExpectedException(typeof(ExpectedTestException))]
126		public void TestDiscard()
127		{
128			DatabaseEnvironment env;
129			byte[] gid;
130
131			testName = "TestDiscard";
132			testHome = testFixtureHome + "/" + testName;
133
134			Configuration.ClearDir(testHome);
135
136			/*
137			 * Open an environment and begin a transaction
138			 * called "transaction". Within the transacion, open a
139			 * database, write a record and close it. Then prepare
140			 * the transaction and panic the environment.
141			 */
142			PanicPreparedTxn(testHome, testName, out env, out gid);
143
144			/*
145			 * Recover the environment. 	Log and db files are not
146			 * destoyed so run normal recovery. Recovery should
147			 * use DB_CREATE and DB_INIT_TXN flags when
148			 * opening the environment.
149			 */
150			DatabaseEnvironmentConfig envConfig
151			    = new DatabaseEnvironmentConfig();
152			envConfig.RunRecovery = true;
153			envConfig.Create = true;
154			envConfig.UseTxns = true;
155			envConfig.UseMPool = true;
156			env = DatabaseEnvironment.Open(testHome, envConfig);
157
158			PreparedTransaction[] preparedTxns
159			    = new PreparedTransaction[10];
160			preparedTxns = env.Recover(10, true);
161
162			Assert.AreEqual(gid, preparedTxns[0].GlobalID);
163			preparedTxns[0].Txn.Discard();
164			try
165			{
166				preparedTxns[0].Txn.Commit();
167			}
168			catch (AccessViolationException)
169			{
170				throw new ExpectedTestException();
171			}
172			finally
173			{
174				env.Close();
175			}
176		}
177
178		[Test]
179		public void TestPrepare()
180		{
181			testName = "TestPrepare";
182			testHome = testFixtureHome + "/" + testName;
183
184			DatabaseEnvironment env;
185			byte[] gid;
186
187			Configuration.ClearDir(testHome);
188
189			/*
190			 * Open an environment and begin a transaction
191			 * called "transaction". Within the transacion, open a
192			 * database, write a record and close it. Then prepare
193			 * the transaction and panic the environment.
194			 */
195			PanicPreparedTxn(testHome, testName, out env, out gid);
196
197			/*
198			 * Recover the environment. 	Log and db files are not
199			 * destoyed so run normal recovery. Recovery should
200			 * use DB_CREATE and DB_INIT_TXN flags when
201			 * opening the environment.
202			 */
203			DatabaseEnvironmentConfig envConfig
204			    = new DatabaseEnvironmentConfig();
205			envConfig.RunRecovery = true;
206			envConfig.Create = true;
207			envConfig.UseTxns = true;
208			envConfig.UseMPool = true;
209			env = DatabaseEnvironment.Open(testHome, envConfig);
210
211			// Reopen the database.
212			BTreeDatabase db;
213			OpenBtreeDBInEnv(testName + ".db", env, out db,
214			    false, null);
215
216			/*
217			 * Confirm that record("key", "data") exists in the
218			 * database.
219			 */
220			DatabaseEntry key, data;
221			key = new DatabaseEntry(
222			    ASCIIEncoding.ASCII.GetBytes("key"));
223			data = new DatabaseEntry(
224			    ASCIIEncoding.ASCII.GetBytes("data"));
225			try
226			{
227				db.GetBoth(key, data);
228			}
229			catch (DatabaseException)
230			{
231				throw new TestException();
232			}
233			finally
234			{
235				db.Close();
236				env.Close();
237			}
238
239		}
240
241		public void PanicPreparedTxn(string home, string dbName,
242		    out DatabaseEnvironment env, out byte[] globalID)
243		{
244			Transaction txn;
245
246			// Put record into database within transaction.
247			PutRecordWithTxn(out env, home, dbName, out txn);
248
249			/*
250			 * Generate global ID for the transaction. Copy
251			 * transaction ID to the first 4 tyes in global ID.
252			 */
253			globalID = new byte[Transaction.GlobalIdLength];
254			byte[] txnID = new byte[4];
255			txnID = BitConverter.GetBytes(txn.Id);
256			for (int i = 0; i < txnID.Length; i++)
257				globalID[i] = txnID[i];
258
259			// Prepare the transaction.
260			txn.Prepare(globalID);
261
262			// Panic the environment.
263			env.Panic();
264
265		}
266
267		[Test]
268		public void TestTxnName()
269		{
270			DatabaseEnvironment env;
271			Transaction txn;
272
273			testName = "TestTxnName";
274			testHome = testFixtureHome + "/" + testName;
275
276			Configuration.ClearDir(testHome);
277
278			SetUpTransactionalEnv(testHome, out env);
279			txn = env.BeginTransaction();
280			txn.Name = testName;
281			Assert.AreEqual(testName, txn.Name);
282			txn.Commit();
283			env.Close();
284		}
285
286		[Test]
287		public void TestSetLockTimeout()
288		{
289			testName = "TestSetLockTimeout";
290			testHome = testFixtureHome + "/" + testName;
291
292			Configuration.ClearDir(testHome);
293
294			// Set lock timeout.
295			TestTimeOut(true);
296
297		}
298
299		[Test]
300		public void TestSetTxnTimeout()
301		{
302			testName = "TestSetTxnTimeout";
303			testHome = testFixtureHome + "/" + testName;
304
305			Configuration.ClearDir(testHome);
306
307			// Set transaction time out.
308			TestTimeOut(false);
309
310		}
311
312		/*
313		 * ifSetLock is used to indicate which timeout function
314		 * is used, SetLockTimeout or SetTxnTimeout.
315		 */
316		public void TestTimeOut(bool ifSetLock)
317		{
318			// Open environment and begin transaction.
319			Transaction txn;
320			deadLockEnv = null;
321			SetUpEnvWithTxnAndLocking(testHome,
322			    out deadLockEnv, out txn, 0, 0, 0, 0);
323
324			// Define deadlock detection and resolve policy.
325			deadLockEnv.DeadlockResolution =
326			    DeadlockPolicy.YOUNGEST;
327			if (ifSetLock == true)
328				txn.SetLockTimeout(10);
329			else
330				txn.SetTxnTimeout(10);
331
332			txn.Commit();
333			deadLockEnv.Close();
334		}
335
336		public static void SetUpEnvWithTxnAndLocking(string envHome,
337		    out DatabaseEnvironment env, out Transaction txn,
338		    uint maxLock, uint maxLocker, uint maxObject, uint partition)
339		{
340			// Configure env and locking subsystem.
341			LockingConfig lkConfig = new LockingConfig();
342
343			/*
344			 * If the maximum number of locks/lockers/objects
345			 * is given, then the LockingConfig is set. Unless,
346			 * it is not set to any value.
347			 */
348			if (maxLock != 0)
349				lkConfig.MaxLocks = maxLock;
350			if (maxLocker != 0)
351				lkConfig.MaxLockers = maxLocker;
352			if (maxObject != 0)
353				lkConfig.MaxObjects = maxObject;
354			if (partition != 0)
355				lkConfig.Partitions = partition;
356
357			DatabaseEnvironmentConfig envConfig =
358				new DatabaseEnvironmentConfig();
359			envConfig.Create = true;
360			envConfig.UseTxns = true;
361			envConfig.UseMPool = true;
362			envConfig.LockSystemCfg = lkConfig;
363			envConfig.UseLocking = true;
364			envConfig.NoLocking = false;
365			env = DatabaseEnvironment.Open(envHome, envConfig);
366			txn = env.BeginTransaction();
367		}
368
369		public void PutRecordWithTxn(out DatabaseEnvironment env,
370		    string home, string dbName, out Transaction txn)
371		{
372			BTreeDatabase db;
373
374			// Open a new environment and begin a transaction.
375			SetUpTransactionalEnv(home, out env);
376			TransactionConfig txnConfig = new TransactionConfig();
377			txnConfig.Name = "Transaction";
378			txn = env.BeginTransaction(txnConfig);
379			Assert.AreEqual("Transaction", txn.Name);
380
381			// Open a new database within the transaction.
382			OpenBtreeDBInEnv(dbName + ".db", env, out db, true, txn);
383
384			// Write to the database within the transaction.
385			WriteOneIntoBtreeDBWithTxn(db, txn);
386
387			// Close the database.
388			db.Close();
389		}
390
391		public void SetUpTransactionalEnv(string home,
392		    out DatabaseEnvironment env)
393		{
394			DatabaseEnvironmentConfig envConfig =
395			    new DatabaseEnvironmentConfig();
396			envConfig.Create = true;
397			envConfig.UseLogging = true;
398			envConfig.UseMPool = true;
399			envConfig.UseTxns = true;
400			env = DatabaseEnvironment.Open(
401			    home, envConfig);
402		}
403
404		public void OpenBtreeDBInEnv(string dbName,
405		    DatabaseEnvironment env, out BTreeDatabase db,
406		    bool create, Transaction txn)
407		{
408			BTreeDatabaseConfig btreeDBConfig =
409			    new BTreeDatabaseConfig();
410			btreeDBConfig.Env = env;
411			if (create == true)
412				btreeDBConfig.Creation = CreatePolicy.IF_NEEDED;
413			else
414				btreeDBConfig.Creation = CreatePolicy.NEVER;
415			if (txn == null)
416				db = BTreeDatabase.Open(dbName,
417				    btreeDBConfig);
418			else
419				db = BTreeDatabase.Open(dbName,
420				    btreeDBConfig, txn);
421		}
422
423		public void WriteOneIntoBtreeDBWithTxn(BTreeDatabase db,
424		    Transaction txn)
425		{
426			DatabaseEntry key, data;
427
428			key = new DatabaseEntry(
429			    ASCIIEncoding.ASCII.GetBytes("key"));
430			data = new DatabaseEntry(
431			    ASCIIEncoding.ASCII.GetBytes("data"));
432			db.Put(key, data, txn);
433		}
434	}
435}
436