mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-29 08:01:23 +03:00
Add SQL scalar function rtreecheck() to the rtree module. For running checks
to ensure the shadow tables used by an rtree virtual table are internally consistent. FossilOrigin-Name: dde0bb3eab1316c3247b1755594527ca70955aab4ad4907190731f7ec092b327
This commit is contained in:
@ -209,7 +209,7 @@ struct RtreeSearchPoint {
|
||||
** The smallest possible node-size is (512-64)==448 bytes. And the largest
|
||||
** supported cell size is 48 bytes (8 byte rowid + ten 4 byte coordinates).
|
||||
** Therefore all non-root nodes must contain at least 3 entries. Since
|
||||
** 2^40 is greater than 2^64, an r-tree structure always has a depth of
|
||||
** 3^40 is greater than 2^64, an r-tree structure always has a depth of
|
||||
** 40 or less.
|
||||
*/
|
||||
#define RTREE_MAX_DEPTH 40
|
||||
@ -3608,6 +3608,425 @@ static void rtreedepth(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Context object passed between the various routines that make up the
|
||||
** implementation of integrity-check function rtreecheck().
|
||||
*/
|
||||
typedef struct RtreeCheck RtreeCheck;
|
||||
struct RtreeCheck {
|
||||
sqlite3 *db; /* Database handle */
|
||||
const char *zDb; /* Database containing rtree table */
|
||||
const char *zTab; /* Name of rtree table */
|
||||
int bInt; /* True for rtree_i32 table */
|
||||
int nDim; /* Number of dimensions for this rtree tbl */
|
||||
sqlite3_stmt *pGetNode; /* Statement used to retrieve nodes */
|
||||
sqlite3_stmt *aCheckMapping[2]; /* Statements to query %_parent/%_rowid */
|
||||
int nLeaf; /* Number of leaf cells in table */
|
||||
int nNonLeaf; /* Number of non-leaf cells in table */
|
||||
int rc; /* Return code */
|
||||
char *zReport; /* Message to report */
|
||||
int nErr; /* Number of lines in zReport */
|
||||
};
|
||||
|
||||
#define RTREE_CHECK_MAX_ERROR 100
|
||||
|
||||
/*
|
||||
** Reset SQL statement pStmt. If the sqlite3_reset() call returns an error,
|
||||
** and RtreeCheck.rc==SQLITE_OK, set RtreeCheck.rc to the error code.
|
||||
*/
|
||||
static void rtreeCheckReset(RtreeCheck *pCheck, sqlite3_stmt *pStmt){
|
||||
int rc = sqlite3_reset(pStmt);
|
||||
if( pCheck->rc==SQLITE_OK ) pCheck->rc = rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** The second and subsequent arguments to this function are a format string
|
||||
** and printf style arguments. This function formats the string and attempts
|
||||
** to compile it as an SQL statement.
|
||||
**
|
||||
** If successful, a pointer to the new SQL statement is returned. Otherwise,
|
||||
** NULL is returned and an error code left in RtreeCheck.rc.
|
||||
*/
|
||||
static sqlite3_stmt *rtreeCheckPrepare(
|
||||
RtreeCheck *pCheck, /* RtreeCheck object */
|
||||
const char *zFmt, ... /* Format string and trailing args */
|
||||
){
|
||||
va_list ap;
|
||||
va_start(ap, zFmt);
|
||||
char *z = sqlite3_vmprintf(zFmt, ap);
|
||||
sqlite3_stmt *pRet = 0;
|
||||
|
||||
if( pCheck->rc==SQLITE_OK ){
|
||||
pCheck->rc = sqlite3_prepare_v2(pCheck->db, z, -1, &pRet, 0);
|
||||
}
|
||||
|
||||
sqlite3_free(z);
|
||||
va_end(ap);
|
||||
return pRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** The second and subsequent arguments to this function are a printf()
|
||||
** style format string and arguments. This function formats the string and
|
||||
** appends it to the report being accumuated in pCheck.
|
||||
*/
|
||||
static void rtreeCheckAppendMsg(RtreeCheck *pCheck, const char *zFmt, ...){
|
||||
va_list ap;
|
||||
va_start(ap, zFmt);
|
||||
if( pCheck->rc==SQLITE_OK && pCheck->nErr<RTREE_CHECK_MAX_ERROR ){
|
||||
char *z = sqlite3_vmprintf(zFmt, ap);
|
||||
if( z==0 ){
|
||||
pCheck->rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
pCheck->zReport = sqlite3_mprintf("%z%s%z",
|
||||
pCheck->zReport, (pCheck->zReport ? "\n" : ""), z
|
||||
);
|
||||
if( pCheck->zReport==0 ){
|
||||
pCheck->rc = SQLITE_NOMEM;
|
||||
}
|
||||
}
|
||||
pCheck->nErr++;
|
||||
}
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is a no-op if there is already an error code stored
|
||||
** in the RtreeCheck object indicated by the first argument. NULL is
|
||||
** returned in this case.
|
||||
**
|
||||
** Otherwise, the contents of rtree table node iNode are loaded from
|
||||
** the database and copied into a buffer obtained from sqlite3_malloc().
|
||||
** If no error occurs, a pointer to the buffer is returned and (*pnNode)
|
||||
** is set to the size of the buffer in bytes.
|
||||
**
|
||||
** Or, if an error does occur, NULL is returned and an error code left
|
||||
** in the RtreeCheck object. The final value of *pnNode is undefined in
|
||||
** this case.
|
||||
*/
|
||||
static u8 *rtreeCheckGetNode(RtreeCheck *pCheck, i64 iNode, int *pnNode){
|
||||
u8 *pRet = 0; /* Return value */
|
||||
|
||||
assert( pCheck->rc==SQLITE_OK );
|
||||
if( pCheck->pGetNode==0 ){
|
||||
pCheck->pGetNode = rtreeCheckPrepare(pCheck,
|
||||
"SELECT data FROM %Q.'%q_node' WHERE nodeno=?",
|
||||
pCheck->zDb, pCheck->zTab
|
||||
);
|
||||
}
|
||||
|
||||
if( pCheck->rc==SQLITE_OK ){
|
||||
sqlite3_bind_int64(pCheck->pGetNode, 1, iNode);
|
||||
if( sqlite3_step(pCheck->pGetNode)==SQLITE_ROW ){
|
||||
int nNode = sqlite3_column_bytes(pCheck->pGetNode, 0);
|
||||
const u8 *pNode = (const u8*)sqlite3_column_blob(pCheck->pGetNode, 0);
|
||||
pRet = sqlite3_malloc(nNode);
|
||||
if( pRet==0 ){
|
||||
pCheck->rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
memcpy(pRet, pNode, nNode);
|
||||
*pnNode = nNode;
|
||||
}
|
||||
}
|
||||
rtreeCheckReset(pCheck, pCheck->pGetNode);
|
||||
if( pCheck->rc==SQLITE_OK && pRet==0 ){
|
||||
rtreeCheckAppendMsg(pCheck, "Node %lld missing from database", iNode);
|
||||
}
|
||||
}
|
||||
|
||||
return pRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is used to check that the %_parent (if bLeaf==0) or %_rowid
|
||||
** (if bLeaf==1) table contains a specified entry. The schemas of the
|
||||
** two tables are:
|
||||
**
|
||||
** CREATE TABLE %_parent(nodeno INTEGER PRIMARY KEY, parentnode INTEGER)
|
||||
** CREATE TABLE %_rowid(rowid INTEGER PRIMARY KEY, nodeno INTEGER)
|
||||
**
|
||||
** In both cases, this function checks that there exists an entry with
|
||||
** IPK value iKey and the second column set to iVal.
|
||||
**
|
||||
*/
|
||||
static void rtreeCheckMapping(
|
||||
RtreeCheck *pCheck, /* RtreeCheck object */
|
||||
int bLeaf, /* True for a leaf cell, false for interior */
|
||||
i64 iKey, /* Key for mapping */
|
||||
i64 iVal /* Expected value for mapping */
|
||||
){
|
||||
int rc;
|
||||
sqlite3_stmt *pStmt;
|
||||
const char *azSql[2] = {
|
||||
"SELECT parentnode FROM %Q.'%q_parent' WHERE nodeno=?",
|
||||
"SELECT nodeno FROM %Q.'%q_rowid' WHERE rowid=?"
|
||||
};
|
||||
|
||||
assert( bLeaf==0 || bLeaf==1 );
|
||||
if( pCheck->aCheckMapping[bLeaf]==0 ){
|
||||
pCheck->aCheckMapping[bLeaf] = rtreeCheckPrepare(pCheck,
|
||||
azSql[bLeaf], pCheck->zDb, pCheck->zTab
|
||||
);
|
||||
}
|
||||
if( pCheck->rc!=SQLITE_OK ) return;
|
||||
|
||||
pStmt = pCheck->aCheckMapping[bLeaf];
|
||||
sqlite3_bind_int64(pStmt, 1, iKey);
|
||||
rc = sqlite3_step(pStmt);
|
||||
if( rc==SQLITE_DONE ){
|
||||
rtreeCheckAppendMsg(pCheck, "Mapping (%lld -> %lld) missing from %s table",
|
||||
iKey, iVal, (bLeaf ? "%_rowid" : "%_parent")
|
||||
);
|
||||
}else if( rc==SQLITE_ROW ){
|
||||
i64 ii = sqlite3_column_int64(pStmt, 0);
|
||||
if( ii!=iVal ){
|
||||
rtreeCheckAppendMsg(pCheck,
|
||||
"Found (%lld -> %lld) in %s table, expected (%lld -> %lld)",
|
||||
iKey, ii, (bLeaf ? "%_rowid" : "%_parent"), iKey, iVal
|
||||
);
|
||||
}
|
||||
}
|
||||
rtreeCheckReset(pCheck, pStmt);
|
||||
}
|
||||
|
||||
static void rtreeCheckCellCoord(
|
||||
RtreeCheck *pCheck,
|
||||
i64 iNode,
|
||||
int iCell,
|
||||
u8 *pCell, /* Pointer to cell coordinates */
|
||||
u8 *pParent /* Pointer to parent coordinates */
|
||||
){
|
||||
RtreeCoord c1, c2;
|
||||
RtreeCoord p1, p2;
|
||||
int i;
|
||||
|
||||
for(i=0; i<pCheck->nDim; i++){
|
||||
readCoord(&pCell[4*2*i], &c1);
|
||||
readCoord(&pCell[4*(2*i + 1)], &c2);
|
||||
|
||||
/* printf("%e, %e\n", c1.u.f, c2.u.f); */
|
||||
if( pCheck->bInt ? c1.i>c2.i : c1.f>c2.f ){
|
||||
rtreeCheckAppendMsg(pCheck,
|
||||
"Dimension %d of cell %d on node %lld is corrupt", i, iCell, iNode
|
||||
);
|
||||
}
|
||||
|
||||
if( pParent ){
|
||||
readCoord(&pParent[4*2*i], &p1);
|
||||
readCoord(&pParent[4*(2*i + 1)], &p2);
|
||||
|
||||
if( (pCheck->bInt ? c1.i<p1.i : c1.f<p1.f)
|
||||
|| (pCheck->bInt ? c2.i>p2.i : c2.f>p2.f)
|
||||
){
|
||||
rtreeCheckAppendMsg(pCheck,
|
||||
"Dimension %d of cell %d on node %lld is corrupt relative to parent"
|
||||
, i, iCell, iNode
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void rtreeCheckNode(
|
||||
RtreeCheck *pCheck,
|
||||
int iDepth, /* Depth of iNode (0==leaf) */
|
||||
u8 *aParent, /* Buffer containing parent coords */
|
||||
i64 iNode /* Node to check */
|
||||
){
|
||||
u8 *aNode = 0;
|
||||
int nNode = 0;
|
||||
|
||||
assert( iNode==1 || aParent!=0 );
|
||||
assert( pCheck->nDim>0 );
|
||||
|
||||
aNode = rtreeCheckGetNode(pCheck, iNode, &nNode);
|
||||
if( aNode ){
|
||||
if( nNode<4 ){
|
||||
rtreeCheckAppendMsg(pCheck,
|
||||
"Node %lld is too small (%d bytes)", iNode, nNode
|
||||
);
|
||||
}else{
|
||||
int nCell; /* Number of cells on page */
|
||||
int i; /* Used to iterate through cells */
|
||||
if( aParent==0 ){
|
||||
iDepth = readInt16(aNode);
|
||||
if( iDepth>RTREE_MAX_DEPTH ){
|
||||
rtreeCheckAppendMsg(pCheck, "Rtree depth out of range (%d)", iDepth);
|
||||
sqlite3_free(aNode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
nCell = readInt16(&aNode[2]);
|
||||
if( (4 + nCell*(8 + pCheck->nDim*2*4))>nNode ){
|
||||
rtreeCheckAppendMsg(pCheck,
|
||||
"Node %lld is too small for cell count of %d (%d bytes)",
|
||||
iNode, nCell, nNode
|
||||
);
|
||||
}
|
||||
for(i=0; i<nCell; i++){
|
||||
u8 *pCell = &aNode[4 + i*(8 + pCheck->nDim*2*4)];
|
||||
i64 iVal = readInt64(pCell);
|
||||
rtreeCheckCellCoord(pCheck, iNode, i, &pCell[8], aParent);
|
||||
|
||||
if( iDepth>0 ){
|
||||
rtreeCheckMapping(pCheck, 0, iVal, iNode);
|
||||
rtreeCheckNode(pCheck, iDepth-1, &pCell[8], iVal);
|
||||
pCheck->nNonLeaf++;
|
||||
}else{
|
||||
rtreeCheckMapping(pCheck, 1, iVal, iNode);
|
||||
pCheck->nLeaf++;
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlite3_free(aNode);
|
||||
}
|
||||
}
|
||||
|
||||
static void rtreeCheckCount(
|
||||
RtreeCheck *pCheck, const char *zTbl, i64 nExpected
|
||||
){
|
||||
if( pCheck->rc==SQLITE_OK ){
|
||||
sqlite3_stmt *pCount;
|
||||
pCount = rtreeCheckPrepare(pCheck, "SELECT count(*) FROM %Q.'%q%s'",
|
||||
pCheck->zDb, pCheck->zTab, zTbl
|
||||
);
|
||||
if( pCount ){
|
||||
if( sqlite3_step(pCount)==SQLITE_ROW ){
|
||||
i64 nActual = sqlite3_column_int64(pCount, 0);
|
||||
if( nActual!=nExpected ){
|
||||
rtreeCheckAppendMsg(pCheck, "Wrong number of entries in %%%s table"
|
||||
" - expected %lld, actual %lld" , zTbl, nExpected, nActual
|
||||
);
|
||||
}
|
||||
}
|
||||
pCheck->rc = sqlite3_finalize(pCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int rtreeCheck(
|
||||
sqlite3 *db, /* Database handle to access db through */
|
||||
const char *zDb, /* Name of db ("main", "temp" etc.) */
|
||||
const char *zTab, /* Name of rtree table to check */
|
||||
char **pzReport /* OUT: sqlite3_malloc'd report text */
|
||||
){
|
||||
RtreeCheck check; /* Common context for various routines */
|
||||
sqlite3_stmt *pStmt = 0; /* Used to find column count of rtree table */
|
||||
int bEnd = 0; /* True if transaction should be closed */
|
||||
|
||||
/* Initialize the context object */
|
||||
memset(&check, 0, sizeof(check));
|
||||
check.db = db;
|
||||
check.zDb = zDb;
|
||||
check.zTab = zTab;
|
||||
|
||||
/* If there is not already an open transaction, open one now. This is
|
||||
** to ensure that the queries run as part of this integrity-check operate
|
||||
** on a consistent snapshot. */
|
||||
if( sqlite3_get_autocommit(db) ){
|
||||
check.rc = sqlite3_exec(db, "BEGIN", 0, 0, 0);
|
||||
bEnd = 1;
|
||||
}
|
||||
|
||||
/* Find number of dimensions in the rtree table. */
|
||||
pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.%Q", zDb, zTab);
|
||||
if( pStmt ){
|
||||
int rc;
|
||||
check.nDim = (sqlite3_column_count(pStmt) - 1) / 2;
|
||||
if( check.nDim<1 ){
|
||||
rtreeCheckAppendMsg(&check, "Schema corrupt or not an rtree");
|
||||
}else if( SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
check.bInt = (sqlite3_column_type(pStmt, 1)==SQLITE_INTEGER);
|
||||
}
|
||||
rc = sqlite3_finalize(pStmt);
|
||||
if( rc!=SQLITE_CORRUPT ) check.rc = rc;
|
||||
}
|
||||
|
||||
/* Do the actual integrity-check */
|
||||
if( check.rc==SQLITE_OK ){
|
||||
rtreeCheckNode(&check, 0, 0, 1);
|
||||
}
|
||||
rtreeCheckCount(&check, "_rowid", check.nLeaf);
|
||||
rtreeCheckCount(&check, "_parent", check.nNonLeaf);
|
||||
|
||||
/* Finalize SQL statements used by the integrity-check */
|
||||
sqlite3_finalize(check.pGetNode);
|
||||
sqlite3_finalize(check.aCheckMapping[0]);
|
||||
sqlite3_finalize(check.aCheckMapping[1]);
|
||||
|
||||
/* If one was opened, close the transaction */
|
||||
if( bEnd ){
|
||||
int rc = sqlite3_exec(db, "END", 0, 0, 0);
|
||||
if( check.rc==SQLITE_OK ) check.rc = rc;
|
||||
}
|
||||
*pzReport = check.zReport;
|
||||
return check.rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Usage:
|
||||
**
|
||||
** rtreecheck(<rtree-table>);
|
||||
** rtreecheck(<database>, <rtree-table>);
|
||||
**
|
||||
** Invoking this SQL function runs an integrity-check on the named rtree
|
||||
** table. The integrity-check verifies the following:
|
||||
**
|
||||
** 1. For each cell in the r-tree structure (%_node table), that:
|
||||
**
|
||||
** a) for each dimension, (coord1 <= coord2).
|
||||
**
|
||||
** b) unless the cell is on the root node, that the cell is bounded
|
||||
** by the parent cell on the parent node.
|
||||
**
|
||||
** c) for leaf nodes, that there is an entry in the %_rowid
|
||||
** table corresponding to the cell's rowid value that
|
||||
** points to the correct node.
|
||||
**
|
||||
** d) for cells on non-leaf nodes, that there is an entry in the
|
||||
** %_parent table mapping from the cell's child node to the
|
||||
** node that it resides on.
|
||||
**
|
||||
** 2. That there are the same number of entries in the %_rowid table
|
||||
** as there are leaf cells in the r-tree structure, and that there
|
||||
** is a leaf cell that corresponds to each entry in the %_rowid table.
|
||||
**
|
||||
** 3. That there are the same number of entries in the %_parent table
|
||||
** as there are non-leaf cells in the r-tree structure, and that
|
||||
** there is a non-leaf cell that corresponds to each entry in the
|
||||
** %_parent table.
|
||||
*/
|
||||
static void rtreecheck(
|
||||
sqlite3_context *ctx,
|
||||
int nArg,
|
||||
sqlite3_value **apArg
|
||||
){
|
||||
if( nArg!=1 && nArg!=2 ){
|
||||
sqlite3_result_error(ctx,
|
||||
"wrong number of arguments to function rtreecheck()", -1
|
||||
);
|
||||
}else{
|
||||
int rc;
|
||||
char *zReport = 0;
|
||||
const char *zDb = (const char*)sqlite3_value_text(apArg[0]);
|
||||
const char *zTab;
|
||||
if( nArg==1 ){
|
||||
zTab = zDb;
|
||||
zDb = "main";
|
||||
}else{
|
||||
zTab = (const char*)sqlite3_value_text(apArg[1]);
|
||||
}
|
||||
rc = rtreeCheck(sqlite3_context_db_handle(ctx), zDb, zTab, &zReport);
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_result_text(ctx, zReport ? zReport : "ok", -1, SQLITE_TRANSIENT);
|
||||
}else{
|
||||
sqlite3_result_error_code(ctx, rc);
|
||||
}
|
||||
sqlite3_free(zReport);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Register the r-tree module with database handle db. This creates the
|
||||
** virtual table module "rtree" and the debugging/analysis scalar
|
||||
@ -3621,6 +4040,9 @@ int sqlite3RtreeInit(sqlite3 *db){
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_create_function(db, "rtreedepth", 1, utf8, 0,rtreedepth, 0, 0);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_create_function(db, "rtreecheck", -1, utf8, 0,rtreecheck, 0,0);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
#ifdef SQLITE_RTREE_INT_ONLY
|
||||
void *c = (void *)RTREE_COORD_INT32;
|
||||
|
Reference in New Issue
Block a user