# See the file LICENSE for redistribution information.
#
# Copyright (c) 2004,2007 Oracle.  All rights reserved.
#
# $Id: test107.tcl,v 12.8 2007/05/17 15:15:56 bostic Exp $
#
# TEST	test107
# TEST	Test of read-committed (degree 2 isolation). [#8689]
# TEST
# TEST	We set up a database.  Open a read-committed transactional cursor and
# TEST	a regular transactional cursor on it. Position each cursor on one page,
# TEST	and do a put to a different page.
# TEST
# TEST	Make sure that:
# TEST	- the put succeeds if we are using degree 2 isolation.
# TEST	- the put deadlocks within a regular transaction with
# TEST 	a regular cursor.
# TEST
proc test107 { method args } {
	source ./include.tcl
	global fixed_len
	global passwd
	set tnum "107"

	# If we are using an env, then skip this test.  It needs its own.
	set eindex [lsearch -exact $args "-env"]
	if { $eindex != -1 } {
		incr eindex
		set env [lindex $args $eindex]
		puts "Test$tnum skipping for env $env"
		return
	}

	# We'll make the data pretty good sized so we can easily
	# move to a different page.  Make the data size a little
	# smaller for fixed-length methods so it works with
	# pagesize 512 tests.
	set data_size 512
	set orig_fixed_len $fixed_len
	set fixed_len [expr $data_size - [expr $data_size / 8]]
	set args [convert_args $method $args]
	set encargs ""
	set ddargs ""
	set args [split_encargs $args encargs]
	if { $encargs != "" } {
		set ddargs " -P $passwd "
	}
	set omethod [convert_method $method]

	puts "Test$tnum: Degree 2 Isolation Test ($method $args)"
	set testfile test$tnum.db
	env_cleanup $testdir

	# Create the environment.
	set timeout 10
	set env [eval {berkdb_env -create -mode 0644 -lock \
	    -cachesize { 0 1048576 1 } \
	    -lock_timeout $timeout -txn} $encargs -home $testdir]
	error_check_good env_open [is_valid_env $env] TRUE

	# Create the database.
	set db [eval {berkdb_open -env $env -create -auto_commit\
	    -mode 0644 $omethod} $args {$testfile}]
	error_check_good dbopen [is_valid_db $db] TRUE

	puts "\tTest$tnum.a: put loop"
	# The data doesn't need to change from key to key.
	# Use numerical keys so we don't need special handling for
	# record-based methods.
	set origdata "data"
	set len [string length $origdata]
	set data [repeat $origdata [expr $data_size / $len]]
	set nentries 200
	set txn [$env txn]
	for { set i 1 } { $i <= $nentries } { incr i } {
		set key $i
		set ret [eval {$db put} \
		    -txn $txn {$key [chop_data $method $data]}]
		error_check_good put:$db $ret 0
	}
	error_check_good txn_commit [$txn commit] 0

	puts "\tTest$tnum.b: Start deadlock detector."
	# Start up a deadlock detector so we can break self-deadlocks.
	set dpid [eval {exec $util_path/db_deadlock} -v -ae -t 1.0 \
	    -h $testdir $ddargs >& $testdir/dd.out &]

	puts "\tTest$tnum.c: Open txns and cursors."
	# We can get degree 2 isolation with either a degree 2
	# txn or a degree 2 cursor or both.  However, the case
	# of a regular txn and regular cursor should deadlock.
	# We put this case last so it won't deadlock the cases
	# which should succeed.
	#
	# Cursors and transactions are named according to
	# whether they specify degree 2 (c2, t2) or not (c, t).
	# Set up all four possibilities.
	#
	set t [$env txn]
	error_check_good reg_txn_begin [is_valid_txn $t $env] TRUE
	set t2 [$env txn -read_committed]
	error_check_good deg2_txn_begin [is_valid_txn $t2 $env] TRUE

	set c2t [$db cursor -txn $t -read_committed]
	error_check_good valid_c2t [is_valid_cursor $c2t $db] TRUE
	set ct2 [$db cursor -txn $t2]
	error_check_good valid_ct2 [is_valid_cursor $ct2 $db] TRUE
	set c2t2 [$db cursor -txn $t2 -read_committed]
	error_check_good valid_c2t2 [is_valid_cursor $c2t2 $db] TRUE
	set ct [$db cursor -txn $t]
	error_check_good valid_ct [is_valid_cursor $ct $db] TRUE

	set curslist [list $c2t $ct2 $c2t2 $ct]
	set newdata newdata
	set offpagekey [expr $nentries - 1]

	# For one cursor at a time, read the first item in the
	# database, then move to an item on a different page.
	# Put a new value in the first item on the first page.  This
	# should work with degree 2 isolation and hang without it.
	#
	# Wrap the whole thing in a catch statement so we still get
	# around to killing the deadlock detector and cleaning up
	# even if the test fails.
	#
	puts "\tTest$tnum.d: Test for read-committed (degree 2 isolation)."
	set status [catch {
		foreach cursor $curslist {
			set retfirst [$cursor get -first]
			set firstkey [lindex [lindex $retfirst 0] 0]
			set ret [$cursor get -set $offpagekey]
			error_check_good cursor_off_page \
			    [lindex [lindex $ret 0] 0] $offpagekey
			if { [catch {eval {$db put} \
			    $firstkey [chop_data $method $newdata]} res]} {
				error_check_good error_is_deadlock \
				    [is_substr $res DB_LOCK_DEADLOCK] 1
				error_check_good right_cursor_failed $cursor $ct
			} else {
				set ret [lindex [lindex [$db get $firstkey] 0] 1]
				error_check_good data_changed \
				    $ret [pad_data $method $newdata]
				error_check_bad right_cursor_succeeded $cursor $ct
			}
			error_check_good close_cursor [$cursor close] 0
		}
	} res]
	if { $status != 0 } {
		puts $res
	}

	# Smoke test for db_stat -txn -read_committed.
	puts "\tTest$tnum.e: Smoke test for db_stat -txn -read_committed"
	if { [catch {set statret [$db stat -txn $t -read_committed]} res] } {
		puts "FAIL: db_stat -txn -read_committed returned $res"
	}

	# End deadlock detection and clean up handles
	puts "\tTest$tnum.f: Clean up."
	tclkill $dpid
	set fixed_len $orig_fixed_len
	error_check_good t_commit [$t commit] 0
	error_check_good t2_commit [$t2 commit] 0
	error_check_good dbclose [$db close] 0
	error_check_good envclose [$env close] 0
}
