# See the file LICENSE for redistribution information.
#
# Copyright (c) 2003,2007 Oracle.  All rights reserved.
#
# $Id: test103.tcl,v 12.5 2007/05/17 15:15:56 bostic Exp $
#
# TEST	test103
# TEST	Test bulk get when record numbers wrap around.
# TEST
# TEST	Load database with items starting before and ending after
# TEST	the record number wrap around point.  Run bulk gets (-multi_key)
# TEST	with various buffer sizes and verify the contents returned match
# TEST	the results from a regular cursor get.
# TEST
# TEST	Then delete items to create a sparse database and make sure it
# TEST	still works.  Test both -multi and -multi_key since they behave
# TEST	differently.
proc test103 { method {nentries 100} {start 4294967250} {tnum "103"} args} {
	source ./include.tcl

	set args [convert_args $method $args]
	set omethod [convert_method $method]
	puts "Test$tnum: $method ($args) Test of bulk get with wraparound."

	if { [is_queueext $method] == 0 } {
		puts "\tSkipping Test$tnum for method $method."
		return
	}

	# Create the database and open the dictionary
	set txnenv 0
	set eindex [lsearch -exact $args "-env"]
	#
	# If we are using an env, then testfile should just be the db name.
	# Otherwise it is the test directory and the name.
	if { $eindex == -1 } {
		set testfile $testdir/test$tnum.db
		set env NULL
	} else {
		set testfile test$tnum.db
		incr eindex
		set env [lindex $args $eindex]
		set txnenv [is_txnenv $env]
		if { $txnenv == 1 } {
			append args " -auto_commit "
			#
			# If we are using txns and running with the
			# default, set the default down a bit.
			#
			if { $nentries == 10000 } {
				set nentries 100
			}
		}
		set testdir [get_home $env]
	}

	cleanup $testdir $env

	set db [eval {berkdb_open_noerr \
	     -create -mode 0644} $args $omethod $testfile]
	error_check_good dbopen [is_valid_db $db] TRUE

	# Find the pagesize so we can use it to size the buffer.
	set stat [$db stat]
	set pagesize [get_pagesize $stat]

	set did [open $dict]

	puts "\tTest$tnum.a: put/get loop"
	set txn ""

	# Here is the loop where we put each key/data pair
	set count 0
	set k [expr $start + 1]
	while { [gets $did str] != -1 && $count < $nentries } {
		#
		# We cannot use 'incr' because it gets unhappy since
		# expr above is using 64-bits.
		set k [expr $k + 1]
		#
		# Detect if we're more than 32 bits now.  If so, wrap
		# our key back to 1.
		#
		if { [expr $k > 0xffffffff] } {
			set k 1
		}
		if { $txnenv == 1 } {
			set t [$env txn]
			error_check_good txn [is_valid_txn $t $env] TRUE
			set txn "-txn $t"
		}
		set ret [eval {$db put} $txn {$k [chop_data $method $str]}]
		error_check_good db_put $ret 0
		if { $txnenv == 1 } {
			error_check_good txn [$t commit] 0
		}
		incr count
	}
	close $did

	# Run tests in verbose mode for debugging.
	set verbose 0

	puts "\tTest$tnum.b: Bulk get with large buffer (retrieves all data)."
	# Buffer is large enough that everything fits in a single get.
	check_multi_recno $db [expr $pagesize * $nentries] multi_key $verbose

	puts "\tTest$tnum.c: Bulk get with buffer = (2 x pagesize)."
	# Buffer gets several items at a get, but not all.
	check_multi_recno $db [expr $pagesize * 2] multi_key $verbose

	# Skip tests if buffer would be smaller than allowed.
	if { $pagesize >= 1024  } {
		puts "\tTest$tnum.d: Bulk get with buffer = pagesize."
		check_multi_recno $db $pagesize multi_key $verbose
	}

	if { $pagesize >= 2048 } {
		puts "\tTest$tnum.e: Bulk get with buffer < pagesize\
		    (returns EINVAL)."
		catch {
			check_multi_recno $db [expr $pagesize / 2] \
			    multi_key $verbose
		} res
		error_check_good \
		    bufsize_less_than_pagesize [is_substr $res "invalid"] 1
	}

	# For a sparsely populated database, test with both -multi_key and
	# -multi.  In any sort of record numbered database, -multi does not
	# return keys, so it returns all items.  -multi_key returns both keys
	# and data so it skips deleted items.
	puts "\tTest$tnum.f: Delete every 10th item to create sparse database."
	if { $txnenv == 1 } {
		set t [$env txn]
		error_check_good txn [is_valid_txn $t $env] TRUE
		set txn "-txn $t"
	}
	set curs [ eval {$db cursor} $txn]
	error_check_good cursor [is_valid_cursor $curs $db] TRUE

	set count 0
	for { set kd [$curs get -first] } { $count < $nentries } \
	    { set kd [$curs get -next] } {
		if { [expr $count % 10 == 0] } {
			error_check_good cdelete [$curs del] 0
		}
		incr count
	}
	error_check_good curs_close [$curs close] 0
	if { $txnenv == 1 } {
		error_check_good txn [$t commit] 0
	}

	puts "\tTest$tnum.g: Sparse database, large buffer, multi_key."
	check_multi_recno $db [expr $pagesize * $nentries] multi_key $verbose
	puts "\tTest$tnum.h: Sparse database, large buffer, multi."
	check_multi_recno $db [expr $pagesize * $nentries] multi $verbose

	puts "\tTest$tnum.i: \
	    Sparse database, buffer = (2 x pagesize), multi_key."
	check_multi_recno $db [expr $pagesize * 2] multi_key $verbose
	puts "\tTest$tnum.j: Sparse database, buffer = (2 x pagesize), multi."
	check_multi_recno $db [expr $pagesize * 2] multi $verbose

	if { $pagesize >= 1024  } {
		puts "\tTest$tnum.k: \
		    Sparse database, buffer = pagesize, multi_key."
		check_multi_recno $db $pagesize multi_key $verbose
		puts "\tTest$tnum.k: Sparse database, buffer = pagesize, multi."
		check_multi_recno $db $pagesize multi $verbose
	}

	error_check_good db_close [$db close] 0
}

# The proc check_multi_recno is a modification of the utility routine
# check_multi_key specifically for recno methods.  We use this instead
# check_multi, even with the -multi flag, because the check_multi utility
# assumes that dups are being used which can't happen with record-based
# methods.
proc check_multi_recno { db size flag {verbose 0}} {
	source ./include.tcl
	set c [eval { $db cursor} ]
	set m [eval { $db cursor} ]

	set j 1

	# Walk the database with -multi_key or -multi bulk get.
	for {set d [$m get -first -$flag $size] } { [llength $d] != 0 } {
	    set d [$m get -next -$flag $size] } {
		if {$verbose == 1 } {
			puts "FETCH $j"
			incr j
		}
		# For each bulk get return, compare the results to what we
		# get by walking the db with an ordinary cursor get.
		for {set i 0} { $i < [llength $d] } { incr i } {
			set kd [lindex $d $i]
			set k [lindex $kd 0]
			set data [lindex $kd 1]
			set len [string length $data]

			if {$verbose == 1 } {
				puts ">> $k << >> $len << "
			}
			# If we hit a deleted item in -multi, skip over it.
			if { $flag == "multi" && $len == 0 } {
				continue
			}

			set check [$c get -next]
			set cd [lindex $check 0]
			set ck [lindex $cd 0]
			set cdata [lindex $cd 1]

			error_check_good key $k $ck
			error_check_good data_len $len [string length $cdata]
			error_check_good data $data $cdata
		}
	}
}
