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