xref: /sqlite-3.40.0/ext/rtree/rtreedoc3.test (revision eda0001d)
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