# See the file LICENSE for redistribution information.
#
# Copyright (c) 1999, 2000
#	Sleepycat Software.  All rights reserved.
#
#	$Id: recd014.tcl,v 1.9 2001/01/11 17:16:04 sue Exp $
#
# Recovery Test 14.
# This is a recovery test for create/delete of queue extents. We have
# hooks in the database so that we can abort the process at various
# points and make sure that the extent file does or does not exist.  We
# then need to recover and make sure the file is correctly existing
# or not, as the case may be.
proc recd014 { method args} {
	global fixed_len
	source ./include.tcl

	if { ![is_queueext $method] == 1 } {
		puts "Recd014: Skipping for method $method"
		return
	}
	set pgindex [lsearch -exact $args "-pagesize"]
	if { $pgindex != -1 } {
		puts "Recd014: skipping for specific pagesizes"
		return
	}

	set orig_fixed_len $fixed_len
	#
	# We will use 512-byte pages, to be able to control
	# when extents get created/removed.
	#
	set fixed_len 300

	set opts [convert_args $method $args]
	set omethod [convert_method $method]
	#
	# We want to set -extent 1 instead of what
	# convert_args gave us.
	#
	set exti [lsearch -exact $opts "-extent"]
	incr exti
	set opts [lreplace $opts $exti $exti 1]

	puts "Recd014: $method extent creation/deletion tests"

	# Create the database and environment.
	env_cleanup $testdir

	set testfile recd014.db
	set flags "-create -txn -home $testdir"

	puts "\tRecd014.a: creating environment"
	set env_cmd "berkdb env $flags"

	puts "\tRecd014.b: Create test commit"
	ext_recover_create $testdir $env_cmd $omethod \
	    $opts $testfile commit
	puts "\tRecd014.b: Create test abort"
	ext_recover_create $testdir $env_cmd $omethod \
	    $opts $testfile abort

	puts "\tRecd014.c: Consume test commit"
	ext_recover_delete $testdir $env_cmd $omethod \
	    $opts $testfile consume commit
	puts "\tRecd014.c: Consume test abort"
	ext_recover_delete $testdir $env_cmd $omethod \
	    $opts $testfile consume abort

	puts "\tRecd014.d: Delete test commit"
	ext_recover_delete $testdir $env_cmd $omethod \
	    $opts $testfile delete commit
	puts "\tRecd014.d: Delete test abort"
	ext_recover_delete $testdir $env_cmd $omethod \
	    $opts $testfile delete abort

	set fixed_len $orig_fixed_len
	puts "\tRecd014.e: Verify db_printlog can read logfile"
	set tmpfile $testdir/printlog.out
	set stat [catch {exec $util_path/db_printlog -h $testdir \
	    > $tmpfile} ret]
	error_check_good db_printlog $stat 0
	fileremove $tmpfile
}

proc ext_recover_create { dir env_cmd method opts dbfile txncmd } {
	global log_log_record_types
	global fixed_len
	global alphabet
	source ./include.tcl

	# Keep track of the log types we've seen
	if { $log_log_record_types == 1} {
		logtrack_read $dir
	}

	env_cleanup $dir
	# Open the environment and set the copy/abort locations
	set env [eval $env_cmd]

	set init_file $dir/$dbfile.init
	set noenvflags "-create $method -mode 0644 -pagesize 512 $opts $dbfile"
	set oflags "-env $env $noenvflags"

	set t [$env txn]
	error_check_good txn_begin [is_valid_txn $t $env] TRUE

	set ret [catch {eval {berkdb_open} $oflags} db]

	#
	# The command to execute to create an extent is a put.
	# We are just creating the first one, so our extnum is 0.
	#
	set extnum 0
	set data [chop_data $method [replicate $alphabet 512]]
	puts "\t\tExecuting command"
	set putrecno [$db put -txn $t -append $data]
	error_check_good db_put $putrecno 1

	# Sync the db so any changes to the file that are
	# in mpool get written to the disk file before the
	# diff.
	puts "\t\tSyncing"
	error_check_good db_sync [$db sync] 0

        catch { file copy -force $dir/$dbfile $dir/$dbfile.afterop } res
	copy_extent_file $dir $dbfile afterop

	error_check_good txn_$txncmd:$t [$t $txncmd] 0
	#
	# If we don't abort, then we expect success.
	# If we abort, we expect no file created.
	#
	set dbq [make_ext_filename $dir $dbfile $extnum]
	error_check_good extput:exists1 [file exists $dbq] 1
	set ret [$db get $putrecno]
	if {$txncmd == "abort"} {
		#
		# Operation was aborted.   Verify our entry is not there.
		#
		puts "\t\tCommand executed and aborted."
		error_check_good db_get [llength $ret] 0
	} else {
		#
		# Operation was committed, verify it exists.
		#
		puts "\t\tCommand executed and committed."
		error_check_good db_get [llength $ret] 1
		catch { file copy -force $dir/$dbfile $init_file } res
		copy_extent_file $dir $dbfile init
	}
	error_check_good db_close [$db close] 0
	error_check_good env_close [$env close] 0

	#
	# Run recovery here.  Should be a no-op.  Verify that
	# the file still does/n't exist when we are done.
	#
	berkdb debug_check
	puts -nonewline "\t\tAbout to run recovery (no-op) ... "
	flush stdout

	set stat [catch {exec $util_path/db_recover -h $dir -c} result]
	if { $stat == 1 } {
		error "FAIL: Recovery error: $result."
		return
	}
	puts "complete"
	#
	# Verify it did not change.
	#
	error_check_good extput:exists2 [file exists $dbq] 1
	ext_create_check $dir $txncmd $init_file $dbfile $noenvflags $putrecno

	#
	# Need a new copy to get the right LSN into the file.
	#
	catch { file copy -force $dir/$dbfile $init_file } res
	copy_extent_file $dir $dbfile init

	#
	# Undo.
	# Now move the .afterop file to $dbfile.  Run recovery again.
	#
	file copy -force $dir/$dbfile.afterop $dir/$dbfile
	move_file_extent $dir $dbfile afterop copy

	berkdb debug_check
	puts -nonewline "\t\tAbout to run recovery (afterop) ... "
	flush stdout

	set stat [catch {exec $util_path/db_recover -h $dir -c} result]
	if { $stat == 1 } {
		error "FAIL: Recovery error: $result."
		return
	}
	puts "complete"
	ext_create_check $dir $txncmd $init_file $dbfile $noenvflags $putrecno

	#
	# To redo, remove the dbfiles.  Run recovery again.
	#
	catch { file rename -force $dir/$dbfile $dir/$dbfile.renamed } res
	copy_extent_file $dir $dbfile renamed rename

	berkdb debug_check
	puts -nonewline "\t\tAbout to run recovery (init) ... "
	flush stdout

	set stat [catch {exec $util_path/db_recover -h $dir -c} result]
	#
	# !!!
	# Even though db_recover exits with status 0, it should print out
	# a warning because the file didn't exist.  Db_recover writes this
	# to stderr.  Tcl assumes that ANYTHING written to stderr is an
	# error, so even though we exit with 0 status, we still get an
	# error back from 'catch'.  Look for the warning.
	#
	if { $stat == 1 && [is_substr $result "warning"] == 0 } {
		error "FAIL: Recovery error: $result."
		return
	}
	puts "complete"

	#
	# Verify it was redone.  However, since we removed the files
	# to begin with, recovery with abort will not recreate the
	# extent.  Recovery with commit will.
	#
	if {$txncmd == "abort"} {
		error_check_good extput:exists3 [file exists $dbq] 0
	} else {
		error_check_good extput:exists3 [file exists $dbq] 1
	}
}

proc ext_create_check { dir txncmd init_file dbfile oflags putrecno } {
	if { $txncmd == "commit" } {
		#
		# Operation was committed. Verify it did not change.
		#
		error_check_good \
		    diff(initial,post-recover2):diff($init_file,$dir/$dbfile) \
		    [dbdump_diff $init_file $dir/$dbfile] 0
	} else {
		#
		# Operation aborted.  The file is there, but make
		# sure the item is not.
		#
		set xdb [eval {berkdb_open} $oflags]
		error_check_good db_open [is_valid_db $xdb] TRUE
		set ret [$xdb get $putrecno]
		error_check_good db_get [llength $ret] 0
		error_check_good db_close [$xdb close] 0
	}
}


proc ext_recover_delete { dir env_cmd method opts dbfile op txncmd} {
	global log_log_record_types
	global alphabet
	source ./include.tcl

	# Keep track of the log types we've seen
	if { $log_log_record_types == 1} {
		logtrack_read $dir
	}

	env_cleanup $dir
	# Open the environment and set the copy/abort locations
	set env [eval $env_cmd]
	
	set oflags "-create $method -mode 0644 -pagesize 512 \
	   -env $env $opts $dbfile"
	
	#
	# Open our db, add some data, close and copy as our
	# init file.
	#
	set db [eval {berkdb_open} $oflags]
	error_check_good db_open [is_valid_db $db] TRUE
	
	set extnum 0
	set data [chop_data $method [replicate $alphabet 512]]

	set txn [$env txn]
	error_check_good txn_begin [is_valid_txn $txn $env] TRUE
	set putrecno [$db put -append $data]
	error_check_good db_put $putrecno 1
	error_check_good commit [$txn commit] 0
	error_check_good db_close [$db close] 0
	
	puts "\t\tExecuting command"
	
	set init_file $dir/$dbfile.init
	catch { file copy -force $dir/$dbfile $init_file } res
	copy_extent_file $dir $dbfile init
	
	#
	# If we don't abort, then we expect success.
	# If we abort, we expect no file removed until recovery is run.
	#
	set db [eval {berkdb_open} $oflags]
	error_check_good db_open [is_valid_db $db] TRUE
	
	set t [$env txn]
	error_check_good txn_begin [is_valid_txn $t $env] TRUE

	if { [string compare $op "delete"] == 0 } {
		set dbcmd "$db del -txn $t $putrecno"
	} else {
		set dbcmd "$db get -txn $t -consume"
	}
	set ret [eval $dbcmd]
	error_check_good db_sync [$db sync] 0

        catch { file copy -force $dir/$dbfile $dir/$dbfile.afterop } res
	copy_extent_file $dir $dbfile afterop

	error_check_good txn_$txncmd:$t [$t $txncmd] 0
	set dbq [make_ext_filename $dir $dbfile $extnum]
	if {$txncmd == "abort"} {
		#
		# Operation was aborted, verify ext did not change.
		#
		puts "\t\tCommand executed and aborted."

		#
		# Check that the file exists.  Final state.
		# Since we aborted the txn, we should be able
		# to get to our original entry.
		#
		error_check_good post$op.1 [file exists $dbq] 1

		set xdb [eval {berkdb_open} $oflags]
		error_check_good db_open [is_valid_db $xdb] TRUE
		set kd [$xdb get $putrecno]
		set key [lindex [lindex $kd 0] 0]
		error_check_good dbget_key $key $putrecno
		set retdata [lindex [lindex $kd 0] 1]
		error_check_good dbget_data $data $retdata
		error_check_good db_close [$xdb close] 0

		error_check_good \
		    diff(init,post$op.2):diff($init_file,$dir/$dbfile)\
		    [dbdump_diff $init_file $dir/$dbfile] 0
	} else {
		#
		# Operation was committed, verify it does
		# not exist.
		#
		puts "\t\tCommand executed and committed."
		#
		# Check file existence.  Consume operations remove
		# the extent when we move off, which we should have
		# done.  Delete operations won't remove the extent
		# until we run recovery.
		#
		if { [string compare $op "delete"] == 0 } {
			error_check_good ${op}_exists [file exists $dbq] 1
		} else {
			error_check_good ${op}_exists [file exists $dbq] 0
		}
	}
	error_check_good db_close [$db close] 0
	error_check_good env_close [$env close] 0

	#
	# Run recovery here on what we ended up with.  Should be a no-op.
	#
	berkdb debug_check
	puts -nonewline "\t\tAbout to run recovery (no-op) ... "
	flush stdout

	set stat [catch {exec $util_path/db_recover -h $dir -c} result]
	if { $stat == 1 } {
		error "FAIL: Recovery error: $result."
		return
	}
	puts "complete"
	if { $txncmd == "abort"} {
		#
		# Operation was aborted, verify it did not change.
		#
		error_check_good \
		    diff(initial,post-recover1):diff($init_file,$dir/$dbfile) \
		    [dbdump_diff $init_file $dir/$dbfile] 0
	} else {
		#
		# Operation was committed, verify it does
		# not exist.  Both operations should result
		# in no file existing now that we've run recovery.
		#
		error_check_good after_recover1 [file exists $dbq] 0
	}

	#
	# Run recovery here. Re-do the operation.
	# Verify that the file doesn't exist 
	# (if we committed)  or change (if we aborted)
	# when we are done.
	#
	catch { file copy -force $dir/$dbfile $init_file } res
	copy_extent_file $dir $dbfile init
	berkdb debug_check
	puts -nonewline "\t\tAbout to run recovery (init) ... "
	flush stdout

	set stat [catch {exec $util_path/db_recover -h $dir -c} result]
	if { $stat == 1 } {
		error "FAIL: Recovery error: $result."
		return
	}
	puts "complete"
	if { $txncmd == "abort"} {
		#
		# Operation was aborted, verify it did not change.
		#
		error_check_good \
		    diff(initial,post-recover1):diff($init_file,$dir/$dbfile) \
		    [dbdump_diff $init_file $dir/$dbfile] 0
	} else {
		#
		# Operation was committed, verify it does
		# not exist.  Both operations should result
		# in no file existing now that we've run recovery.
		#
		error_check_good after_recover1 [file exists $dbq] 0
	}

	#
	# Now move the .afterop file to $dbfile.  Run recovery again.
	#
	set filecopy [glob $dir/*.afterop]
	set afterop [lindex $filecopy 0]
	file rename -force $afterop $dir/$dbfile
	set afterop [string range $afterop \
	    [expr [string last "/" $afterop] + 1]  \
	    [string last "." $afterop]]
	move_file_extent $dir $dbfile afterop rename

	berkdb debug_check
	puts -nonewline "\t\tAbout to run recovery (afterop) ... "
	flush stdout

	set stat [catch {exec $util_path/db_recover -h $dir -c} result]
	if { $stat == 1 } {
		error "FAIL: Recovery error: $result."
		return
	}
	puts "complete"

	if { $txncmd == "abort"} {
		#
		# Operation was aborted, verify it did not change.
		#
		error_check_good \
		    diff(initial,post-recover2):diff($init_file,$dir/$dbfile) \
		    [dbdump_diff $init_file $dir/$dbfile] 0
	} else {
		#
		# Operation was committed, verify it still does
		# not exist.
		#
		error_check_good after_recover2 [file exists $dbq] 0
	}
}
