1
0
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:
dan
2017-10-25 16:38:34 +00:00
parent b5d013edd1
commit 1917e92fdb
18 changed files with 623 additions and 34 deletions

View File

@ -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;