1*eda0001dSdan# 2021 September 13 2*eda0001dSdan# 3*eda0001dSdan# The author disclaims copyright to this source code. In place of 4*eda0001dSdan# a legal notice, here is a blessing: 5*eda0001dSdan# 6*eda0001dSdan# May you do good and not evil. 7*eda0001dSdan# May you find forgiveness for yourself and forgive others. 8*eda0001dSdan# May you share freely, never taking more than you give. 9*eda0001dSdan# 10*eda0001dSdan#*********************************************************************** 11*eda0001dSdan# 12*eda0001dSdan# The focus of this file is testing the r-tree extension. 13*eda0001dSdan# 14*eda0001dSdan 15*eda0001dSdanif {![info exists testdir]} { 16*eda0001dSdan set testdir [file join [file dirname [info script]] .. .. test] 17*eda0001dSdan} 18*eda0001dSdansource [file join [file dirname [info script]] rtree_util.tcl] 19*eda0001dSdansource $testdir/tester.tcl 20*eda0001dSdanset testprefix rtreedoc3 21*eda0001dSdan 22*eda0001dSdanifcapable !rtree { 23*eda0001dSdan finish_test 24*eda0001dSdan return 25*eda0001dSdan} 26*eda0001dSdan 27*eda0001dSdan 28*eda0001dSdan# This command assumes that the argument is a node blob for a 2 dimensional 29*eda0001dSdan# i32 r-tree table. It decodes and returns a list of cells from the node 30*eda0001dSdan# as a list. Each cell is itself a list of the following form: 31*eda0001dSdan# 32*eda0001dSdan# {$rowid $minX $maxX $minY $maxY} 33*eda0001dSdan# 34*eda0001dSdan# For internal (non-leaf) nodes, the rowid is replaced by the child node 35*eda0001dSdan# number. 36*eda0001dSdan# 37*eda0001dSdanproc rnode_cells {aData} { 38*eda0001dSdan set nDim 2 39*eda0001dSdan 40*eda0001dSdan set nData [string length $aData] 41*eda0001dSdan set nBytePerCell [expr (8 + 2*$nDim*4)] 42*eda0001dSdan binary scan [string range $aData 2 3] S nCell 43*eda0001dSdan 44*eda0001dSdan set res [list] 45*eda0001dSdan for {set i 0} {$i < $nCell} {incr i} { 46*eda0001dSdan set iOff [expr $i*$nBytePerCell+4] 47*eda0001dSdan set cell [string range $aData $iOff [expr $iOff+$nBytePerCell-1]] 48*eda0001dSdan binary scan $cell WIIII rowid x1 x2 y1 y2 49*eda0001dSdan lappend res [list $rowid $x1 $x2 $y1 $y2] 50*eda0001dSdan } 51*eda0001dSdan 52*eda0001dSdan return $res 53*eda0001dSdan} 54*eda0001dSdan 55*eda0001dSdan# Interpret the first two bytes of the blob passed as the only parameter 56*eda0001dSdan# as a 16-bit big-endian integer and return the value. If this blob is 57*eda0001dSdan# the root node of an r-tree, this value is the height of the tree. 58*eda0001dSdan# 59*eda0001dSdanproc rnode_height {aData} { 60*eda0001dSdan binary scan [string range $aData 0 1] S nHeight 61*eda0001dSdan return $nHeight 62*eda0001dSdan} 63*eda0001dSdan 64*eda0001dSdan# Return a blob containing node iNode of r-tree "rt". 65*eda0001dSdan# 66*eda0001dSdanproc rt_node_get {iNode} { 67*eda0001dSdan db one { SELECT data FROM rt_node WHERE nodeno=$iNode } 68*eda0001dSdan} 69*eda0001dSdan 70*eda0001dSdan 71*eda0001dSdan#-------------------------------------------------------------- 72*eda0001dSdan# API: 73*eda0001dSdan# 74*eda0001dSdan# pq_init 75*eda0001dSdan# Initialize a new test. 76*eda0001dSdan# 77*eda0001dSdan# pq_test_callback 78*eda0001dSdan# Invoked each time the xQueryCallback function is called. This Tcl 79*eda0001dSdan# command checks that the arguments that SQLite passed to xQueryCallback 80*eda0001dSdan# are as expected. 81*eda0001dSdan# 82*eda0001dSdan# pq_test_row 83*eda0001dSdan# Invoked each time a row is returned. Checks that the row returned 84*eda0001dSdan# was predicted by the documentation. 85*eda0001dSdan# 86*eda0001dSdan# DATA STRUCTURE: 87*eda0001dSdan# The priority queue is stored as a Tcl list. The order of elements in 88*eda0001dSdan# the list is unimportant - it is just used as a set here. Each element 89*eda0001dSdan# in the priority queue is itself a list. The first element is the 90*eda0001dSdan# priority value for the entry (a real). Following this is a list of 91*eda0001dSdan# key-value pairs that make up the entries fields. 92*eda0001dSdan# 93*eda0001dSdanproc pq_init {} { 94*eda0001dSdan global Q 95*eda0001dSdan set Q(pri_queue) [list] 96*eda0001dSdan 97*eda0001dSdan set nHeight [rnode_height [rt_node_get 1]] 98*eda0001dSdan set nCell [llength [rnode_cells [rt_node_get 1]]] 99*eda0001dSdan 100*eda0001dSdan # EVIDENCE-OF: R-54708-13595 An R*Tree query is initialized by making 101*eda0001dSdan # the root node the only entry in a priority queue sorted by rScore. 102*eda0001dSdan lappend Q(pri_queue) [list 0.0 [list \ 103*eda0001dSdan iLevel [expr $nHeight+1] \ 104*eda0001dSdan iChild 1 \ 105*eda0001dSdan iCurrent 0 \ 106*eda0001dSdan ]] 107*eda0001dSdan} 108*eda0001dSdan 109*eda0001dSdanproc pq_extract {} { 110*eda0001dSdan global Q 111*eda0001dSdan if {[llength $Q(pri_queue)]==0} { 112*eda0001dSdan error "priority queue is empty!" 113*eda0001dSdan } 114*eda0001dSdan 115*eda0001dSdan # Find the priority queue entry with the lowest score. 116*eda0001dSdan # 117*eda0001dSdan # EVIDENCE-OF: R-47257-47871 Smaller scores are processed first. 118*eda0001dSdan set iBest 0 119*eda0001dSdan set rBestScore [lindex $Q(pri_queue) 0 0] 120*eda0001dSdan for {set ii 1} {$ii < [llength $Q(pri_queue)]} {incr ii} { 121*eda0001dSdan set rScore [expr [lindex $Q(pri_queue) $ii 0]] 122*eda0001dSdan if {$rScore<$rBestScore} { 123*eda0001dSdan set rBestScore $rScore 124*eda0001dSdan set iBest $ii 125*eda0001dSdan } 126*eda0001dSdan } 127*eda0001dSdan 128*eda0001dSdan # Extract the entry with the lowest score from the queue and return it. 129*eda0001dSdan # 130*eda0001dSdan # EVIDENCE-OF: R-60002-49798 The query proceeds by extracting the entry 131*eda0001dSdan # from the priority queue that has the lowest score. 132*eda0001dSdan set ret [lindex $Q(pri_queue) $iBest] 133*eda0001dSdan set Q(pri_queue) [lreplace $Q(pri_queue) $iBest $iBest] 134*eda0001dSdan 135*eda0001dSdan return $ret 136*eda0001dSdan} 137*eda0001dSdan 138*eda0001dSdanproc pq_new_entry {rScore iLevel cell} { 139*eda0001dSdan global Q 140*eda0001dSdan 141*eda0001dSdan set rowid_name "iChild" 142*eda0001dSdan if {$iLevel==0} { set rowid_name "iRowid" } 143*eda0001dSdan 144*eda0001dSdan set kv [list] 145*eda0001dSdan lappend kv aCoord [lrange $cell 1 end] 146*eda0001dSdan lappend kv iLevel $iLevel 147*eda0001dSdan 148*eda0001dSdan if {$iLevel==0} { 149*eda0001dSdan lappend kv iRowid [lindex $cell 0] 150*eda0001dSdan } else { 151*eda0001dSdan lappend kv iChild [lindex $cell 0] 152*eda0001dSdan lappend kv iCurrent 0 153*eda0001dSdan } 154*eda0001dSdan 155*eda0001dSdan lappend Q(pri_queue) [list $rScore $kv] 156*eda0001dSdan} 157*eda0001dSdan 158*eda0001dSdanproc pq_test_callback {L res} { 159*eda0001dSdan #pq_debug "pq_test_callback $L -> $res" 160*eda0001dSdan global Q 161*eda0001dSdan 162*eda0001dSdan array set G $L ;# "Got" - as in stuff passed to xQuery 163*eda0001dSdan 164*eda0001dSdan # EVIDENCE-OF: R-65127-42665 If the extracted priority queue entry is a 165*eda0001dSdan # node (a subtree), then the next child of that node is passed to the 166*eda0001dSdan # xQueryFunc callback. 167*eda0001dSdan # 168*eda0001dSdan # If it had been a leaf, the row should have been returned, instead of 169*eda0001dSdan # xQueryCallback being called on a child - as is happening here. 170*eda0001dSdan foreach {rParentScore parent} [pq_extract] {} 171*eda0001dSdan array set P $parent ;# "Parent" - as in parent of expected cell 172*eda0001dSdan if {$P(iLevel)==0} { error "query callback mismatch (1)" } 173*eda0001dSdan set child_node [rnode_cells [rt_node_get $P(iChild)]] 174*eda0001dSdan set expected_cell [lindex $child_node $P(iCurrent)] 175*eda0001dSdan set expected_coords [lrange $expected_cell 1 end] 176*eda0001dSdan if {[llength $expected_coords] != [llength $G(aCoord)]} { 177*eda0001dSdan puts [array get P] 178*eda0001dSdan puts "E: $expected_coords G: $G(aCoord)" 179*eda0001dSdan error "coordinate mismatch in query callback (1)" 180*eda0001dSdan } 181*eda0001dSdan foreach a [lrange $expected_cell 1 end] b $G(aCoord) { 182*eda0001dSdan if {$a!=$b} { error "coordinate mismatch in query callback (2)" } 183*eda0001dSdan } 184*eda0001dSdan 185*eda0001dSdan # Check level is as expected 186*eda0001dSdan # 187*eda0001dSdan if {$G(iLevel) != $P(iLevel)-1} { 188*eda0001dSdan error "iLevel mismatch in query callback (1)" 189*eda0001dSdan } 190*eda0001dSdan 191*eda0001dSdan # Unless the callback returned NOT_WITHIN, add the entry to the priority 192*eda0001dSdan # queue. 193*eda0001dSdan # 194*eda0001dSdan # EVIDENCE-OF: R-28754-35153 Those subelements for which the xQueryFunc 195*eda0001dSdan # callback sets eWithin to PARTLY_WITHIN or FULLY_WITHIN are added to 196*eda0001dSdan # the priority queue using the score supplied by the callback. 197*eda0001dSdan # 198*eda0001dSdan # EVIDENCE-OF: R-08681-45277 Subelements that return NOT_WITHIN are 199*eda0001dSdan # discarded. 200*eda0001dSdan set r [lindex $res 0] 201*eda0001dSdan set rScore [lindex $res 1] 202*eda0001dSdan if {$r!="fully" && $r!="partly" && $r!="not"} { 203*eda0001dSdan error "unknown result: $r - expected \"fully\", \"partly\" or \"not\"" 204*eda0001dSdan } 205*eda0001dSdan if {$r!="not"} { 206*eda0001dSdan pq_new_entry $rScore [expr $P(iLevel)-1] $expected_cell 207*eda0001dSdan } 208*eda0001dSdan 209*eda0001dSdan # EVIDENCE-OF: R-07194-63805 If the node has more children then it is 210*eda0001dSdan # returned to the priority queue. Otherwise it is discarded. 211*eda0001dSdan incr P(iCurrent) 212*eda0001dSdan if {$P(iCurrent)<[llength $child_node]} { 213*eda0001dSdan lappend Q(pri_queue) [list $rParentScore [array get P]] 214*eda0001dSdan } 215*eda0001dSdan} 216*eda0001dSdan 217*eda0001dSdanproc pq_test_result {id x1 x2 y1 y2} { 218*eda0001dSdan #pq_debug "pq_test_result $id $x1 $x2 $y1 $y2" 219*eda0001dSdan foreach {rScore next} [pq_extract] {} 220*eda0001dSdan 221*eda0001dSdan # The extracted entry must be a leaf (otherwise, xQueryCallback would 222*eda0001dSdan # have been called on the extracted entries children instead of just 223*eda0001dSdan # returning the data). 224*eda0001dSdan # 225*eda0001dSdan # EVIDENCE-OF: R-13214-54017 If that entry is a leaf (meaning that it is 226*eda0001dSdan # an actual R*Tree entry and not a subtree) then that entry is returned 227*eda0001dSdan # as one row of the query result. 228*eda0001dSdan array set N $next 229*eda0001dSdan if {$N(iLevel)!=0} { error "result row mismatch (1)" } 230*eda0001dSdan 231*eda0001dSdan if {$x1!=[lindex $N(aCoord) 0] || $x2!=[lindex $N(aCoord) 1] 232*eda0001dSdan || $y1!=[lindex $N(aCoord) 2] || $y2!=[lindex $N(aCoord) 3] 233*eda0001dSdan } { 234*eda0001dSdan if {$N(iLevel)!=0} { error "result row mismatch (2)" } 235*eda0001dSdan } 236*eda0001dSdan 237*eda0001dSdan if {$id!=$N(iRowid)} { error "result row mismatch (3)" } 238*eda0001dSdan} 239*eda0001dSdan 240*eda0001dSdanproc pq_done {} { 241*eda0001dSdan global Q 242*eda0001dSdan # EVIDENCE-OF: R-57438-45968 The query runs until the priority queue is 243*eda0001dSdan # empty. 244*eda0001dSdan if {[llength $Q(pri_queue)]>0} { 245*eda0001dSdan error "priority queue is not empty!" 246*eda0001dSdan } 247*eda0001dSdan} 248*eda0001dSdan 249*eda0001dSdanproc pq_debug {caption} { 250*eda0001dSdan global Q 251*eda0001dSdan 252*eda0001dSdan puts "**** $caption ****" 253*eda0001dSdan set i 0 254*eda0001dSdan foreach q [lsort -real -index 0 $Q(pri_queue)] { 255*eda0001dSdan puts "PQ $i: $q" 256*eda0001dSdan incr i 257*eda0001dSdan } 258*eda0001dSdan} 259*eda0001dSdan 260*eda0001dSdan#-------------------------------------------------------------- 261*eda0001dSdan 262*eda0001dSdanproc box_query {a} { 263*eda0001dSdan set res [list fully [expr rand()]] 264*eda0001dSdan pq_test_callback $a $res 265*eda0001dSdan return $res 266*eda0001dSdan} 267*eda0001dSdan 268*eda0001dSdanregister_box_query db box_query 269*eda0001dSdan 270*eda0001dSdando_execsql_test 1.0 { 271*eda0001dSdan CREATE VIRTUAL TABLE rt USING rtree_i32(id, x1,x2, y1,y2); 272*eda0001dSdan WITH s(i) AS ( 273*eda0001dSdan SELECT 0 UNION ALL SELECT i+1 FROM s WHERE i<64 274*eda0001dSdan ) 275*eda0001dSdan INSERT INTO rt SELECT NULL, a.i, a.i+1, b.i, b.i+1 FROM s a, s b; 276*eda0001dSdan} 277*eda0001dSdan 278*eda0001dSdanproc box_query {a} { 279*eda0001dSdan set res [list fully [expr rand()]] 280*eda0001dSdan pq_test_callback $a $res 281*eda0001dSdan return $res 282*eda0001dSdan} 283*eda0001dSdan 284*eda0001dSdanpq_init 285*eda0001dSdandb eval { SELECT id, x1,x2, y1,y2 FROM rt WHERE id MATCH qbox() } { 286*eda0001dSdan pq_test_result $id $x1 $x2 $y1 $y2 287*eda0001dSdan} 288*eda0001dSdanpq_done 289*eda0001dSdan 290*eda0001dSdanfinish_test 291*eda0001dSdan 292*eda0001dSdan 293