Behaviour of rtree, rtree_i32 and dbstat as OneLite implements it, with what was settled by observing the pinned shell. Results are
compared with the shell by tools/OneLite.RTreeDiff (seeded random scripts, statement by statement, then the shell's own checks on the file
OneLite wrote and OneLite's checks on the file the shell wrote) and by tests/OneLite.Tests/Engine/RTreeTests.cs.
R*Tree
- Shadow tables are created in the order
%_rowid,%_node,%_parentafter the virtual table's own schema row (rootpage0):%_rowid(rowid INTEGER PRIMARY KEY,nodeno,a0,a1,...)(oneaNper auxiliary column),%_node(nodeno INTEGER PRIMARY KEY,data),%_parent(nodeno INTEGER PRIMARY KEY,parentnode).%_nodestarts with node 1, the root, aspageSize - 64zero bytes. - Node blob: bytes 0-1 tree depth (meaningful in the root), bytes 2-3 cell count, then cells of
8 + 8 * dimsbytes: big-endian int64 rowid (child node number above the leaves) and per dimensionmin,maxas big-endian float32 (rtree) or int32 (rtree_i32). Observed: a node holds at most 51 cells whatever its size allows (the shell reports a node with more as corrupt); OneLite splits at 52. - Columns: the first column is an INTEGER rowid (NULL assigns
max(rowid)+1; other values go through the integer conversion, so'7'is 7 and'x'is 0);rowidnamed in an INSERT or UPDATE is ignored in favour of the first column.rtreecoordinates are REAL on the way out,rtree_i32INTEGER. Column names are the first token of each argument;+namemarks an auxiliary column and must come last. - Coordinates in:
rtree_i32takes the low 32 bits of the integer conversion (observed:1e300and9223372036854775807both fail themin <= maxcheck against a normal minimum).rtreeconverts to REAL; a value that is not exactly a float32 is moved outward by a relative 2^-23 (lower bounds down, upper bounds up) and rounded to the nearest float32 (observed against the shell on 0.1, 3.7, 559.936, 592.297 and 16777217; this is the "0.000012%" of the documentation). - Errors (messages as the shell):
Too few columns for an rtree table,Too many columns for an rtree table(more than 11 coordinate columns or 100 columns),Wrong number of columns for an rtree table(even number of coordinate columns),Auxiliary rtree columns must be last,rtree constraint failed: T.(minCol<=maxCol)(SQLITE_CONSTRAINT),UNIQUE constraint failed: T.id(message compared; the extended code is OneLite's choice, 1555, and has not been compared). - MATCH with a geometry callback is not supported: no
rtree_geometryor query callbacks are registered, andx MATCH yis not usable (the shell fails it withSQL logic error). - Scans: constraints on coordinate columns (
=,<,<=,>,>=) prune subtrees; an equality on the first column goes through%_rowid. The engine tests every term again on the returned rows (observed:a = 0.1finds nothing in a table holding the rounded value, as in the shell), so pruning only needs to keep every row that could match. Row order is the tree order and is not promised. - Writes: the algorithm of Beckmann et al.: ChooseSubtree by least overlap enlargement above the leaves (the 32 best by area enlargement
when there are more) and least area enlargement elsewhere; one forced reinsertion of the 30% of entries farthest from the node centre per
level and insertion (closest first); otherwise the margin-sum axis and least-overlap distribution split with a minimum fill of 40%. A delete
condenses as Guttman's: nodes under a third of capacity are dissolved and their entries reinserted, and a root with one child is lifted.
The root stays node 1. The shape of the tree, and so the shadow tables' bytes, differ from the shell's for the same SQL; both are valid
and answer every query alike, so
.dumpof two files built by the two engines matches for the table's content but not for%_node. - Suspension: a change is planned in memory (reads of nodes, parent links and rowid rows that miss the cache throw an internal exception
after starting the read,
Updatereturns Pending, and the next call plans again from the start with the finished reads remembered), then written with one shadow-table write per step, each repeated with the same arguments after Pending. Scans keep their position in a stack of nodes and cell indexes, and load a child before moving past its cell. - Functions:
rtreecheck([schema,] table)returnsokor one line per problem (boxes ordered and inside the parent's, leaf cells and%_rowid, child cells and%_parent, row counts); the wording of problem lines is OneLite's own.rtreedepth(blob)andrtreenode(ndim, blob)as in the shell (%gcoordinates). - Not done: reading and writing at once (the shell fails the second with SQLITE_LOCKED; OneLite plans from a drained scan, so its UPDATE and DELETE do not meet that), geometry callbacks.
dbstat
- Columns
name, path, pageno, pagetype, ncell, payload, unused, mx_payload, pgoffset, pgsizeand hiddenschema, aggregate; eponymous, withdbstat('schema', aggregate)arguments;CREATE VIRTUAL TABLE x USING dbstat(schema)sets the default schema. - B-trees in
sqlite_schemarow order, starting with the schema table itself (namedsqlite_schema). Rows of a B-tree: a page, then for each cell its overflow chain (path/001/000+000002), then (for interior pages) the child; the right-most child last. The path of a child is the parent's path plus the 3-digit hexadecimal cell index and/. payloadis the bytes of cell payload held on the page (the local part only),mx_payloadthe largest total payload length of a cell on the page,unusedthe gap before the cell content area, the freeblocks and the fragmented bytes. An overflow page reports the payload bytes it holds andusable - 4 - payloadunused. Aggregate mode sums these per B-tree;pagenobecomes the page count,pgsizepages times page size.- Observed quirk reproduced: an overflow page's
pgoffsetis the offset of the page listed just before it. - A scan reads all pages of the schema when it starts; unlike the R*Tree it is not suspendable (it waits on I/O inside
Filter).