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