Add support for auxiliary columns to the rtree extension.
FossilOrigin-Name: c6071ac99cfa4b6272ac4d739fc61a85acb544f6c1c2ae67b31e92aadcc995bd
This commit is contained in:
commit
e8cf689df3
@ -24,14 +24,15 @@
|
||||
**
|
||||
** CREATE TABLE %_node(nodeno INTEGER PRIMARY KEY, data BLOB)
|
||||
** CREATE TABLE %_parent(nodeno INTEGER PRIMARY KEY, parentnode INTEGER)
|
||||
** CREATE TABLE %_rowid(rowid INTEGER PRIMARY KEY, nodeno INTEGER)
|
||||
** CREATE TABLE %_rowid(rowid INTEGER PRIMARY KEY, nodeno INTEGER, ...)
|
||||
**
|
||||
** The data for each node of the r-tree structure is stored in the %_node
|
||||
** table. For each node that is not the root node of the r-tree, there is
|
||||
** an entry in the %_parent table associating the node with its parent.
|
||||
** And for each row of data in the table, there is an entry in the %_rowid
|
||||
** table that maps from the entries rowid to the id of the node that it
|
||||
** is stored on.
|
||||
** is stored on. If the r-tree contains auxiliary columns, those are stored
|
||||
** on the end of the %_rowid table.
|
||||
**
|
||||
** The root node of an r-tree always exists, even if the r-tree table is
|
||||
** empty. The nodeno of the root node is always 1. All other nodes in the
|
||||
@ -94,6 +95,9 @@ typedef struct RtreeSearchPoint RtreeSearchPoint;
|
||||
/* The rtree may have between 1 and RTREE_MAX_DIMENSIONS dimensions. */
|
||||
#define RTREE_MAX_DIMENSIONS 5
|
||||
|
||||
/* Maximum number of auxiliary columns */
|
||||
#define RTREE_MAX_AUX_COLUMN 100
|
||||
|
||||
/* Size of hash table Rtree.aHash. This hash table is not expected to
|
||||
** ever contain very many entries, so a fixed number of buckets is
|
||||
** used.
|
||||
@ -122,12 +126,14 @@ struct Rtree {
|
||||
u8 eCoordType; /* RTREE_COORD_REAL32 or RTREE_COORD_INT32 */
|
||||
u8 nBytesPerCell; /* Bytes consumed per cell */
|
||||
u8 inWrTrans; /* True if inside write transaction */
|
||||
u8 nAux; /* # of auxiliary columns in %_rowid */
|
||||
int iDepth; /* Current depth of the r-tree structure */
|
||||
char *zDb; /* Name of database containing r-tree table */
|
||||
char *zName; /* Name of r-tree table */
|
||||
u32 nBusy; /* Current number of users of this structure */
|
||||
i64 nRowEst; /* Estimated number of rows in this table */
|
||||
u32 nCursor; /* Number of open cursors */
|
||||
char *zReadAuxSql; /* SQL for statement to read aux data */
|
||||
|
||||
/* List of nodes removed during a CondenseTree operation. List is
|
||||
** linked together via the pointer normally used for hash chains -
|
||||
@ -154,6 +160,9 @@ struct Rtree {
|
||||
sqlite3_stmt *pWriteParent;
|
||||
sqlite3_stmt *pDeleteParent;
|
||||
|
||||
/* Statement for writing to the "aux:" fields, if there are any */
|
||||
sqlite3_stmt *pWriteAux;
|
||||
|
||||
RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */
|
||||
};
|
||||
|
||||
@ -230,6 +239,7 @@ struct RtreeCursor {
|
||||
sqlite3_vtab_cursor base; /* Base class. Must be first */
|
||||
u8 atEOF; /* True if at end of search */
|
||||
u8 bPoint; /* True if sPoint is valid */
|
||||
u8 bAuxValid; /* True if pReadAux is valid */
|
||||
int iStrategy; /* Copy of idxNum search parameter */
|
||||
int nConstraint; /* Number of entries in aConstraint */
|
||||
RtreeConstraint *aConstraint; /* Search constraints. */
|
||||
@ -237,6 +247,7 @@ struct RtreeCursor {
|
||||
int nPoint; /* Number of slots used in aPoint[] */
|
||||
int mxLevel; /* iLevel value for root of the tree */
|
||||
RtreeSearchPoint *aPoint; /* Priority queue for search points */
|
||||
sqlite3_stmt *pReadAux; /* Statement to read aux-data */
|
||||
RtreeSearchPoint sPoint; /* Cached next search point */
|
||||
RtreeNode *aNode[RTREE_CACHE_SZ]; /* Rtree node cache */
|
||||
u32 anQueue[RTREE_MAX_DEPTH+1]; /* Number of queued entries by iLevel */
|
||||
@ -930,6 +941,8 @@ static void rtreeRelease(Rtree *pRtree){
|
||||
sqlite3_finalize(pRtree->pReadParent);
|
||||
sqlite3_finalize(pRtree->pWriteParent);
|
||||
sqlite3_finalize(pRtree->pDeleteParent);
|
||||
sqlite3_finalize(pRtree->pWriteAux);
|
||||
sqlite3_free(pRtree->zReadAuxSql);
|
||||
sqlite3_free(pRtree);
|
||||
}
|
||||
}
|
||||
@ -1018,6 +1031,7 @@ static int rtreeClose(sqlite3_vtab_cursor *cur){
|
||||
RtreeCursor *pCsr = (RtreeCursor *)cur;
|
||||
assert( pRtree->nCursor>0 );
|
||||
freeCursorConstraints(pCsr);
|
||||
sqlite3_finalize(pCsr->pReadAux);
|
||||
sqlite3_free(pCsr->aPoint);
|
||||
for(ii=0; ii<RTREE_CACHE_SZ; ii++) nodeRelease(pRtree, pCsr->aNode[ii]);
|
||||
sqlite3_free(pCsr);
|
||||
@ -1560,6 +1574,10 @@ static int rtreeNext(sqlite3_vtab_cursor *pVtabCursor){
|
||||
|
||||
/* Move to the next entry that matches the configured constraints. */
|
||||
RTREE_QUEUE_TRACE(pCsr, "POP-Nx:");
|
||||
if( pCsr->bAuxValid ){
|
||||
pCsr->bAuxValid = 0;
|
||||
sqlite3_reset(pCsr->pReadAux);
|
||||
}
|
||||
rtreeSearchPointPop(pCsr);
|
||||
rc = rtreeStepToLeaf(pCsr);
|
||||
return rc;
|
||||
@ -1594,7 +1612,7 @@ static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
|
||||
if( p==0 ) return SQLITE_OK;
|
||||
if( i==0 ){
|
||||
sqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell));
|
||||
}else{
|
||||
}else if( i<=pRtree->nDim2 ){
|
||||
nodeGetCoord(pRtree, pNode, p->iCell, i-1, &c);
|
||||
#ifndef SQLITE_RTREE_INT_ONLY
|
||||
if( pRtree->eCoordType==RTREE_COORD_REAL32 ){
|
||||
@ -1605,7 +1623,27 @@ static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
|
||||
assert( pRtree->eCoordType==RTREE_COORD_INT32 );
|
||||
sqlite3_result_int(ctx, c.i);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if( !pCsr->bAuxValid ){
|
||||
if( pCsr->pReadAux==0 ){
|
||||
rc = sqlite3_prepare_v3(pRtree->db, pRtree->zReadAuxSql, -1, 0,
|
||||
&pCsr->pReadAux, 0);
|
||||
if( rc ) return rc;
|
||||
}
|
||||
sqlite3_bind_int64(pCsr->pReadAux, 1,
|
||||
nodeGetRowid(pRtree, pNode, p->iCell));
|
||||
rc = sqlite3_step(pCsr->pReadAux);
|
||||
if( rc==SQLITE_ROW ){
|
||||
pCsr->bAuxValid = 1;
|
||||
}else{
|
||||
sqlite3_reset(pCsr->pReadAux);
|
||||
if( rc==SQLITE_DONE ) rc = SQLITE_OK;
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
sqlite3_result_value(ctx,
|
||||
sqlite3_column_value(pCsr->pReadAux, i - pRtree->nDim2 + 1));
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
@ -1683,14 +1721,17 @@ static int rtreeFilter(
|
||||
int ii;
|
||||
int rc = SQLITE_OK;
|
||||
int iCell = 0;
|
||||
sqlite3_stmt *pStmt;
|
||||
|
||||
rtreeReference(pRtree);
|
||||
|
||||
/* Reset the cursor to the same state as rtreeOpen() leaves it in. */
|
||||
freeCursorConstraints(pCsr);
|
||||
sqlite3_free(pCsr->aPoint);
|
||||
pStmt = pCsr->pReadAux;
|
||||
memset(pCsr, 0, sizeof(RtreeCursor));
|
||||
pCsr->base.pVtab = (sqlite3_vtab*)pRtree;
|
||||
pCsr->pReadAux = pStmt;
|
||||
|
||||
pCsr->iStrategy = idxNum;
|
||||
if( idxNum==1 ){
|
||||
@ -1856,7 +1897,10 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
if( p->usable && (p->iColumn>0 || p->op==SQLITE_INDEX_CONSTRAINT_MATCH) ){
|
||||
if( p->usable
|
||||
&& ((p->iColumn>0 && p->iColumn<=pRtree->nDim2)
|
||||
|| p->op==SQLITE_INDEX_CONSTRAINT_MATCH)
|
||||
){
|
||||
u8 op;
|
||||
switch( p->op ){
|
||||
case SQLITE_INDEX_CONSTRAINT_EQ: op = RTREE_EQ; break;
|
||||
@ -3015,7 +3059,7 @@ static int rtreeConstraintError(Rtree *pRtree, int iCol){
|
||||
static int rtreeUpdate(
|
||||
sqlite3_vtab *pVtab,
|
||||
int nData,
|
||||
sqlite3_value **azData,
|
||||
sqlite3_value **aData,
|
||||
sqlite_int64 *pRowid
|
||||
){
|
||||
Rtree *pRtree = (Rtree *)pVtab;
|
||||
@ -3041,8 +3085,10 @@ static int rtreeUpdate(
|
||||
*/
|
||||
if( nData>1 ){
|
||||
int ii;
|
||||
int nn = nData - 4;
|
||||
|
||||
/* Populate the cell.aCoord[] array. The first coordinate is azData[3].
|
||||
if( nn > pRtree->nDim2 ) nn = pRtree->nDim2;
|
||||
/* Populate the cell.aCoord[] array. The first coordinate is aData[3].
|
||||
**
|
||||
** NB: nData can only be less than nDim*2+3 if the rtree is mis-declared
|
||||
** with "column" that are interpreted as table constraints.
|
||||
@ -3050,13 +3096,12 @@ static int rtreeUpdate(
|
||||
** This problem was discovered after years of use, so we silently ignore
|
||||
** these kinds of misdeclared tables to avoid breaking any legacy.
|
||||
*/
|
||||
assert( nData<=(pRtree->nDim2 + 3) );
|
||||
|
||||
#ifndef SQLITE_RTREE_INT_ONLY
|
||||
if( pRtree->eCoordType==RTREE_COORD_REAL32 ){
|
||||
for(ii=0; ii<nData-4; ii+=2){
|
||||
cell.aCoord[ii].f = rtreeValueDown(azData[ii+3]);
|
||||
cell.aCoord[ii+1].f = rtreeValueUp(azData[ii+4]);
|
||||
for(ii=0; ii<nn; ii+=2){
|
||||
cell.aCoord[ii].f = rtreeValueDown(aData[ii+3]);
|
||||
cell.aCoord[ii+1].f = rtreeValueUp(aData[ii+4]);
|
||||
if( cell.aCoord[ii].f>cell.aCoord[ii+1].f ){
|
||||
rc = rtreeConstraintError(pRtree, ii+1);
|
||||
goto constraint;
|
||||
@ -3065,9 +3110,9 @@ static int rtreeUpdate(
|
||||
}else
|
||||
#endif
|
||||
{
|
||||
for(ii=0; ii<nData-4; ii+=2){
|
||||
cell.aCoord[ii].i = sqlite3_value_int(azData[ii+3]);
|
||||
cell.aCoord[ii+1].i = sqlite3_value_int(azData[ii+4]);
|
||||
for(ii=0; ii<nn; ii+=2){
|
||||
cell.aCoord[ii].i = sqlite3_value_int(aData[ii+3]);
|
||||
cell.aCoord[ii+1].i = sqlite3_value_int(aData[ii+4]);
|
||||
if( cell.aCoord[ii].i>cell.aCoord[ii+1].i ){
|
||||
rc = rtreeConstraintError(pRtree, ii+1);
|
||||
goto constraint;
|
||||
@ -3077,10 +3122,10 @@ static int rtreeUpdate(
|
||||
|
||||
/* If a rowid value was supplied, check if it is already present in
|
||||
** the table. If so, the constraint has failed. */
|
||||
if( sqlite3_value_type(azData[2])!=SQLITE_NULL ){
|
||||
cell.iRowid = sqlite3_value_int64(azData[2]);
|
||||
if( sqlite3_value_type(azData[0])==SQLITE_NULL
|
||||
|| sqlite3_value_int64(azData[0])!=cell.iRowid
|
||||
if( sqlite3_value_type(aData[2])!=SQLITE_NULL ){
|
||||
cell.iRowid = sqlite3_value_int64(aData[2]);
|
||||
if( sqlite3_value_type(aData[0])==SQLITE_NULL
|
||||
|| sqlite3_value_int64(aData[0])!=cell.iRowid
|
||||
){
|
||||
int steprc;
|
||||
sqlite3_bind_int64(pRtree->pReadRowid, 1, cell.iRowid);
|
||||
@ -3099,16 +3144,16 @@ static int rtreeUpdate(
|
||||
}
|
||||
}
|
||||
|
||||
/* If azData[0] is not an SQL NULL value, it is the rowid of a
|
||||
/* If aData[0] is not an SQL NULL value, it is the rowid of a
|
||||
** record to delete from the r-tree table. The following block does
|
||||
** just that.
|
||||
*/
|
||||
if( sqlite3_value_type(azData[0])!=SQLITE_NULL ){
|
||||
rc = rtreeDeleteRowid(pRtree, sqlite3_value_int64(azData[0]));
|
||||
if( sqlite3_value_type(aData[0])!=SQLITE_NULL ){
|
||||
rc = rtreeDeleteRowid(pRtree, sqlite3_value_int64(aData[0]));
|
||||
}
|
||||
|
||||
/* If the azData[] array contains more than one element, elements
|
||||
** (azData[2]..azData[argc-1]) contain a new record to insert into
|
||||
/* If the aData[] array contains more than one element, elements
|
||||
** (aData[2]..aData[argc-1]) contain a new record to insert into
|
||||
** the r-tree structure.
|
||||
*/
|
||||
if( rc==SQLITE_OK && nData>1 ){
|
||||
@ -3133,6 +3178,16 @@ static int rtreeUpdate(
|
||||
rc = rc2;
|
||||
}
|
||||
}
|
||||
if( pRtree->nAux ){
|
||||
sqlite3_stmt *pUp = pRtree->pWriteAux;
|
||||
int jj;
|
||||
sqlite3_bind_int64(pUp, 1, *pRowid);
|
||||
for(jj=0; jj<pRtree->nAux; jj++){
|
||||
sqlite3_bind_value(pUp, jj+2, aData[pRtree->nDim2+3+jj]);
|
||||
}
|
||||
sqlite3_step(pUp);
|
||||
rc = sqlite3_reset(pUp);
|
||||
}
|
||||
}
|
||||
|
||||
constraint:
|
||||
@ -3289,18 +3344,18 @@ static int rtreeSqlInit(
|
||||
#define N_STATEMENT 8
|
||||
static const char *azSql[N_STATEMENT] = {
|
||||
/* Write the xxx_node table */
|
||||
"INSERT OR REPLACE INTO '%q'.'%q_node' VALUES(:1, :2)",
|
||||
"DELETE FROM '%q'.'%q_node' WHERE nodeno = :1",
|
||||
"INSERT OR REPLACE INTO '%q'.'%q_node' VALUES(?1, ?2)",
|
||||
"DELETE FROM '%q'.'%q_node' WHERE nodeno = ?1",
|
||||
|
||||
/* Read and write the xxx_rowid table */
|
||||
"SELECT nodeno FROM '%q'.'%q_rowid' WHERE rowid = :1",
|
||||
"INSERT OR REPLACE INTO '%q'.'%q_rowid' VALUES(:1, :2)",
|
||||
"DELETE FROM '%q'.'%q_rowid' WHERE rowid = :1",
|
||||
"SELECT nodeno FROM '%q'.'%q_rowid' WHERE rowid = ?1",
|
||||
"INSERT OR REPLACE INTO '%q'.'%q_rowid' VALUES(?1, ?2)",
|
||||
"DELETE FROM '%q'.'%q_rowid' WHERE rowid = ?1",
|
||||
|
||||
/* Read and write the xxx_parent table */
|
||||
"SELECT parentnode FROM '%q'.'%q_parent' WHERE nodeno = :1",
|
||||
"INSERT OR REPLACE INTO '%q'.'%q_parent' VALUES(:1, :2)",
|
||||
"DELETE FROM '%q'.'%q_parent' WHERE nodeno = :1"
|
||||
"SELECT parentnode FROM '%q'.'%q_parent' WHERE nodeno = ?1",
|
||||
"INSERT OR REPLACE INTO '%q'.'%q_parent' VALUES(?1, ?2)",
|
||||
"DELETE FROM '%q'.'%q_parent' WHERE nodeno = ?1"
|
||||
};
|
||||
sqlite3_stmt **appStmt[N_STATEMENT];
|
||||
int i;
|
||||
@ -3308,14 +3363,25 @@ static int rtreeSqlInit(
|
||||
pRtree->db = db;
|
||||
|
||||
if( isCreate ){
|
||||
char *zCreate = sqlite3_mprintf(
|
||||
"CREATE TABLE \"%w\".\"%w_node\"(nodeno INTEGER PRIMARY KEY, data BLOB);"
|
||||
"CREATE TABLE \"%w\".\"%w_rowid\"(rowid INTEGER PRIMARY KEY, nodeno INTEGER);"
|
||||
"CREATE TABLE \"%w\".\"%w_parent\"(nodeno INTEGER PRIMARY KEY,"
|
||||
" parentnode INTEGER);"
|
||||
"INSERT INTO '%q'.'%q_node' VALUES(1, zeroblob(%d))",
|
||||
zDb, zPrefix, zDb, zPrefix, zDb, zPrefix, zDb, zPrefix, pRtree->iNodeSize
|
||||
);
|
||||
char *zCreate;
|
||||
sqlite3_str *p = sqlite3_str_new(db);
|
||||
int ii;
|
||||
sqlite3_str_appendf(p,
|
||||
"CREATE TABLE \"%w\".\"%w_rowid\"(rowid INTEGER PRIMARY KEY,nodeno",
|
||||
zDb, zPrefix);
|
||||
for(ii=0; ii<pRtree->nAux; ii++){
|
||||
sqlite3_str_appendf(p,",a%d",ii);
|
||||
}
|
||||
sqlite3_str_appendf(p,
|
||||
");CREATE TABLE \"%w\".\"%w_node\"(nodeno INTEGER PRIMARY KEY,data);",
|
||||
zDb, zPrefix);
|
||||
sqlite3_str_appendf(p,
|
||||
"CREATE TABLE \"%w\".\"%w_parent\"(nodeno INTEGER PRIMARY KEY,parentnode);",
|
||||
zDb, zPrefix);
|
||||
sqlite3_str_appendf(p,
|
||||
"INSERT INTO \"%w\".\"%w_node\"VALUES(1,zeroblob(%d))",
|
||||
zDb, zPrefix, pRtree->iNodeSize);
|
||||
zCreate = sqlite3_str_finish(p);
|
||||
if( !zCreate ){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
@ -3337,7 +3403,17 @@ static int rtreeSqlInit(
|
||||
|
||||
rc = rtreeQueryStat1(db, pRtree);
|
||||
for(i=0; i<N_STATEMENT && rc==SQLITE_OK; i++){
|
||||
char *zSql = sqlite3_mprintf(azSql[i], zDb, zPrefix);
|
||||
char *zSql;
|
||||
const char *zFormat;
|
||||
if( i!=3 || pRtree->nAux==0 ){
|
||||
zFormat = azSql[i];
|
||||
}else {
|
||||
/* An UPSERT is very slightly slower than REPLACE, but it is needed
|
||||
** if there are auxiliary columns */
|
||||
zFormat = "INSERT INTO\"%w\".\"%w_rowid\"(rowid,nodeno)VALUES(?1,?2)"
|
||||
"ON CONFLICT(rowid)DO UPDATE SET nodeno=excluded.nodeno";
|
||||
}
|
||||
zSql = sqlite3_mprintf(zFormat, zDb, zPrefix);
|
||||
if( zSql ){
|
||||
rc = sqlite3_prepare_v3(db, zSql, -1, SQLITE_PREPARE_PERSISTENT,
|
||||
appStmt[i], 0);
|
||||
@ -3346,6 +3422,32 @@ static int rtreeSqlInit(
|
||||
}
|
||||
sqlite3_free(zSql);
|
||||
}
|
||||
if( pRtree->nAux ){
|
||||
pRtree->zReadAuxSql = sqlite3_mprintf(
|
||||
"SELECT * FROM \"%w\".\"%w_rowid\" WHERE rowid=?1",
|
||||
zDb, zPrefix);
|
||||
if( pRtree->zReadAuxSql==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
sqlite3_str *p = sqlite3_str_new(db);
|
||||
int ii;
|
||||
char *zSql;
|
||||
sqlite3_str_appendf(p, "UPDATE \"%w\".\"%w_rowid\"SET ", zDb, zPrefix);
|
||||
for(ii=0; ii<pRtree->nAux; ii++){
|
||||
if( ii ) sqlite3_str_append(p, ",", 1);
|
||||
sqlite3_str_appendf(p,"a%d=?%d",ii,ii+2);
|
||||
}
|
||||
sqlite3_str_appendf(p, " WHERE rowid=?1");
|
||||
zSql = sqlite3_str_finish(p);
|
||||
if( zSql==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
rc = sqlite3_prepare_v3(db, zSql, -1, SQLITE_PREPARE_PERSISTENT,
|
||||
&pRtree->pWriteAux, 0);
|
||||
sqlite3_free(zSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
@ -3448,17 +3550,22 @@ static int rtreeInit(
|
||||
int nDb; /* Length of string argv[1] */
|
||||
int nName; /* Length of string argv[2] */
|
||||
int eCoordType = (pAux ? RTREE_COORD_INT32 : RTREE_COORD_REAL32);
|
||||
sqlite3_str *pSql;
|
||||
char *zSql;
|
||||
int ii = 4;
|
||||
int iErr;
|
||||
|
||||
const char *aErrMsg[] = {
|
||||
0, /* 0 */
|
||||
"Wrong number of columns for an rtree table", /* 1 */
|
||||
"Too few columns for an rtree table", /* 2 */
|
||||
"Too many columns for an rtree table" /* 3 */
|
||||
"Too many columns for an rtree table", /* 3 */
|
||||
"Auxiliary rtree columns must be last" /* 4 */
|
||||
};
|
||||
|
||||
int iErr = (argc<6) ? 2 : argc>(RTREE_MAX_DIMENSIONS*2+4) ? 3 : argc%2;
|
||||
if( aErrMsg[iErr] ){
|
||||
*pzErr = sqlite3_mprintf("%s", aErrMsg[iErr]);
|
||||
assert( RTREE_MAX_AUX_COLUMN<256 ); /* Aux columns counted by a u8 */
|
||||
if( argc>RTREE_MAX_AUX_COLUMN+3 ){
|
||||
*pzErr = sqlite3_mprintf("%s", aErrMsg[3]);
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
|
||||
@ -3476,53 +3583,73 @@ static int rtreeInit(
|
||||
pRtree->base.pModule = &rtreeModule;
|
||||
pRtree->zDb = (char *)&pRtree[1];
|
||||
pRtree->zName = &pRtree->zDb[nDb+1];
|
||||
pRtree->nDim = (u8)((argc-4)/2);
|
||||
pRtree->nDim2 = pRtree->nDim*2;
|
||||
pRtree->nBytesPerCell = 8 + pRtree->nDim2*4;
|
||||
pRtree->eCoordType = (u8)eCoordType;
|
||||
memcpy(pRtree->zDb, argv[1], nDb);
|
||||
memcpy(pRtree->zName, argv[2], nName);
|
||||
|
||||
/* Figure out the node size to use. */
|
||||
rc = getNodeSize(db, pRtree, isCreate, pzErr);
|
||||
|
||||
/* Create/Connect to the underlying relational database schema. If
|
||||
** that is successful, call sqlite3_declare_vtab() to configure
|
||||
** the r-tree table schema.
|
||||
*/
|
||||
if( rc==SQLITE_OK ){
|
||||
if( (rc = rtreeSqlInit(pRtree, db, argv[1], argv[2], isCreate)) ){
|
||||
*pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
|
||||
pSql = sqlite3_str_new(db);
|
||||
sqlite3_str_appendf(pSql, "CREATE TABLE x(%s", argv[3]);
|
||||
for(ii=4; ii<argc; ii++){
|
||||
if( argv[ii][0]=='+' ){
|
||||
pRtree->nAux++;
|
||||
sqlite3_str_appendf(pSql, ",%s", argv[ii]+1);
|
||||
}else if( pRtree->nAux>0 ){
|
||||
break;
|
||||
}else{
|
||||
sqlite3_str *pSql = sqlite3_str_new(db);
|
||||
char *zSql;
|
||||
int ii;
|
||||
if( pSql==0 ){
|
||||
zSql = 0;
|
||||
}else{
|
||||
sqlite3_str_appendf(pSql, "CREATE TABLE x(%s", argv[3]);
|
||||
for(ii=4; ii<argc; ii++){
|
||||
sqlite3_str_appendf(pSql, ", %s", argv[ii]);
|
||||
}
|
||||
sqlite3_str_appendf(pSql, ");");
|
||||
zSql = sqlite3_str_finish(pSql);
|
||||
}
|
||||
if( !zSql ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else if( SQLITE_OK!=(rc = sqlite3_declare_vtab(db, zSql)) ){
|
||||
*pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
|
||||
}
|
||||
sqlite3_free(zSql);
|
||||
pRtree->nDim2++;
|
||||
sqlite3_str_appendf(pSql, ",%s", argv[ii]);
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
*ppVtab = (sqlite3_vtab *)pRtree;
|
||||
}else{
|
||||
assert( *ppVtab==0 );
|
||||
assert( pRtree->nBusy==1 );
|
||||
rtreeRelease(pRtree);
|
||||
sqlite3_str_appendf(pSql, ");");
|
||||
zSql = sqlite3_str_finish(pSql);
|
||||
if( !zSql ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else if( ii<argc ){
|
||||
*pzErr = sqlite3_mprintf("%s", aErrMsg[4]);
|
||||
rc = SQLITE_ERROR;
|
||||
}else if( SQLITE_OK!=(rc = sqlite3_declare_vtab(db, zSql)) ){
|
||||
*pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
|
||||
}
|
||||
sqlite3_free(zSql);
|
||||
if( rc ) goto rtreeInit_fail;
|
||||
pRtree->nDim = pRtree->nDim2/2;
|
||||
if( pRtree->nDim<1 ){
|
||||
iErr = 2;
|
||||
}else if( pRtree->nDim2>RTREE_MAX_DIMENSIONS*2 ){
|
||||
iErr = 3;
|
||||
}else if( pRtree->nDim2 % 2 ){
|
||||
iErr = 1;
|
||||
}else{
|
||||
iErr = 0;
|
||||
}
|
||||
if( iErr ){
|
||||
*pzErr = sqlite3_mprintf("%s", aErrMsg[iErr]);
|
||||
goto rtreeInit_fail;
|
||||
}
|
||||
pRtree->nBytesPerCell = 8 + pRtree->nDim2*4;
|
||||
|
||||
/* Figure out the node size to use. */
|
||||
rc = getNodeSize(db, pRtree, isCreate, pzErr);
|
||||
if( rc ) goto rtreeInit_fail;
|
||||
rc = rtreeSqlInit(pRtree, db, argv[1], argv[2], isCreate);
|
||||
if( rc ){
|
||||
*pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
|
||||
goto rtreeInit_fail;
|
||||
}
|
||||
|
||||
*ppVtab = (sqlite3_vtab *)pRtree;
|
||||
return SQLITE_OK;
|
||||
|
||||
rtreeInit_fail:
|
||||
if( rc==SQLITE_OK ) rc = SQLITE_ERROR;
|
||||
assert( *ppVtab==0 );
|
||||
assert( pRtree->nBusy==1 );
|
||||
rtreeRelease(pRtree);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -3751,7 +3878,7 @@ static u8 *rtreeCheckGetNode(RtreeCheck *pCheck, i64 iNode, int *pnNode){
|
||||
** two tables are:
|
||||
**
|
||||
** CREATE TABLE %_parent(nodeno INTEGER PRIMARY KEY, parentnode INTEGER)
|
||||
** CREATE TABLE %_rowid(rowid INTEGER PRIMARY KEY, nodeno 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.
|
||||
@ -3766,8 +3893,8 @@ static void rtreeCheckMapping(
|
||||
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=?"
|
||||
"SELECT parentnode FROM %Q.'%q_parent' WHERE nodeno=?1",
|
||||
"SELECT nodeno FROM %Q.'%q_rowid' WHERE rowid=?1"
|
||||
};
|
||||
|
||||
assert( bLeaf==0 || bLeaf==1 );
|
||||
@ -3951,6 +4078,7 @@ static int rtreeCheckTable(
|
||||
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 */
|
||||
int nAux = 0; /* Number of extra columns. */
|
||||
|
||||
/* Initialize the context object */
|
||||
memset(&check, 0, sizeof(check));
|
||||
@ -3966,11 +4094,21 @@ static int rtreeCheckTable(
|
||||
bEnd = 1;
|
||||
}
|
||||
|
||||
/* Find the number of auxiliary columns */
|
||||
if( check.rc==SQLITE_OK ){
|
||||
pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.'%q_rowid'", zDb, zTab);
|
||||
if( pStmt ){
|
||||
nAux = sqlite3_column_count(pStmt) - 2;
|
||||
sqlite3_finalize(pStmt);
|
||||
}
|
||||
check.rc = SQLITE_OK;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
check.nDim = (sqlite3_column_count(pStmt) - 1 - nAux) / 2;
|
||||
if( check.nDim<1 ){
|
||||
rtreeCheckAppendMsg(&check, "Schema corrupt or not an rtree");
|
||||
}else if( SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
|
@ -609,5 +609,43 @@ do_execsql_test 15.2 {
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
# Test cases for the new auxiliary columns feature
|
||||
#
|
||||
do_catchsql_test 16.100 {
|
||||
CREATE VIRTUAL TABLE t16 USING rtree(id,x0,x1,y0,+aux1,x1);
|
||||
} {1 {Auxiliary rtree columns must be last}}
|
||||
do_test 16.110 {
|
||||
set sql {
|
||||
CREATE VIRTUAL TABLE t16 USING rtree(
|
||||
id, x00, x01, x10, x11, x20, x21, x30, x31, x40, x41
|
||||
}
|
||||
for {set i 12} {$i<=100} {incr i} {
|
||||
append sql ", +a$i"
|
||||
}
|
||||
append sql ");"
|
||||
execsql $sql
|
||||
} {}
|
||||
do_test 16.120 {
|
||||
set sql {
|
||||
CREATE VIRTUAL TABLE t16b USING rtree(
|
||||
id, x00, x01, x10, x11, x20, x21, x30, x31, x40, x41
|
||||
}
|
||||
for {set i 12} {$i<=101} {incr i} {
|
||||
append sql ", +a$i"
|
||||
}
|
||||
append sql ");"
|
||||
catchsql $sql
|
||||
} {1 {Too many columns for an rtree table}}
|
||||
|
||||
do_execsql_test 16.130 {
|
||||
DROP TABLE IF EXISTS rt1;
|
||||
CREATE VIRTUAL TABLE rt1 USING rtree(id, x1, x2, +aux);
|
||||
INSERT INTO rt1 VALUES(1, 1, 2, 'aux1');
|
||||
INSERT INTO rt1 VALUES(2, 2, 3, 'aux2');
|
||||
INSERT INTO rt1 VALUES(3, 3, 4, 'aux3');
|
||||
INSERT INTO rt1 VALUES(4, 4, 5, 'aux4');
|
||||
SELECT * FROM rt1 WHERE id IN (1, 2, 3, 4);
|
||||
} {1 1.0 2.0 aux1 2 2.0 3.0 aux2 3 3.0 4.0 aux3 4 4.0 5.0 aux4}
|
||||
|
||||
expand_all_sql db
|
||||
finish_test
|
||||
|
@ -81,7 +81,7 @@ do_faultsim_test rtree3-2 -faults oom* -prep {
|
||||
do_malloc_test rtree3-3.prep {
|
||||
faultsim_delete_and_reopen
|
||||
execsql {
|
||||
CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2);
|
||||
CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2, +a1, +a2);
|
||||
INSERT INTO rt VALUES(NULL, 3, 5, 7, 9);
|
||||
}
|
||||
faultsim_save_and_close
|
||||
|
@ -178,7 +178,7 @@ do_execsql_test 4.3 {
|
||||
reset_db
|
||||
do_execsql_test 5.1 {
|
||||
CREATE TABLE t1(x PRIMARY KEY, y);
|
||||
CREATE VIRTUAL TABLE rt USING rtree(id, x1, x2);
|
||||
CREATE VIRTUAL TABLE rt USING rtree(id, x1, x2, +d1);
|
||||
|
||||
INSERT INTO t1(x) VALUES(1);
|
||||
INSERT INTO t1(x) SELECT x+1 FROM t1; -- 2
|
||||
@ -192,7 +192,7 @@ do_execsql_test 5.1 {
|
||||
INSERT INTO t1(x) SELECT x+256 FROM t1; -- 512
|
||||
INSERT INTO t1(x) SELECT x+512 FROM t1; --1024
|
||||
|
||||
INSERT INTO rt SELECT x, x, x+1 FROM t1 WHERE x<=5;
|
||||
INSERT INTO rt SELECT x, x, x+1, printf('x%04xy',x) FROM t1 WHERE x<=5;
|
||||
}
|
||||
do_rtree_integrity_test 5.1.1 rt
|
||||
|
||||
|
80
ext/rtree/rtreeH.test
Normal file
80
ext/rtree/rtreeH.test
Normal file
@ -0,0 +1,80 @@
|
||||
# 2018-05-16
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file contains tests for the r-tree module, specifically the
|
||||
# auxiliary column mechanism.
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] rtree_util.tcl]
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !rtree { finish_test ; return }
|
||||
|
||||
do_execsql_test rtreeH-100 {
|
||||
CREATE VIRTUAL TABLE t1 USING rtree(id,x0,x1,y0,y1,+label,+other);
|
||||
INSERT INTO t1(x0,x1,y0,y1,label) VALUES
|
||||
(0,10,0,10,'lower-left corner'),
|
||||
(0,10,90,100,'upper-left corner'),
|
||||
(90,100,0,10,'lower-right corner'),
|
||||
(90,100,90,100,'upper-right corner'),
|
||||
(40,60,40,60,'center'),
|
||||
(0,5,0,100,'left edge'),
|
||||
(95,100,0,100,'right edge'),
|
||||
(0,100,0,5,'bottom edge'),
|
||||
(0,100,95,100,'top edge'),
|
||||
(0,100,0,100,'the whole thing'),
|
||||
(0,50,0,100,'left half'),
|
||||
(51,100,0,100,'right half'),
|
||||
(0,100,0,50,'bottom half'),
|
||||
(0,100,51,100,'top half');
|
||||
} {}
|
||||
do_execsql_test rtreeH-101 {
|
||||
SELECT * FROM t1_rowid ORDER BY rowid
|
||||
} {1 1 {lower-left corner} {} 2 1 {upper-left corner} {} 3 1 {lower-right corner} {} 4 1 {upper-right corner} {} 5 1 center {} 6 1 {left edge} {} 7 1 {right edge} {} 8 1 {bottom edge} {} 9 1 {top edge} {} 10 1 {the whole thing} {} 11 1 {left half} {} 12 1 {right half} {} 13 1 {bottom half} {} 14 1 {top half} {}}
|
||||
|
||||
do_execsql_test rtreeH-102 {
|
||||
SELECT * FROM t1 WHERE rowid=5;
|
||||
} {5 40.0 60.0 40.0 60.0 center {}}
|
||||
do_execsql_test rtreeH-103 {
|
||||
SELECT * FROM t1 WHERE label='center';
|
||||
} {5 40.0 60.0 40.0 60.0 center {}}
|
||||
|
||||
do_rtree_integrity_test rtreeH-110 t1
|
||||
|
||||
do_execsql_test rtreeH-120 {
|
||||
SELECT label FROM t1 WHERE x1<=50 ORDER BY id
|
||||
} {{lower-left corner} {upper-left corner} {left edge} {left half}}
|
||||
do_execsql_test rtreeH-121 {
|
||||
SELECT label FROM t1 WHERE x1<=50 AND label NOT LIKE '%corner%' ORDER BY id
|
||||
} {{left edge} {left half}}
|
||||
|
||||
do_execsql_test rtreeH-200 {
|
||||
WITH RECURSIVE
|
||||
c1(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM c1 WHERE x<99),
|
||||
c2(y) AS (VALUES(0) UNION ALL SELECT y+1 FROM c2 WHERE y<99)
|
||||
INSERT INTO t1(id, x0,x1,y0,y1,label)
|
||||
SELECT 1000+x+y*100, x, x+1, y, y+1, printf('box-%d,%d',x,y) FROM c1, c2;
|
||||
} {}
|
||||
|
||||
do_execsql_test rtreeH-210 {
|
||||
SELECT label FROM t1 WHERE x0>=48 AND x1<=50 AND y0>=48 AND y1<=50
|
||||
ORDER BY id;
|
||||
} {box-48,48 box-49,48 box-48,49 box-49,49}
|
||||
|
||||
do_execsql_test rtreeH-300 {
|
||||
UPDATE t1 SET label='x'||label
|
||||
WHERE x0>=49 AND x1<=50 AND y0>=49 AND y1<=50;
|
||||
SELECT label FROM t1 WHERE x0>=48 AND x1<=50 AND y0>=48 AND y1<=50
|
||||
ORDER BY id;
|
||||
} {box-48,48 box-49,48 box-48,49 xbox-49,49}
|
||||
|
||||
|
||||
finish_test
|
20
manifest
20
manifest
@ -1,5 +1,5 @@
|
||||
C In\sthe\sCLI,\sdetect\sand\sreport\serrors\son\ssqlite3_close().\s\sClear\sglobal\nvariables\sprior\sto\sexit\sto\sso\sthat\svalgrind\scan\sbetter\sdetect\sresource\nleaks.
|
||||
D 2018-05-18T17:11:50.887
|
||||
C Add\ssupport\sfor\sauxiliary\scolumns\sto\sthe\srtree\sextension.
|
||||
D 2018-05-18T17:58:33.081
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
F Makefile.in bfc40f350586923e0419d2ea4b559c37ec10ee4b6e210e08c14401f8e340f0da
|
||||
@ -355,11 +355,11 @@ F ext/repair/test/checkfreelist01.test 3e8aa6aeb4007680c94a8d07b41c339aa635cc782
|
||||
F ext/repair/test/checkindex01.test 6945d0ffc0c1dc993b2ce88036b26e0f5d6fcc65da70fc9df27c2647bb358b0f
|
||||
F ext/repair/test/test.tcl 686d76d888dffd021f64260abf29a55c57b2cedfa7fc69150b42b1d6119aac3c
|
||||
F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
|
||||
F ext/rtree/rtree.c 11dc450eec4c86a9b1eee104e478f9556382622a6a7e8080a478567ea975ea69
|
||||
F ext/rtree/rtree.c 148ae743198ca9fe1dfdf2fed74e5c3b0e69330e5b71ddc0d9df9993324dcedc
|
||||
F ext/rtree/rtree.h 4a690463901cb5e6127cf05eb8e642f127012fd5003830dbc974eca5802d9412
|
||||
F ext/rtree/rtree1.test 47e2095bebea6813754fd7afa6a20e2b7b4ebcd5cb7dbcb6932b6c9f86bbf972
|
||||
F ext/rtree/rtree1.test 587889855b26fb8637417921b0709ca843cb6d36db94a882371fd8b7dcc3aa0b
|
||||
F ext/rtree/rtree2.test 5f25b01acd03470067a2d52783b2eb0a50bf836803d4342d20ca39e541220fe2
|
||||
F ext/rtree/rtree3.test 2cafe8265d1ff28f206fce88d114f208349df482
|
||||
F ext/rtree/rtree3.test 4ee5d7df86040efe3d8d84f141f2962a7745452200a7cba1db06f86d97050499
|
||||
F ext/rtree/rtree4.test 304de65d484540111b896827e4261815e5dca4ce28eeecd58be648cd73452c4b
|
||||
F ext/rtree/rtree5.test 49c9041d713d54560b315c2c7ef7207ee287eba1b20f8266968a06f2e55d3142
|
||||
F ext/rtree/rtree6.test 593e0d36510d5ac1d1fb39b018274ff17604fe8fdca8cf1f8e16559cea1477f4
|
||||
@ -368,11 +368,12 @@ F ext/rtree/rtree8.test 649f5a37ec656028a4a32674b9b1183104285a7625a09d2a8f52a1ce
|
||||
F ext/rtree/rtree9.test c646f12c8c1c68ef015c6c043d86a0c42488e2e68ed1bb1b0771a7ca246cbabf
|
||||
F ext/rtree/rtreeA.test 20623ca337ca3bd7e008cc9fb49e44dbe97f1a80b238e10a12bb4afcd0da3776
|
||||
F ext/rtree/rtreeB.test 4cec297f8e5c588654bbf3c6ed0903f10612be8a2878055dd25faf8c71758bc9
|
||||
F ext/rtree/rtreeC.test 55e40c4bd9735d9944280f0e664f39374e71bcd9cd3fe4e82786d20b48017fb5
|
||||
F ext/rtree/rtreeC.test 128928549d22b65c381ab1366760d08703cd75e34f6a7a506ece38f9330b7282
|
||||
F ext/rtree/rtreeD.test fe46aa7f012e137bd58294409b16c0d43976c3bb92c8f710481e577c4a1100dc
|
||||
F ext/rtree/rtreeE.test e65d3fc625da1800b412fc8785817327d43ccfec5f5973912d8c9e471928caa9
|
||||
F ext/rtree/rtreeF.test 81ffa7ef51c4e4618d497a57328c265bf576990c7070633b623b23cd450ed331
|
||||
F ext/rtree/rtreeG.test 1b9ca6e3effb48f4161edaa463ddeaa8fca4b2526d084f9cbf5dbe4e0184939c
|
||||
F ext/rtree/rtreeH.test aa08cc4fa8005b4c67446c7110205055b4d6da90e760e6f44b82dfa4cdf8d87a
|
||||
F ext/rtree/rtree_perf.tcl 6c18c1f23cd48e0f948930c98dfdd37dfccb5195
|
||||
F ext/rtree/rtree_util.tcl db734b4c5e75fed6acc56d9701f2235345acfdec750b5fc7b587936f5f6bceed
|
||||
F ext/rtree/rtreecheck.test 4d29103d1e16fcbf90135d1c637b833688492b063b2971dfb5dc6ba76555cfee
|
||||
@ -1728,7 +1729,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P 5139ea62a8a6c6dc6558c337de39bcbadd26f6515742263387be03c862c78cf0
|
||||
R b8765ab2a09ad6fd3afc8344422cf1a3
|
||||
P e3b2e0a078b82ac6cd3c3312e8ac0983c1375e1052f1e324476d2f8d1b227c30 a350040a3bb962823f92908fb31cade52baf13eab90ef608ca3a8349e4c28c9d
|
||||
R c67fcd3e8c429daa0c0bf5aeb356dbf0
|
||||
T +closed a350040a3bb962823f92908fb31cade52baf13eab90ef608ca3a8349e4c28c9d
|
||||
U drh
|
||||
Z d089a713f2ba352bce4662bcdd3c7d90
|
||||
Z 6ceb3a28c710c648a7e5cb8343e1c6ba
|
||||
|
@ -1 +1 @@
|
||||
e3b2e0a078b82ac6cd3c3312e8ac0983c1375e1052f1e324476d2f8d1b227c30
|
||||
c6071ac99cfa4b6272ac4d739fc61a85acb544f6c1c2ae67b31e92aadcc995bd
|
Loading…
Reference in New Issue
Block a user