1# See the file LICENSE for redistribution information.
2#
3# Copyright (c) 1999,2008 Oracle.  All rights reserved.
4#
5# $Id: sdb012.tcl,v 12.8 2008/01/08 20:58:53 bostic Exp $
6#
7# TEST	sdb012
8# TEST	Test subdbs with locking and transactions
9# TEST  Tests creating and removing subdbs while handles
10# TEST  are open works correctly, and in the face of txns.
11#
12proc sdb012 { method args } {
13	source ./include.tcl
14
15	set args [convert_args $method $args]
16	set omethod [convert_method $method]
17
18	if { [is_queue $method] == 1 } {
19		puts "Subdb012: skipping for method $method"
20		return
21	}
22
23	# If we are using an env, then skip this test.  It needs its own.
24	set eindex [lsearch -exact $args "-env"]
25	if { $eindex != -1 } {
26		incr eindex
27		set env [lindex $args $eindex]
28		puts "Subdb012 skipping for env $env"
29		return
30	}
31	set encargs ""
32	set largs [split_encargs $args encargs]
33
34	puts "Subdb012: $method ($largs $encargs) subdb txn/locking tests"
35
36	#
37	# sdb012_body takes a txn list containing 4 elements.
38	# {txn command for first subdb
39	#  txn command for second subdb
40	#  txn command for first subdb removal
41	#  txn command for second subdb removal}
42	#
43	# The allowed commands are 'none' 'one', 'auto', 'abort', 'commit'.
44	# 'none' is a special case meaning run without a txn.  In the
45	# case where all 4 items are 'none', we run in a lock-only env.
46	# 'one' is a special case meaning we create the subdbs together
47	# in one single transaction.  It is indicated as the value for t1,
48	# and the value in t2 indicates if that single txn should be
49	# aborted or committed.  It is not used and has no meaning
50	# in the removal case.  'auto' means use the -auto_commit flag
51	# to the operation, and 'abort' and 'commit' do the obvious.
52	# "-auto" is applied only to the creation of the subdbs, since
53	# it is done by default on database removes in transactional
54	# environments.
55	#
56	# First test locking w/o txns.  If any in tlist are 'none',
57	# all must be none.
58	#
59	# Now run through the txn-based operations
60	set count 0
61	set sdb "Subdb012."
62	set teststr "abcdefghijklmnopqrstuvwxyz"
63	set testlet [split $teststr {}]
64	foreach t1 { none one abort auto commit } {
65		foreach t2 { none abort auto commit } {
66			if { $t1 == "one" } {
67				if { $t2 == "none" || $t2 == "auto"} {
68					continue
69				}
70			}
71			set tlet [lindex $testlet $count]
72			foreach r1 { none abort commit } {
73				foreach r2 { none abort commit } {
74					set tlist [list $t1 $t2 $r1 $r2]
75					set nnone [llength \
76					    [lsearch -all $tlist none]]
77					if { $nnone != 0 && $nnone != 4 } {
78						continue
79					}
80					sdb012_body $testdir $omethod $largs \
81					    $encargs $sdb$tlet $tlist
82				}
83			}
84			incr count
85		}
86	}
87
88}
89
90proc s012 { method args } {
91	source ./include.tcl
92
93	set omethod [convert_method $method]
94
95	set encargs ""
96	set largs ""
97
98	puts "Subdb012: $method ($largs $encargs) subdb txn/locking tests"
99
100	set sdb "Subdb012."
101	set tlet X
102	set tlist $args
103	error_check_good tlist [llength $tlist] 4
104	sdb012_body $testdir $omethod $largs $encargs $sdb$tlet $tlist
105}
106
107#
108# This proc checks the tlist values and returns the flags
109# that should be used when opening the env.  If we are running
110# with no txns, then just -lock, otherwise -txn.
111#
112proc sdb012_subsys { tlist } {
113	set t1 [lindex $tlist 0]
114	#
115	# If we have no txns, all elements of the list should be none.
116	# In that case we only run with locking turned on.
117	# Otherwise, we use the full txn subsystems.
118	#
119	set allnone {none none none none}
120	if { $allnone == $tlist } {
121		set subsys "-lock"
122	} else {
123		set subsys "-txn"
124	}
125	return $subsys
126}
127
128#
129# This proc parses the tlist and returns a list of 4 items that
130# should be used in operations.  I.e. it will begin the txns as
131# needed, or return a -auto_commit flag, etc.
132#
133proc sdb012_tflags { env tlist } {
134	set ret ""
135	set t1 ""
136	foreach t $tlist {
137		switch $t {
138		one {
139			set t1 [$env txn]
140			error_check_good txnbegin [is_valid_txn $t1 $env] TRUE
141			lappend ret "-txn $t1"
142			lappend ret "-txn $t1"
143		}
144		auto {
145			lappend ret "-auto_commit"
146		}
147		abort -
148		commit {
149			#
150			# If the previous command was a "one", skip over
151			# this commit/abort.  Otherwise start a new txn
152			# for the removal case.
153			#
154			if { $t1 == "" } {
155				set txn [$env txn]
156				error_check_good txnbegin [is_valid_txn $txn \
157				    $env] TRUE
158				lappend ret "-txn $txn"
159			} else {
160				set t1 ""
161			}
162		}
163		none {
164			lappend ret ""
165		}
166		default {
167			error "Txn command $t not implemented"
168		}
169		}
170	}
171	return $ret
172}
173
174#
175# This proc parses the tlist and returns a list of 4 items that
176# should be used in the txn conclusion operations.  I.e. it will
177# give "" if using auto_commit (i.e. no final txn op), or a single
178# abort/commit if both subdb's are in one txn.
179#
180proc sdb012_top { tflags tlist } {
181	set ret ""
182	set t1 ""
183	#
184	# We know both lists have 4 items.  Iterate over them
185	# using multiple value lists so we know which txn goes
186	# with each op.
187	#
188	# The tflags list is needed to extract the txn command
189	# out for the operation.  The tlist list is needed to
190	# determine what operation we are doing.
191	#
192	foreach t $tlist tf $tflags {
193		switch $t {
194		one {
195			set t1 [lindex $tf 1]
196		}
197		auto {
198			lappend ret "sdb012_nop"
199		}
200		abort -
201		commit {
202			#
203			# If the previous command was a "one" (i.e. t1
204			# is set), append a correct command and then
205			# an empty one.
206			#
207			if { $t1 == "" } {
208				set txn [lindex $tf 1]
209				set top "$txn $t"
210				lappend ret $top
211			} else {
212				set top "$t1 $t"
213				lappend ret "sdb012_nop"
214				lappend ret $top
215				set t1 ""
216			}
217		}
218		none {
219			lappend ret "sdb012_nop"
220		}
221		}
222	}
223	return $ret
224}
225
226proc sdb012_nop { } {
227	return 0
228}
229
230proc sdb012_isabort { tlist item } {
231	set i [lindex $tlist $item]
232	if { $i == "one" } {
233		set i [lindex $tlist [expr $item + 1]]
234	}
235	if { $i == "abort" } {
236		return 1
237	} else {
238		return 0
239	}
240}
241
242proc sdb012_body { testdir omethod largs encargs msg tlist } {
243
244	puts "\t$msg: $tlist"
245	set testfile subdb012.db
246	set subdb1 sub1
247	set subdb2 sub2
248
249	set subsys [sdb012_subsys $tlist]
250	env_cleanup $testdir
251	set env [eval {berkdb_env -create -home} $testdir $subsys $encargs]
252	error_check_good dbenv [is_valid_env $env] TRUE
253	error_check_good test_lock [$env test abort subdb_lock] 0
254
255	#
256	# Convert from our tlist txn commands into real flags we
257	# will pass to commands.  Use the multiple values feature
258	# of foreach to do this efficiently.
259	#
260	set tflags [sdb012_tflags $env $tlist]
261	foreach {txn1 txn2 rem1 rem2} $tflags {break}
262	foreach {top1 top2 rop1 rop2} [sdb012_top $tflags $tlist] {break}
263
264# puts "txn1 $txn1, txn2 $txn2, rem1 $rem1, rem2 $rem2"
265# puts "top1 $top1, top2 $top2, rop1 $rop1, rop2 $rop2"
266	puts "\t$msg.0: Create sub databases in env with $subsys"
267	set s1 [eval {berkdb_open -env $env -create -mode 0644} \
268	    $largs $txn1 {$omethod $testfile $subdb1}]
269	error_check_good dbopen [is_valid_db $s1] TRUE
270
271	set ret [eval $top1]
272	error_check_good t1_end $ret 0
273
274	set s2 [eval {berkdb_open -env $env -create -mode 0644} \
275	    $largs $txn2 {$omethod $testfile $subdb2}]
276	error_check_good dbopen [is_valid_db $s2] TRUE
277
278	puts "\t$msg.1: Subdbs are open; resolve txns if necessary"
279	set ret [eval $top2]
280	error_check_good t2_end $ret 0
281
282	set t1_isabort [sdb012_isabort $tlist 0]
283	set t2_isabort [sdb012_isabort $tlist 1]
284	set r1_isabort [sdb012_isabort $tlist 2]
285	set r2_isabort [sdb012_isabort $tlist 3]
286
287# puts "t1_isabort $t1_isabort, t2_isabort $t2_isabort, r1_isabort $r1_isabort, r2_isabort $r2_isabort"
288
289	puts "\t$msg.2: Subdbs are open; verify removal failures"
290	# Verify removes of subdbs with open subdb's fail
291	#
292	# We should fail no matter what.  If we aborted, then the
293	# subdb should not exist.  If we didn't abort, we should fail
294	# with DB_LOCK_NOTGRANTED.
295	#
296	# XXX - Do we need -auto_commit for all these failing ones?
297	set r [ catch {berkdb dbremove -env $env $testfile $subdb1} result ]
298	error_check_bad dbremove1_open $r 0
299	if { $t1_isabort } {
300		error_check_good dbremove1_open_ab [is_substr \
301		    $result "no such file"] 1
302	} else {
303		error_check_good dbremove1_open [is_substr \
304		    $result DB_LOCK_NOTGRANTED] 1
305	}
306
307	set r [ catch {berkdb dbremove -env $env $testfile $subdb2} result ]
308	error_check_bad dbremove2_open $r 0
309	if { $t2_isabort } {
310		error_check_good dbremove2_open_ab [is_substr \
311		    $result "no such file"] 1
312	} else {
313		error_check_good dbremove2_open [is_substr \
314		    $result DB_LOCK_NOTGRANTED] 1
315	}
316
317	# Verify file remove fails
318	set r [catch {berkdb dbremove -env $env $testfile} result]
319	error_check_bad dbremovef_open $r 0
320
321	#
322	# If both aborted, there should be no file??
323	#
324	if { $t1_isabort && $t2_isabort } {
325		error_check_good dbremovef_open_ab [is_substr \
326		    $result "no such file"] 1
327	} else {
328		error_check_good dbremovef_open [is_substr \
329		    $result DB_LOCK_NOTGRANTED] 1
330	}
331
332	puts "\t$msg.3: Close subdb2; verify removals"
333	error_check_good close_s2 [$s2 close] 0
334	set r [ catch {eval {berkdb dbremove -env} \
335	    $env $rem2 $testfile $subdb2} result ]
336	if { $t2_isabort } {
337		error_check_bad dbrem2_ab $r 0
338		error_check_good dbrem2_ab [is_substr \
339		    $result "no such file"] 1
340	} else {
341		error_check_good dbrem2 $result 0
342	}
343	# Resolve subdb2 removal txn
344	set r [eval $rop2]
345	error_check_good rop2 $r 0
346
347	set r [ catch {berkdb dbremove -env $env $testfile $subdb1} result ]
348	error_check_bad dbremove1.2_open $r 0
349	if { $t1_isabort } {
350		error_check_good dbremove1.2_open_ab [is_substr \
351		    $result "no such file"] 1
352	} else {
353		error_check_good dbremove1.2_open [is_substr \
354		    $result DB_LOCK_NOTGRANTED] 1
355	}
356
357	# There are three cases here:
358	# 1. if both t1 and t2 aborted, the file shouldn't exist
359	# 2. if only t1 aborted, the file still exists and nothing is open
360	# 3. if neither aborted a remove should fail because the first
361	#	 subdb is still open
362	# In case 2, don't try the remove, because it should succeed
363	# and we won't be able to test anything else.
364	if { !$t1_isabort || $t2_isabort } {
365		set r [catch {berkdb dbremove -env $env $testfile} result]
366		if { $t1_isabort && $t2_isabort } {
367			error_check_bad dbremovef.2_open $r 0
368			error_check_good dbremove.2_open_ab [is_substr \
369			    $result "no such file"] 1
370		} else {
371			error_check_bad dbremovef.2_open $r 0
372			error_check_good dbremove.2_open [is_substr \
373			    $result DB_LOCK_NOTGRANTED] 1
374		}
375	}
376
377	puts "\t$msg.4: Close subdb1; verify removals"
378	error_check_good close_s1 [$s1 close] 0
379	set r [ catch {eval {berkdb dbremove -env} \
380	    $env $rem1 $testfile $subdb1} result ]
381	if { $t1_isabort } {
382		error_check_bad dbremove1_ab $r 0
383		error_check_good dbremove1_ab [is_substr \
384		    $result "no such file"] 1
385	} else {
386		error_check_good dbremove1 $result 0
387	}
388	# Resolve subdb1 removal txn
389	set r [eval $rop1]
390	error_check_good rop1 $r 0
391
392	# Verify removal of subdb2.  All DB handles are closed now.
393	# So we have two scenarios:
394	#	1.  The removal of subdb2 above was successful and subdb2
395	#	    doesn't exist and we should fail that way.
396	#	2.  The removal of subdb2 above was aborted, and this
397	#	    removal should succeed.
398	#
399	set r [ catch {berkdb dbremove -env $env $testfile $subdb2} result ]
400	if { $r2_isabort && !$t2_isabort } {
401		error_check_good dbremove2.1_ab $result 0
402	} else {
403		error_check_bad dbremove2.1 $r 0
404		error_check_good dbremove2.1 [is_substr \
405		    $result "no such file"] 1
406	}
407
408	# Verify removal of subdb1.  All DB handles are closed now.
409	# So we have two scenarios:
410	#	1.  The removal of subdb1 above was successful and subdb1
411	#	    doesn't exist and we should fail that way.
412	#	2.  The removal of subdb1 above was aborted, and this
413	#	    removal should succeed.
414	#
415	set r [ catch {berkdb dbremove -env $env $testfile $subdb1} result ]
416	if { $r1_isabort && !$t1_isabort } {
417		error_check_good dbremove1.1 $result 0
418	} else {
419		error_check_bad dbremove_open $r 0
420		error_check_good dbremove.1 [is_substr \
421		    $result "no such file"] 1
422	}
423
424	puts "\t$msg.5: All closed; remove file"
425	set r [catch {berkdb dbremove -env $env $testfile} result]
426	if { $t1_isabort && $t2_isabort } {
427		error_check_bad dbremove_final_ab $r 0
428		error_check_good dbremove_file_abstr [is_substr \
429		    $result "no such file"] 1
430	} else {
431		error_check_good dbremove_final $r 0
432	}
433	error_check_good envclose [$env close] 0
434}
435