mirror of
https://github.com/berkeleydb/libdb.git
synced 2024-11-16 17:16:25 +00:00
479 lines
15 KiB
Tcl
479 lines
15 KiB
Tcl
# See the file LICENSE for redistribution information.
|
|
#
|
|
# Copyright (c) 2011 Oracle and/or its affiliates. All rights reserved.
|
|
#
|
|
# $Id$
|
|
#
|
|
# TEST test131
|
|
# TEST Test foreign database operations.
|
|
# TEST Create a foreign db, and put some records into it.
|
|
# TEST Then associate the foreign db with a secondary db, and
|
|
# TEST put records into the primary db.
|
|
# TEST Do operations in the foreign db and check results.
|
|
# TEST Finally, verify the foreign relation between the foreign db
|
|
# TEST and secondary db.
|
|
# TEST Here, we test three different foreign delete constraints:
|
|
# TEST - DB_FOREIGN_ABORT
|
|
# TEST - DB_FOREIGN_CASCADE
|
|
# TEST - DB_FOREIGN_NULLIFY
|
|
|
|
proc test131 {method {nentries 1000} {tnum "131"} {ndups 5} {subdb 0}
|
|
{inmem 0} args } {
|
|
source ./include.tcl
|
|
|
|
# For rrecno, when keys are deleted, the ones after will move forward,
|
|
# and the keys change. It is not good to use rrecno for the primary
|
|
# database.
|
|
if {[is_rrecno $method]} {
|
|
puts "Skipping test$tnum for $method test."
|
|
return
|
|
}
|
|
|
|
set sub_msg ""
|
|
# Check if we use sub databases.
|
|
if { $subdb } {
|
|
if {[is_queue $method]} {
|
|
puts "Skipping test$tnum with sub database\
|
|
for $method."
|
|
return
|
|
}
|
|
if {[is_partitioned $args]} {
|
|
puts "Skipping test$tnum with sub database\
|
|
for partitioned $method test."
|
|
return
|
|
}
|
|
if {[is_heap $method]} {
|
|
puts "Skipping test$tnum with sub database\
|
|
for $method."
|
|
return
|
|
}
|
|
# Check if the sub databases should be in-memory.
|
|
if {$inmem} {
|
|
set sub_msg "using in-memory sub databases"
|
|
} else {
|
|
set sub_msg "using sub databases"
|
|
}
|
|
}
|
|
|
|
# If we are using an env, then basename should just be the name prefix.
|
|
# Otherwise it is the test directory and the name prefix.
|
|
set eindex [lsearch -exact $args "-env"]
|
|
set txnenv 0
|
|
set txn ""
|
|
if { $eindex == -1 } {
|
|
set basename $testdir/test$tnum
|
|
set env NULL
|
|
if {$subdb} {
|
|
puts "Skipping test$tnum $sub_msg for non-env test."
|
|
return
|
|
}
|
|
} else {
|
|
set basename test$tnum
|
|
set nentries 200
|
|
incr eindex
|
|
set env [lindex $args $eindex]
|
|
set txnenv [is_txnenv $env]
|
|
if { $txnenv == 1 } {
|
|
append args " -auto_commit "
|
|
}
|
|
set testdir [get_home $env]
|
|
}
|
|
|
|
cleanup $testdir $env
|
|
|
|
|
|
# To simplify the case, just set the type of foreign database as
|
|
# btree or hash. And for the secondary database, the type is dupsort
|
|
# hash/btree. For foreign and secondary databases, we just pass
|
|
# the original args, since the converted args does not work for them.
|
|
# For example, if the primary database is rbtree, convert_args will
|
|
# generate "-recnum" which is invalid to the foreign and secondary
|
|
# databases.
|
|
set origargs $args
|
|
set secargs $args
|
|
set args [convert_args $method $args]
|
|
set omethod [convert_method $method]
|
|
|
|
puts "Test$tnum: $method ($args) Foreign operations $sub_msg."
|
|
|
|
# The type-pairs for the foreign and secondary databases.
|
|
set typepairs {
|
|
{"-btree" "-btree"}
|
|
{"-btree" "-hash"}
|
|
{"-hash" "-btree"}
|
|
{"-hash" "-hash"}
|
|
}
|
|
|
|
# The sub procs which test specific foreign operations.
|
|
set subtests {
|
|
{"test131_sub1" "-abort" "0"}
|
|
{"test131_sub2" "-cascade" "0"}
|
|
{"test131_sub3" "-nullify test131_nullify_cb1" "1"}
|
|
{"test131_sub4" "-nullify test131_nullify_cb2" "0"}
|
|
}
|
|
|
|
set i 0
|
|
|
|
foreach subtest $subtests {
|
|
foreach typepair $typepairs {
|
|
# Initialize the names
|
|
set pri_subname ""
|
|
set sec_subname ""
|
|
set foreign_subname ""
|
|
set pri_file $basename.db
|
|
set sec_file $basename.db
|
|
set foreign_file $basename.db
|
|
if {$inmem} {
|
|
set pri_file ""
|
|
set sec_file ""
|
|
set foreign_file ""
|
|
}
|
|
|
|
set foreign_proc [lindex $subtest 0]
|
|
set foreign_arg [lindex $subtest 1]
|
|
set foreign_vrfy [lindex $subtest 2]
|
|
set foreign_dbtype [lindex $typepair 0]
|
|
set sec_dbtype [lindex $typepair 1]
|
|
if {$subdb} {
|
|
set pri_subname "primary_db$i"
|
|
set sec_subname "secondary_db$i"
|
|
set foreign_subname "foreign_db$i"
|
|
} else {
|
|
set pri_file "${basename}_primary$i.db"
|
|
set sec_file "${basename}_secondary$i.db"
|
|
set foreign_file "${basename}_foreign$i.db"
|
|
}
|
|
|
|
puts "\tTest$tnum.$i subtest={$subtest}, \
|
|
typepair={$typepair}"
|
|
|
|
# Skip partition range test and compression test
|
|
# when there are non-btree databases.
|
|
if {![is_btree $foreign_dbtype] || \
|
|
![is_btree $sec_dbtype]} {
|
|
set skip 0
|
|
if {[is_partitioned $origargs] && \
|
|
![is_partition_callback $origargs]} {
|
|
set skip 1
|
|
}
|
|
if {[is_compressed $origargs]} {
|
|
set skip 1
|
|
}
|
|
if {$skip} {
|
|
puts "\tSkipping for {$origargs}"
|
|
continue
|
|
}
|
|
}
|
|
|
|
set txn ""
|
|
if {$txnenv == 1} {
|
|
set t [$env txn]
|
|
error_check_good txn \
|
|
[is_valid_txn $t $env] TRUE
|
|
set txn "-txn $t"
|
|
}
|
|
# Open the foreign database.
|
|
puts "\tTest$tnum.$i.a Open and truncate the databases."
|
|
set foreign_db [eval berkdb_open_noerr -create \
|
|
-mode 0644 $foreign_dbtype $origargs \
|
|
{$foreign_file} $foreign_subname]
|
|
error_check_good foreign_db_open \
|
|
[is_valid_db $foreign_db] TRUE
|
|
|
|
# Open the primary database.
|
|
set pri_db [eval berkdb_open_noerr -create -mode 0644 \
|
|
$omethod $args {$pri_file} $pri_subname]
|
|
error_check_good pri_db_open \
|
|
[is_valid_db $pri_db] TRUE
|
|
|
|
# Open the secondary database.
|
|
set sec_db [eval berkdb_open_noerr -create -mode 0644 \
|
|
$sec_dbtype $secargs -dup -dupsort \
|
|
{$sec_file} $sec_subname]
|
|
error_check_good sec_db_open \
|
|
[is_valid_db $sec_db] TRUE
|
|
|
|
# Truncate the databases.
|
|
# For some tests(e.g. run_secenv), we will run this
|
|
# unit twice. The "cleanup $testdir" could clean the
|
|
# on-disk database files, but it can not clean the
|
|
# in-memory ones, so the records are still in the
|
|
# in-memory databases, which will affect our test.
|
|
# So, we need to truncate these databases first.
|
|
error_check_good foreign_db_trunc \
|
|
[expr [eval $foreign_db truncate $txn] >= 0] 1
|
|
error_check_good pri_db_trunc \
|
|
[expr [eval $pri_db truncate $txn] >= 0] 1
|
|
error_check_good sec_db_trunc \
|
|
[expr [eval $sec_db truncate $txn] >= 0] 1
|
|
|
|
# Establish the relations between the databases.
|
|
set ret [eval $pri_db \
|
|
associate -create $txn test131_sec_cb1 $sec_db]
|
|
error_check_good db_associate $ret 0
|
|
|
|
set ret [eval $foreign_db \
|
|
associate_foreign $foreign_arg $sec_db]
|
|
error_check_good db_associate_foreign $ret 0
|
|
|
|
puts "\tTest$tnum.$i.b Populate the foreign database."
|
|
test131_populate_foreigndb $txn $foreign_db $nentries
|
|
|
|
# Put records into the primary database.
|
|
puts "\tTest$tnum.$i.c Populate primary database."
|
|
test131_populate_pridb \
|
|
$txn $pri_db $method $nentries $ndups
|
|
|
|
# Check records in the secondary database.
|
|
puts "\tTest$tnum.$i.d\
|
|
Check records in secondary database."
|
|
test131_check_secdb $txn $sec_db $nentries $ndups
|
|
|
|
# Update the foreign database.
|
|
puts "\tTest$tnum.$i.e Update the foreign database."
|
|
test131_update_foreigndb $txn $foreign_db $nentries
|
|
|
|
# Delete the keys only in the foreign database.
|
|
puts "\tTest$tnum.$i.f Delete foreign-only keys."
|
|
test131_delitems_foreigndb $txn $foreign_db $nentries
|
|
|
|
# Test different foreign delete constraints.
|
|
puts "\tTest$tnum.$i.g\
|
|
Test specific foreign delete constraints."
|
|
$foreign_proc $txn $foreign_db $pri_db $sec_db \
|
|
$nentries $ndups "Test$tnum.$i.g"
|
|
|
|
puts "\tTest$tnum.$i.h Verifying foreign key \
|
|
relationships ..."
|
|
error_check_good verify_foreign [verify_foreign \
|
|
$txn $foreign_db $sec_db 0] $foreign_vrfy
|
|
|
|
if {$txnenv == 1} {
|
|
error_check_good txn_commit [$t commit] 0
|
|
}
|
|
|
|
error_check_good foreign_db_close [$foreign_db close] 0
|
|
error_check_good pri_db_close [$pri_db close] 0
|
|
error_check_good sec_db_close [$sec_db close] 0
|
|
|
|
incr i
|
|
}
|
|
}
|
|
}
|
|
|
|
# Test for DB_FOREIGN_ABORT
|
|
# Delete the keys which exist in the secondary database.
|
|
# The delete should fail because DB_FOREIGN_ABORT has been set.
|
|
proc test131_sub1 {txn foreign_db pri_db sec_db nentries ndups msghdr} {
|
|
puts "\t\t$msghdr.1 Test DB_FOREIGN_ABORT"
|
|
set nentries2 [expr $nentries / 2]
|
|
for {set i 0} {$i < $nentries2} {incr i} {
|
|
set ret [catch {eval $foreign_db del $txn $i} res]
|
|
error_check_bad foreign_del $ret 0
|
|
error_check_good "check DB_FOREIGN_CONFLICT" \
|
|
[is_substr $res DB_FOREIGN_CONFLICT] 1
|
|
error_check_good check_ndups \
|
|
[llength [eval $sec_db get $txn $i]] $ndups
|
|
error_check_good check_foreign \
|
|
[llength [eval $foreign_db get $txn $i]] 1
|
|
}
|
|
}
|
|
|
|
# Test the DB_FOREIGN_CASCADE.
|
|
# Delete the keys which exist in the secondary database.
|
|
# The delete should succeed, and all the related records in
|
|
# the secondary database should be deleted as well.
|
|
proc test131_sub2 {txn foreign_db pri_db sec_db nentries ndups msghdr} {
|
|
puts "\t\t$msghdr.1 Test DB_FOREIGN_CASCADE"
|
|
set nentries2 [expr $nentries / 2]
|
|
for {set i 0} {$i < $nentries2} {incr i} {
|
|
set ret [catch {eval $foreign_db del $txn $i} res]
|
|
error_check_good foreign_del $ret 0
|
|
error_check_good check_ndups \
|
|
[llength [eval $sec_db get $txn $i]] 0
|
|
error_check_good check_foreign \
|
|
[llength [eval $foreign_db get $txn $i]] 0
|
|
}
|
|
}
|
|
|
|
# Test the DB_FOREIGN_NULLIFY with a bad nullify function(
|
|
# the nullify function does not change the data).
|
|
# Delete the keys which exist in the secondary database.
|
|
# The delete should fail, but the records in the foreign
|
|
# database should be removed anyway, while the related records
|
|
# in the primary/secondary are still there.
|
|
proc test131_sub3 {txn foreign_db pri_db sec_db nentries ndups msghdr} {
|
|
puts "\t\t$msghdr.1 \
|
|
Test DB_FOREIGN_NULLIFY with a bad nullify function."
|
|
set nentries2 [expr $nentries / 2]
|
|
for {set i 0} {$i < $nentries2} {incr i} {
|
|
set ret [catch {eval $foreign_db del $txn $i} res]
|
|
error_check_good foreign_del $ret 0
|
|
error_check_good check_ndups \
|
|
[llength [eval $sec_db get $txn $i]] $ndups
|
|
error_check_good check_foreign \
|
|
[llength [eval $foreign_db get $txn $i]] 0
|
|
}
|
|
}
|
|
|
|
# Test the DB_FOREIGN_NULLIFY with a good nullify function.
|
|
# Delete the keys which exist in the secondary database.
|
|
# The delete should succeed, and the records in the foreign
|
|
# database should be removed, while the related records
|
|
# in the primary/secondary are changed.
|
|
proc test131_sub4 {txn foreign_db pri_db sec_db nentries ndups msghdr} {
|
|
puts "\t\t$msghdr.1 \
|
|
Test DB_FOREIGN_NULLIFY with a good nullify function."
|
|
set nentries2 [expr $nentries / 2 - 1]
|
|
for {set i 0} {$i < $nentries2} {incr i 2} {
|
|
set ret [catch {eval $foreign_db del $txn $i} res]
|
|
error_check_good foreign_del $ret 0
|
|
error_check_good check_ndups \
|
|
[llength [eval $sec_db get $txn $i]] 0
|
|
error_check_good check_foreign \
|
|
[llength [eval $foreign_db get $txn $i]] 0
|
|
set newkey [expr $i + 1]
|
|
error_check_good check_ndups \
|
|
[llength [eval $sec_db get $txn $newkey]] [expr $ndups * 2]
|
|
error_check_good check_foreign \
|
|
[llength [eval $foreign_db get $txn $newkey]] 1
|
|
}
|
|
}
|
|
|
|
# The callback function for secondary database.
|
|
proc test131_sec_cb1 {pkey pdata} {
|
|
set indx [string first "-" $pdata]
|
|
error_check_bad good_indx [expr $indx > 0] 0
|
|
return [string range $pdata 0 [expr $indx - 1]]
|
|
}
|
|
|
|
# The 1st callback function for foreign.
|
|
proc test131_nullify_cb1 {pkey pdata fkey} {
|
|
return $pdata
|
|
}
|
|
|
|
# The 2nd callback function for foreign.
|
|
proc test131_nullify_cb2 {pkey pdata fkey} {
|
|
# We should make sure the size does not grow, since
|
|
# that will cause queue test to fail.
|
|
set indx [string first "-" $pdata]
|
|
error_check_bad good_indx [expr $indx > 0] 0
|
|
set num [string range $pdata 0 [expr $indx - 1]]
|
|
set str [string range $pdata [expr $indx + 1] end]
|
|
return "[expr $num + 1]-$str"
|
|
}
|
|
|
|
# Put records into the foreign database.
|
|
proc test131_populate_foreigndb {txn foreign_db nentries} {
|
|
global alphabet
|
|
for {set i 0} {$i < $nentries} {incr i} {
|
|
set ret [eval $foreign_db put $txn $i $i$alphabet]
|
|
error_check_good foreign_put $ret 0
|
|
}
|
|
}
|
|
|
|
# Update the records in the foreign database.
|
|
proc test131_update_foreigndb {txn foreign_db nentries} {
|
|
global alphabet
|
|
for {set i 0} {$i < $nentries} {incr i} {
|
|
set ret [eval $foreign_db get $txn $i]
|
|
error_check_good check_pair [llength $ret] 1
|
|
set key [lindex [lindex $ret 0] 0]
|
|
set data [lindex [lindex $ret 0] 1]
|
|
error_check_good check_key $key $i
|
|
set ret [eval $foreign_db put $txn $key $data-NEW]
|
|
error_check_good foreign_put $ret 0
|
|
}
|
|
}
|
|
|
|
# Delete items which only exist in the foreign database.
|
|
proc test131_delitems_foreigndb {txn foreign_db nentries} {
|
|
global alphabet
|
|
set nentries2 [expr $nentries / 2]
|
|
for {set i $nentries2} {$i < $nentries} {incr i 2} {
|
|
set ret [eval $foreign_db del $txn $i]
|
|
error_check_good foreign_del $ret 0
|
|
set ret [eval $foreign_db get $txn $i]
|
|
error_check_good check_empty [llength $ret] 0
|
|
}
|
|
}
|
|
|
|
# Put records into the primary database.
|
|
# Here we make every key in the secondary database
|
|
# has $ndups duplicate records.
|
|
proc test131_populate_pridb {txn pri_db pri_method nentries ndups} {
|
|
source ./include.tcl
|
|
global alphabet
|
|
|
|
set lastindx [expr [string length $alphabet] - 1]
|
|
# We just cover the first half of keys in the foreign database.
|
|
set nentries2 [expr $nentries / 2]
|
|
set number 1
|
|
for {set i 0} {$i < $nentries2} {incr i} {
|
|
for {set j 0} {$j < $ndups} {incr j} {
|
|
set beg [berkdb random_int 0 $lastindx]
|
|
set end [berkdb random_int 0 $lastindx]
|
|
if {$beg <= $end} {
|
|
set str [string range $alphabet $beg $end]
|
|
} else {
|
|
set str [string range $alphabet $end $beg]
|
|
}
|
|
if {[is_record_based $pri_method] == 1} {
|
|
set key $number
|
|
} else {
|
|
set key $number$str
|
|
}
|
|
set ret [eval $pri_db put $txn $key \
|
|
[make_fixed_length $pri_method "$i-$str"]]
|
|
error_check_good pri_db_put $ret 0
|
|
error_check_good pri_db_get \
|
|
[llength [eval $pri_db get $txn $key]] 1
|
|
incr number
|
|
}
|
|
}
|
|
|
|
set nentries2 [expr $nentries + $nentries2]
|
|
for {set i $nentries} {$i < $nentries2} {incr i} {
|
|
for {set j 0} {$j < $ndups} {incr j} {
|
|
set beg [berkdb random_int 0 $lastindx]
|
|
set end [berkdb random_int 0 $lastindx]
|
|
if {$beg <= $end} {
|
|
set str [string range $alphabet $beg $end]
|
|
} else {
|
|
set str [string range $alphabet $end $beg]
|
|
}
|
|
if {[is_record_based $pri_method] == 1} {
|
|
set key $number
|
|
} else {
|
|
set key $number$str
|
|
}
|
|
set ret [catch {eval $pri_db put $txn $key \
|
|
[make_fixed_length $pri_method "$i-$str"]} res]
|
|
error_check_bad pri_db_put $ret 0
|
|
error_check_good "check DB_FOREIGN_CONFLICT" \
|
|
[is_substr $res DB_FOREIGN_CONFLICT] 1
|
|
error_check_good pri_db_get \
|
|
[llength [eval $pri_db get $txn $key]] 0
|
|
incr number
|
|
}
|
|
}
|
|
}
|
|
|
|
# Check the records in the secondary database.
|
|
proc test131_check_secdb {txn sec_db nentries ndups} {
|
|
set nentries2 [expr $nentries / 2]
|
|
for {set i 0} {$i < $nentries2} {incr i} {
|
|
set ret [eval $sec_db get $txn $i]
|
|
error_check_good check_rec_num [llength $ret] $ndups
|
|
}
|
|
for {set i $nentries2} {$i < $nentries} {incr i} {
|
|
set ret [eval $sec_db get $txn $i]
|
|
error_check_good check_rec_num [llength $ret] 0
|
|
}
|
|
|
|
set nentries2 [expr $nentries + $nentries2]
|
|
for {set i $nentries} {$i < $nentries2} {incr i} {
|
|
set ret [eval $sec_db get $txn $i]
|
|
error_check_good check_rec_num [llength $ret] 0
|
|
}
|
|
}
|