Added the transitive_closure, ieee754, and amatch extensions.

FossilOrigin-Name: 84018099c8715b982cd24ce9221f93c7379e8c08
This commit is contained in:
drh 2013-04-25 16:42:55 +00:00
parent b7045ab2ed
commit 8416fc7fc7
11 changed files with 2815 additions and 13 deletions

View File

@ -388,7 +388,10 @@ TESTSRC = \
# Statically linked extensions
#
TESTSRC += \
$(TOP)/ext/misc/amatch.c \
$(TOP)/ext/misc/closure.c \
$(TOP)/ext/misc/fuzzer.c \
$(TOP)/ext/misc/ieee754.c \
$(TOP)/ext/misc/regexp.c \
$(TOP)/ext/misc/spellfix.c \
$(TOP)/ext/misc/wholenumber.c

View File

@ -708,7 +708,10 @@ TESTSRC = \
# Statically linked extensions
#
TESTEXT = \
$(TOP)\ext\misc\amatch.c \
$(TOP)\ext\misc\closure.c \
$(TOP)\ext\misc\fuzzer.c \
$(TOP)\ext\misc\ieee754.c \
$(TOP)\ext\misc\regexp.c \
$(TOP)\ext\misc\spellfix.c \
$(TOP)\ext\misc\wholenumber.c

1477
ext/misc/amatch.c Normal file

File diff suppressed because it is too large Load Diff

942
ext/misc/closure.c Normal file
View File

@ -0,0 +1,942 @@
/*
** 2013-04-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 code for a virtual table that finds the transitive
** closure of a parent/child relationship in a real table. The virtual
** table is called "transitive_closure".
**
** A transitive_closure virtual table is created like this:
**
** CREATE VIRTUAL TABLE x USING transitive_closure(
** tablename=<tablename>, -- T
** idcolumn=<columnname>, -- X
** parentcolumn=<columnname> -- P
** );
**
** When it is created, the new transitive_closure table may be supplied
** with default values for the name of a table T and columns T.X and T.P.
** The T.X and T.P columns must contain integers. The ideal case is for
** T.X to be the INTEGER PRIMARY KEY. The T.P column should reference
** the T.X column. The row referenced by T.P is the parent of the current row.
**
** The tablename, idcolumn, and parentcolumn supplied by the CREATE VIRTUAL
** TABLE statement may be overridden in individual queries by including
** terms like tablename='newtable', idcolumn='id2', or
** parentcolumn='parent3' in the WHERE clause of the query.
**
** For efficiency, it is essential that there be an index on the P column:
**
** CREATE Tidx1 ON T(P)
**
** Suppose a specific instance of the closure table is as follows:
**
** CREATE VIRTUAL TABLE ct1 USING transitive_closure(
** tablename='group',
** idcolumn='groupId',
** parentcolumn='parentId'
** );
**
** Such an instance of the transitive_closure virtual table would be
** appropriate for walking a tree defined using a table like this, for example:
**
** CREATE TABLE group(
** groupId INTEGER PRIMARY KEY,
** parentId INTEGER REFERENCES group
** );
** CREATE INDEX group_idx1 ON group(parentId);
**
** The group table above would presumably have other application-specific
** fields. The key point here is that rows of the group table form a
** tree. The purpose of the ct1 virtual table is to easily extract
** branches of that tree.
**
** Once it has been created, the ct1 virtual table can be queried
** as follows:
**
** SELECT * FROM element
** WHERE element.groupId IN (SELECT id FROM ct1 WHERE root=?1);
**
** The above query will return all elements that are part of group ?1
** or children of group ?1 or grand-children of ?1 and so forth for all
** descendents of group ?1. The same query can be formulated as a join:
**
** SELECT element.* FROM element, ct1
** WHERE element.groupid=ct1.id
** AND ct1.root=?1;
**
** The depth of the transitive_closure (the number of generations of
** parent/child relations to follow) can be limited by setting "depth"
** column in the WHERE clause. So, for example, the following query
** finds only children and grandchildren but no further descendents:
**
** SELECT element.* FROM element, ct1
** WHERE element.groupid=ct1.id
** AND ct1.root=?1
** AND ct1.depth<=2;
**
** The "ct1.depth<=2" term could be a strict equality "ct1.depth=2" in
** order to find only the grandchildren of ?1, not ?1 itself or the
** children of ?1.
**
** The root=?1 term must be supplied in WHERE clause or else the query
** of the ct1 virtual table will return an empty set. The tablename,
** idcolumn, and parentcolumn attributes can be overridden in the WHERE
** clause if desired. So, for example, the ct1 table could be repurposed
** to find ancestors rather than descendents by inverting the roles of
** the idcolumn and parentcolumn:
**
** SELECT element.* FROM element, ct1
** WHERE element.groupid=ct1.id
** AND ct1.root=?1
** AND ct1.idcolumn='parentId'
** AND ct1.parentcolumn='groupId';
**
** Multiple calls to ct1 could be combined. For example, the following
** query finds all elements that "cousins" of groupId ?1. That is to say
** elements where the groupId is a grandchild of the grandparent of ?1.
** (This definition of "cousins" also includes siblings and self.)
**
** SELECT element.* FROM element, ct1
** WHERE element.groupId=ct1.id
** AND ct1.depth=2
** AND ct1.root IN (SELECT id FROM ct1
** WHERE root=?1
** AND depth=2
** AND idcolumn='parentId'
** AND parentcolumn='groupId');
**
** In our example, the group.groupId column is unique and thus the
** subquery will return exactly one row. For that reason, the IN
** operator could be replaced by "=" to get the same result. But
** in the general case where the idcolumn is not unique, an IN operator
** would be required for this kind of query.
**
** Note that because the tablename, idcolumn, and parentcolumn can
** all be specified in the query, it is possible for an application
** to define a single transitive_closure virtual table for use on lots
** of different hierarchy tables. One might say:
**
** CREATE VIRTUAL TABLE temp.closure USING transitive_closure;
**
** As each database connection is being opened. Then the application
** would always have a "closure" virtual table handy to use for querying.
**
** SELECT element.* FROM element, closure
** WHERE element.groupid=ct1.id
** AND closure.root=?1
** AND closure.tablename='group'
** AND closure.idname='groupId'
** AND closure.parentname='parentId';
**
** See the documentation at http://www.sqlite.org/loadext.html for information
** on how to compile and use loadable extensions such as this one.
*/
#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <ctype.h>
/*
** Forward declaration of objects used by this implementation
*/
typedef struct closure_vtab closure_vtab;
typedef struct closure_cursor closure_cursor;
typedef struct closure_queue closure_queue;
typedef struct closure_avl closure_avl;
/*****************************************************************************
** AVL Tree implementation
*/
/*
** Objects that want to be members of the AVL tree should embedded an
** instance of this structure.
*/
struct closure_avl {
sqlite3_int64 id; /* Id of this entry in the table */
int iGeneration; /* Which generation is this entry part of */
closure_avl *pList; /* A linked list of nodes */
closure_avl *pBefore; /* Other elements less than id */
closure_avl *pAfter; /* Other elements greater than id */
closure_avl *pUp; /* Parent element */
short int height; /* Height of this node. Leaf==1 */
short int imbalance; /* Height difference between pBefore and pAfter */
};
/* Recompute the closure_avl.height and closure_avl.imbalance fields for p.
** Assume that the children of p have correct heights.
*/
static void closureAvlRecomputeHeight(closure_avl *p){
short int hBefore = p->pBefore ? p->pBefore->height : 0;
short int hAfter = p->pAfter ? p->pAfter->height : 0;
p->imbalance = hBefore - hAfter; /* -: pAfter higher. +: pBefore higher */
p->height = (hBefore>hAfter ? hBefore : hAfter)+1;
}
/*
** P B
** / \ / \
** B Z ==> X P
** / \ / \
** X Y Y Z
**
*/
static closure_avl *closureAvlRotateBefore(closure_avl *pP){
closure_avl *pB = pP->pBefore;
closure_avl *pY = pB->pAfter;
pB->pUp = pP->pUp;
pB->pAfter = pP;
pP->pUp = pB;
pP->pBefore = pY;
if( pY ) pY->pUp = pP;
closureAvlRecomputeHeight(pP);
closureAvlRecomputeHeight(pB);
return pB;
}
/*
** P A
** / \ / \
** X A ==> P Z
** / \ / \
** Y Z X Y
**
*/
static closure_avl *closureAvlRotateAfter(closure_avl *pP){
closure_avl *pA = pP->pAfter;
closure_avl *pY = pA->pBefore;
pA->pUp = pP->pUp;
pA->pBefore = pP;
pP->pUp = pA;
pP->pAfter = pY;
if( pY ) pY->pUp = pP;
closureAvlRecomputeHeight(pP);
closureAvlRecomputeHeight(pA);
return pA;
}
/*
** Return a pointer to the pBefore or pAfter pointer in the parent
** of p that points to p. Or if p is the root node, return pp.
*/
static closure_avl **closureAvlFromPtr(closure_avl *p, closure_avl **pp){
closure_avl *pUp = p->pUp;
if( pUp==0 ) return pp;
if( pUp->pAfter==p ) return &pUp->pAfter;
return &pUp->pBefore;
}
/*
** Rebalance all nodes starting with p and working up to the root.
** Return the new root.
*/
static closure_avl *closureAvlBalance(closure_avl *p){
closure_avl *pTop = p;
closure_avl **pp;
while( p ){
closureAvlRecomputeHeight(p);
if( p->imbalance>=2 ){
closure_avl *pB = p->pBefore;
if( pB->imbalance<0 ) p->pBefore = closureAvlRotateAfter(pB);
pp = closureAvlFromPtr(p,&p);
p = *pp = closureAvlRotateBefore(p);
}else if( p->imbalance<=(-2) ){
closure_avl *pA = p->pAfter;
if( pA->imbalance>0 ) p->pAfter = closureAvlRotateBefore(pA);
pp = closureAvlFromPtr(p,&p);
p = *pp = closureAvlRotateAfter(p);
}
pTop = p;
p = p->pUp;
}
return pTop;
}
/* Search the tree rooted at p for an entry with id. Return a pointer
** to the entry or return NULL.
*/
static closure_avl *closureAvlSearch(closure_avl *p, sqlite3_int64 id){
while( p && id!=p->id ){
p = (id<p->id) ? p->pBefore : p->pAfter;
}
return p;
}
/* Find the first node (the one with the smallest key).
*/
static closure_avl *closureAvlFirst(closure_avl *p){
if( p ) while( p->pBefore ) p = p->pBefore;
return p;
}
/* Return the node with the next larger key after p.
*/
closure_avl *closureAvlNext(closure_avl *p){
closure_avl *pPrev = 0;
while( p && p->pAfter==pPrev ){
pPrev = p;
p = p->pUp;
}
if( p && pPrev==0 ){
p = closureAvlFirst(p->pAfter);
}
return p;
}
/* Insert a new node pNew. Return NULL on success. If the key is not
** unique, then do not perform the insert but instead leave pNew unchanged
** and return a pointer to an existing node with the same key.
*/
static closure_avl *closureAvlInsert(
closure_avl **ppHead, /* Head of the tree */
closure_avl *pNew /* New node to be inserted */
){
closure_avl *p = *ppHead;
if( p==0 ){
p = pNew;
pNew->pUp = 0;
}else{
while( p ){
if( pNew->id<p->id ){
if( p->pBefore ){
p = p->pBefore;
}else{
p->pBefore = pNew;
pNew->pUp = p;
break;
}
}else if( pNew->id>p->id ){
if( p->pAfter ){
p = p->pAfter;
}else{
p->pAfter = pNew;
pNew->pUp = p;
break;
}
}else{
return p;
}
}
}
pNew->pBefore = 0;
pNew->pAfter = 0;
pNew->height = 1;
pNew->imbalance = 0;
*ppHead = closureAvlBalance(p);
return 0;
}
/* Walk the tree can call xDestroy on each node
*/
static void closureAvlDestroy(closure_avl *p, void (*xDestroy)(closure_avl*)){
if( p ){
closureAvlDestroy(p->pBefore, xDestroy);
closureAvlDestroy(p->pAfter, xDestroy);
xDestroy(p);
}
}
/*
** End of the AVL Tree implementation
******************************************************************************/
/*
** A closure virtual-table object
*/
struct closure_vtab {
sqlite3_vtab base; /* Base class - must be first */
char *zDb; /* Name of database. (ex: "main") */
char *zSelf; /* Name of this virtual table */
char *zTableName; /* Name of table holding parent/child relation */
char *zIdColumn; /* Name of ID column of zTableName */
char *zParentColumn; /* Name of PARENT column in zTableName */
sqlite3 *db; /* The database connection */
int nCursor; /* Number of pending cursors */
};
/* A closure cursor object */
struct closure_cursor {
sqlite3_vtab_cursor base; /* Base class - must be first */
closure_vtab *pVtab; /* The virtual table this cursor belongs to */
char *zTableName; /* Name of table holding parent/child relation */
char *zIdColumn; /* Name of ID column of zTableName */
char *zParentColumn; /* Name of PARENT column in zTableName */
closure_avl *pCurrent; /* Current element of output */
closure_avl *pClosure; /* The complete closure tree */
};
/* A queue of AVL nodes */
struct closure_queue {
closure_avl *pFirst; /* Oldest node on the queue */
closure_avl *pLast; /* Youngest node on the queue */
};
/*
** Add a node to the end of the queue
*/
static void queuePush(closure_queue *pQueue, closure_avl *pNode){
pNode->pList = 0;
if( pQueue->pLast ){
pQueue->pLast->pList = pNode;
}else{
pQueue->pFirst = pNode;
}
pQueue->pLast = pNode;
}
/*
** Extract the oldest element (the front element) from the queue.
*/
static closure_avl *queuePull(closure_queue *pQueue){
closure_avl *p = pQueue->pFirst;
if( p ){
pQueue->pFirst = p->pList;
if( pQueue->pFirst==0 ) pQueue->pLast = 0;
}
return p;
}
/*
** This function converts an SQL quoted string into an unquoted string
** and returns a pointer to a buffer allocated using sqlite3_malloc()
** containing the result. The caller should eventually free this buffer
** using sqlite3_free.
**
** Examples:
**
** "abc" becomes abc
** 'xyz' becomes xyz
** [pqr] becomes pqr
** `mno` becomes mno
*/
static char *closureDequote(const char *zIn){
int nIn; /* Size of input string, in bytes */
char *zOut; /* Output (dequoted) string */
nIn = (int)strlen(zIn);
zOut = sqlite3_malloc(nIn+1);
if( zOut ){
char q = zIn[0]; /* Quote character (if any ) */
if( q!='[' && q!= '\'' && q!='"' && q!='`' ){
memcpy(zOut, zIn, nIn+1);
}else{
int iOut = 0; /* Index of next byte to write to output */
int iIn; /* Index of next byte to read from input */
if( q=='[' ) q = ']';
for(iIn=1; iIn<nIn; iIn++){
if( zIn[iIn]==q ) iIn++;
zOut[iOut++] = zIn[iIn];
}
}
assert( (int)strlen(zOut)<=nIn );
}
return zOut;
}
/*
** Deallocate an closure_vtab object
*/
static void closureFree(closure_vtab *p){
if( p ){
sqlite3_free(p->zDb);
sqlite3_free(p->zSelf);
sqlite3_free(p->zTableName);
sqlite3_free(p->zIdColumn);
sqlite3_free(p->zParentColumn);
memset(p, 0, sizeof(*p));
sqlite3_free(p);
}
}
/*
** xDisconnect/xDestroy method for the closure module.
*/
static int closureDisconnect(sqlite3_vtab *pVtab){
closure_vtab *p = (closure_vtab*)pVtab;
assert( p->nCursor==0 );
closureFree(p);
return SQLITE_OK;
}
/*
** Check to see if the argument is of the form:
**
** KEY = VALUE
**
** If it is, return a pointer to the first character of VALUE.
** If not, return NULL. Spaces around the = are ignored.
*/
static const char *closureValueOfKey(const char *zKey, const char *zStr){
int nKey = (int)strlen(zKey);
int nStr = (int)strlen(zStr);
int i;
if( nStr<nKey+1 ) return 0;
if( memcmp(zStr, zKey, nKey)!=0 ) return 0;
for(i=nKey; isspace(zStr[i]); i++){}
if( zStr[i]!='=' ) return 0;
i++;
while( isspace(zStr[i]) ){ i++; }
return zStr+i;
}
/*
** xConnect/xCreate method for the closure module. Arguments are:
**
** argv[0] -> module name ("approximate_match")
** argv[1] -> database name
** argv[2] -> table name
** argv[3...] -> arguments
*/
static int closureConnect(
sqlite3 *db,
void *pAux,
int argc, const char *const*argv,
sqlite3_vtab **ppVtab,
char **pzErr
){
int rc = SQLITE_OK; /* Return code */
closure_vtab *pNew = 0; /* New virtual table */
const char *zDb = argv[1];
const char *zVal;
int i;
(void)pAux;
*ppVtab = 0;
pNew = sqlite3_malloc( sizeof(*pNew) );
if( pNew==0 ) return SQLITE_NOMEM;
rc = SQLITE_NOMEM;
memset(pNew, 0, sizeof(*pNew));
pNew->db = db;
pNew->zDb = sqlite3_mprintf("%s", zDb);
if( pNew->zDb==0 ) goto closureConnectError;
pNew->zSelf = sqlite3_mprintf("%s", argv[2]);
if( pNew->zSelf==0 ) goto closureConnectError;
for(i=3; i<argc; i++){
zVal = closureValueOfKey("tablename", argv[i]);
if( zVal ){
sqlite3_free(pNew->zTableName);
pNew->zTableName = closureDequote(zVal);
if( pNew->zTableName==0 ) goto closureConnectError;
continue;
}
zVal = closureValueOfKey("idcolumn", argv[i]);
if( zVal ){
sqlite3_free(pNew->zIdColumn);
pNew->zIdColumn = closureDequote(zVal);
if( pNew->zIdColumn==0 ) goto closureConnectError;
continue;
}
zVal = closureValueOfKey("parentcolumn", argv[i]);
if( zVal ){
sqlite3_free(pNew->zParentColumn);
pNew->zParentColumn = closureDequote(zVal);
if( pNew->zParentColumn==0 ) goto closureConnectError;
continue;
}
*pzErr = sqlite3_mprintf("unrecognized argument: [%s]\n", argv[i]);
closureFree(pNew);
*ppVtab = 0;
return SQLITE_ERROR;
}
rc = sqlite3_declare_vtab(db,
"CREATE TABLE x(id,depth,root HIDDEN,tablename HIDDEN,"
"idcolumn HIDDEN,parentcolumn HIDDEN)"
);
#define CLOSURE_COL_ID 0
#define CLOSURE_COL_DEPTH 1
#define CLOSURE_COL_ROOT 2
#define CLOSURE_COL_TABLENAME 3
#define CLOSURE_COL_IDCOLUMN 4
#define CLOSURE_COL_PARENTCOLUMN 5
if( rc!=SQLITE_OK ){
closureFree(pNew);
}
*ppVtab = &pNew->base;
return rc;
closureConnectError:
closureFree(pNew);
return rc;
}
/*
** Open a new closure cursor.
*/
static int closureOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
closure_vtab *p = (closure_vtab*)pVTab;
closure_cursor *pCur;
pCur = sqlite3_malloc( sizeof(*pCur) );
if( pCur==0 ) return SQLITE_NOMEM;
memset(pCur, 0, sizeof(*pCur));
pCur->pVtab = p;
*ppCursor = &pCur->base;
p->nCursor++;
return SQLITE_OK;
}
/*
** Free up all the memory allocated by a cursor. Set it rLimit to 0
** to indicate that it is at EOF.
*/
static void closureClearCursor(closure_cursor *pCur){
closureAvlDestroy(pCur->pClosure, (void(*)(closure_avl*))sqlite3_free);
sqlite3_free(pCur->zTableName);
sqlite3_free(pCur->zIdColumn);
sqlite3_free(pCur->zParentColumn);
pCur->zTableName = 0;
pCur->zIdColumn = 0;
pCur->zParentColumn = 0;
pCur->pCurrent = 0;
pCur->pClosure = 0;
}
/*
** Close a closure cursor.
*/
static int closureClose(sqlite3_vtab_cursor *cur){
closure_cursor *pCur = (closure_cursor *)cur;
closureClearCursor(pCur);
pCur->pVtab->nCursor--;
sqlite3_free(pCur);
return SQLITE_OK;
}
/*
** Advance a cursor to its next row of output
*/
static int closureNext(sqlite3_vtab_cursor *cur){
closure_cursor *pCur = (closure_cursor*)cur;
pCur->pCurrent = closureAvlNext(pCur->pCurrent);
return SQLITE_OK;
}
/*
** Allocate and insert a node
*/
static int closureInsertNode(
closure_queue *pQueue, /* Add new node to this queue */
closure_cursor *pCur, /* The cursor into which to add the node */
sqlite3_int64 id, /* The node ID */
int iGeneration /* The generation number for this node */
){
closure_avl *pNew = sqlite3_malloc( sizeof(*pNew) );
if( pNew==0 ) return SQLITE_NOMEM;
memset(pNew, 0, sizeof(*pNew));
pNew->id = id;
pNew->iGeneration = iGeneration;
closureAvlInsert(&pCur->pClosure, pNew);
queuePush(pQueue, pNew);
return SQLITE_OK;
}
/*
** Called to "rewind" a cursor back to the beginning so that
** it starts its output over again. Always called at least once
** prior to any closureColumn, closureRowid, or closureEof call.
**
** This routine actually computes the closure.
**
** See the comment at the beginning of closureBestIndex() for a
** description of the meaning of idxNum. The idxStr parameter is
** not used.
*/
static int closureFilter(
sqlite3_vtab_cursor *pVtabCursor,
int idxNum, const char *idxStr,
int argc, sqlite3_value **argv
){
closure_cursor *pCur = (closure_cursor *)pVtabCursor;
closure_vtab *pVtab = pCur->pVtab;
sqlite3_int64 iRoot;
int mxGen = 999999999;
char *zSql;
sqlite3_stmt *pStmt;
closure_avl *pAvl;
int rc = SQLITE_OK;
const char *zTableName = pVtab->zTableName;
const char *zIdColumn = pVtab->zIdColumn;
const char *zParentColumn = pVtab->zParentColumn;
closure_queue sQueue;
(void)idxStr; /* Unused parameter */
(void)argc; /* Unused parameter */
closureClearCursor(pCur);
memset(&sQueue, 0, sizeof(sQueue));
if( (idxNum & 1)==0 ){
/* No root=$root in the WHERE clause. Return an empty set */
return SQLITE_OK;
}
iRoot = sqlite3_value_int64(argv[0]);
if( (idxNum & 0x000f0)!=0 ){
mxGen = sqlite3_value_int(argv[(idxNum>>4)&0x0f]);
if( (idxNum & 0x00002)!=0 ) mxGen--;
}
if( (idxNum & 0x00f00)!=0 ){
zTableName = (const char*)sqlite3_value_text(argv[(idxNum>>8)&0x0f]);
pCur->zTableName = sqlite3_mprintf("%s", zTableName);
}
if( (idxNum & 0x0f000)!=0 ){
zIdColumn = (const char*)sqlite3_value_text(argv[(idxNum>>12)&0x0f]);
pCur->zIdColumn = sqlite3_mprintf("%s", zIdColumn);
}
if( (idxNum & 0x0f0000)!=0 ){
zParentColumn = (const char*)sqlite3_value_text(argv[(idxNum>>16)&0x0f]);
pCur->zParentColumn = sqlite3_mprintf("%s", zParentColumn);
}
zSql = sqlite3_mprintf(
"SELECT \"%w\".\"%w\" FROM \"%w\" WHERE \"%w\".\"%w\"=?1",
zTableName, zIdColumn, zTableName, zTableName, zParentColumn);
if( zSql==0 ){
return SQLITE_NOMEM;
}else{
rc = sqlite3_prepare_v2(pVtab->db, zSql, -1, &pStmt, 0);
sqlite3_free(zSql);
if( rc ){
sqlite3_free(pVtab->base.zErrMsg);
pVtab->base.zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pVtab->db));
return rc;
}
}
if( rc==SQLITE_OK ){
rc = closureInsertNode(&sQueue, pCur, iRoot, 0);
}
while( (pAvl = queuePull(&sQueue))!=0 ){
if( pAvl->iGeneration>=mxGen ) continue;
sqlite3_bind_int64(pStmt, 1, pAvl->id);
while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
if( sqlite3_column_type(pStmt,0)==SQLITE_INTEGER ){
sqlite3_int64 iNew = sqlite3_column_int64(pStmt, 0);
if( closureAvlSearch(pCur->pClosure, iNew)==0 ){
rc = closureInsertNode(&sQueue, pCur, iNew, pAvl->iGeneration+1);
}
}
}
sqlite3_reset(pStmt);
}
sqlite3_finalize(pStmt);
if( rc==SQLITE_OK ){
pCur->pCurrent = closureAvlFirst(pCur->pClosure);
}
return rc;
}
/*
** Only the word and distance columns have values. All other columns
** return NULL
*/
static int closureColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
closure_cursor *pCur = (closure_cursor*)cur;
switch( i ){
case CLOSURE_COL_ID: {
sqlite3_result_int64(ctx, pCur->pCurrent->id);
break;
}
case CLOSURE_COL_DEPTH: {
sqlite3_result_int(ctx, pCur->pCurrent->iGeneration);
break;
}
case CLOSURE_COL_ROOT: {
sqlite3_result_null(ctx);
break;
}
case CLOSURE_COL_TABLENAME: {
sqlite3_result_text(ctx,
pCur->zTableName ? pCur->zTableName : pCur->pVtab->zTableName,
-1, SQLITE_TRANSIENT);
break;
}
case CLOSURE_COL_IDCOLUMN: {
sqlite3_result_text(ctx,
pCur->zIdColumn ? pCur->zIdColumn : pCur->pVtab->zIdColumn,
-1, SQLITE_TRANSIENT);
break;
}
case CLOSURE_COL_PARENTCOLUMN: {
sqlite3_result_text(ctx,
pCur->zParentColumn ? pCur->zParentColumn : pCur->pVtab->zParentColumn,
-1, SQLITE_TRANSIENT);
break;
}
}
return SQLITE_OK;
}
/*
** The rowid. For the closure table, this is the same as the "id" column.
*/
static int closureRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
closure_cursor *pCur = (closure_cursor*)cur;
*pRowid = pCur->pCurrent->id;
return SQLITE_OK;
}
/*
** EOF indicator
*/
static int closureEof(sqlite3_vtab_cursor *cur){
closure_cursor *pCur = (closure_cursor*)cur;
return pCur->pCurrent==0;
}
/*
** Search for terms of these forms:
**
** (A) root = $root
** (B1) depth < $depth
** (B2) depth <= $depth
** (B3) depth = $depth
** (C) tablename = $tablename
** (D) idcolumn = $idcolumn
** (E) parentcolumn = $parentcolumn
**
**
**
** idxNum meaning
** ---------- ------------------------------------------------------
** 0x00000001 Term of the form (A) found
** 0x00000002 The term of bit-2 is like (B1)
** 0x000000f0 Index in filter.argv[] of $depth. 0 if not used.
** 0x00000f00 Index in filter.argv[] of $tablename. 0 if not used.
** 0x0000f000 Index in filter.argv[] of $idcolumn. 0 if not used
** 0x000f0000 Index in filter.argv[] of $parentcolumn. 0 if not used.
**
** There must be a term of type (A). If there is not, then the index type
** is 0 and the query will return an empty set.
*/
static int closureBestIndex(
sqlite3_vtab *pTab, /* The virtual table */
sqlite3_index_info *pIdxInfo /* Information about the query */
){
int iPlan = 0;
int i;
int idx = 1;
const struct sqlite3_index_constraint *pConstraint;
closure_vtab *pVtab = (closure_vtab*)pTab;
pConstraint = pIdxInfo->aConstraint;
for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
if( pConstraint->usable==0 ) continue;
if( (iPlan & 1)==0
&& pConstraint->iColumn==CLOSURE_COL_ROOT
&& pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ
){
iPlan |= 1;
pIdxInfo->aConstraintUsage[i].argvIndex = 1;
pIdxInfo->aConstraintUsage[i].omit = 1;
}
if( (iPlan & 0x0000f0)==0
&& pConstraint->iColumn==CLOSURE_COL_DEPTH
&& (pConstraint->op==SQLITE_INDEX_CONSTRAINT_LT
|| pConstraint->op==SQLITE_INDEX_CONSTRAINT_LE
|| pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ)
){
iPlan |= idx<<4;
pIdxInfo->aConstraintUsage[i].argvIndex = ++idx;
if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_LT ) iPlan |= 0x000002;
}
if( (iPlan & 0x000f00)==0
&& pConstraint->iColumn==CLOSURE_COL_TABLENAME
&& pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ
){
iPlan |= idx<<8;
pIdxInfo->aConstraintUsage[i].argvIndex = ++idx;
pIdxInfo->aConstraintUsage[i].omit = 1;
}
if( (iPlan & 0x00f000)==0
&& pConstraint->iColumn==CLOSURE_COL_IDCOLUMN
&& pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ
){
iPlan |= idx<<12;
pIdxInfo->aConstraintUsage[i].argvIndex = ++idx;
pIdxInfo->aConstraintUsage[i].omit = 1;
}
if( (iPlan & 0x0f0000)==0
&& pConstraint->iColumn==CLOSURE_COL_PARENTCOLUMN
&& pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ
){
iPlan |= idx<<16;
pIdxInfo->aConstraintUsage[i].argvIndex = ++idx;
pIdxInfo->aConstraintUsage[i].omit = 1;
}
}
if( (pVtab->zTableName==0 && (iPlan & 0x000f00)==0)
|| (pVtab->zIdColumn==0 && (iPlan & 0x00f000)==0)
|| (pVtab->zParentColumn==0 && (iPlan & 0x0f0000)==0)
){
/* All of tablename, idcolumn, and parentcolumn must be specified
** in either the CREATE VIRTUAL TABLE or in the WHERE clause constraints
** or else the result is an empty set. */
iPlan = 0;
}
pIdxInfo->idxNum = iPlan;
if( pIdxInfo->nOrderBy==1
&& pIdxInfo->aOrderBy[0].iColumn==CLOSURE_COL_ID
&& pIdxInfo->aOrderBy[0].desc==0
){
pIdxInfo->orderByConsumed = 1;
}
pIdxInfo->estimatedCost = (double)10000;
return SQLITE_OK;
}
/*
** A virtual table module that implements the "approximate_match".
*/
static sqlite3_module closureModule = {
0, /* iVersion */
closureConnect, /* xCreate */
closureConnect, /* xConnect */
closureBestIndex, /* xBestIndex */
closureDisconnect, /* xDisconnect */
closureDisconnect, /* xDestroy */
closureOpen, /* xOpen - open a cursor */
closureClose, /* xClose - close a cursor */
closureFilter, /* xFilter - configure scan constraints */
closureNext, /* xNext - advance a cursor */
closureEof, /* xEof - check for end of scan */
closureColumn, /* xColumn - read data */
closureRowid, /* xRowid - read data */
0, /* xUpdate */
0, /* xBegin */
0, /* xSync */
0, /* xCommit */
0, /* xRollback */
0, /* xFindMethod */
0, /* xRename */
0, /* xSavepoint */
0, /* xRelease */
0 /* xRollbackTo */
};
/*
** Register the closure virtual table
*/
#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_closure_init(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
int rc = SQLITE_OK;
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg;
rc = sqlite3_create_module(db, "transitive_closure", &closureModule, 0);
return rc;
}

131
ext/misc/ieee754.c Normal file
View File

@ -0,0 +1,131 @@
/*
** 2013-04-17
**
** 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 SQLite extension implements functions for the exact display
** and input of IEEE754 Binary64 floating-point numbers.
**
** ieee754(X)
** ieee754(Y,Z)
**
** In the first form, the value X should be a floating-point number.
** The function will return a string of the form 'ieee754(Y,Z)' where
** Y and Z are integers such that X==Y*pow(w.0,Z).
**
** In the second form, Y and Z are integers which are the mantissa and
** base-2 exponent of a new floating point number. The function returns
** a floating-point value equal to Y*pow(2.0,Z).
**
** Examples:
**
** ieee754(2.0) -> 'ieee754(2,0)'
** ieee754(45.25) -> 'ieee754(181,-2)'
** ieee754(2, 0) -> 2.0
** ieee754(181, -2) -> 45.25
*/
#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
#include <assert.h>
#include <string.h>
/*
** Implementation of the ieee754() function
*/
static void ieee754func(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
if( argc==1 ){
sqlite3_int64 m, a;
double r;
int e;
int isNeg;
char zResult[100];
assert( sizeof(m)==sizeof(r) );
if( sqlite3_value_type(argv[0])!=SQLITE_FLOAT ) return;
r = sqlite3_value_double(argv[0]);
if( r<0.0 ){
isNeg = 1;
r = -r;
}else{
isNeg = 0;
}
memcpy(&a,&r,sizeof(a));
if( a==0 ){
e = 0;
m = 0;
}else{
e = a>>52;
m = a & ((((sqlite3_int64)1)<<52)-1);
m |= ((sqlite3_int64)1)<<52;
while( e<1075 && m>0 && (m&1)==0 ){
m >>= 1;
e++;
}
if( isNeg ) m = -m;
}
sqlite3_snprintf(sizeof(zResult), zResult, "ieee754(%lld,%d)",
m, e-1075);
sqlite3_result_text(context, zResult, -1, SQLITE_TRANSIENT);
}else if( argc==2 ){
sqlite3_int64 m, e, a;
double r;
int isNeg = 0;
m = sqlite3_value_int64(argv[0]);
e = sqlite3_value_int64(argv[1]);
if( m<0 ){
isNeg = 1;
m = -m;
if( m<0 ) return;
}else if( m==0 && e>1000 && e<1000 ){
sqlite3_result_double(context, 0.0);
return;
}
while( (m>>32)&0xffe00000 ){
m >>= 1;
e++;
}
while( ((m>>32)&0xfff00000)==0 ){
m <<= 1;
e--;
}
e += 1075;
if( e<0 ) e = m = 0;
if( e>0x7ff ) m = 0;
a = m & ((((sqlite3_int64)1)<<52)-1);
a |= e<<52;
if( isNeg ) a |= ((sqlite3_int64)1)<<63;
memcpy(&r, &a, sizeof(r));
sqlite3_result_double(context, r);
}
}
#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_ieee_init(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
int rc = SQLITE_OK;
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg; /* Unused parameter */
rc = sqlite3_create_function(db, "ieee754", 1, SQLITE_UTF8, 0,
ieee754func, 0, 0);
if( rc==SQLITE_OK ){
rc = sqlite3_create_function(db, "ieee754", 2, SQLITE_UTF8, 0,
ieee754func, 0, 0);
}
return rc;
}

View File

@ -12,7 +12,16 @@
**
** The code in this file implements a compact but reasonably
** efficient regular-expression matcher for posix extended regular
** expressions against UTF8 text. The following syntax is supported:
** expressions against UTF8 text.
**
** This file is an SQLite extension. It registers a single function
** named "regexp(A,B)" where A is the regular expression and B is the
** string to be matched. By registering this function, SQLite will also
** then implement the "B regexp A" operator. Note that with the function
** the regular expression comes first, but with the operator it comes
** second.
**
** The following regular expression syntax is supported:
**
** X* zero or more occurrences of X
** X+ one or more occurrences of X

View File

@ -270,7 +270,10 @@ TESTSRC = \
# Extensions to be statically loaded.
#
TESTSRC += \
$(TOP)/ext/misc/amatch.c \
$(TOP)/ext/misc/closure.c \
$(TOP)/ext/misc/fuzzer.c \
$(TOP)/ext/misc/ieee754.c \
$(TOP)/ext/misc/regexp.c \
$(TOP)/ext/misc/spellfix.c \
$(TOP)/ext/misc/wholenumber.c

View File

@ -1,9 +1,9 @@
C Move\sthe\stest_spellfix.c\smodule\sto\sext/misc/spellfix.c.
D 2013-04-25T14:59:01.366
C Added\sthe\stransitive_closure,\sieee754,\sand\samatch\sextensions.
D 2013-04-25T16:42:55.478
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in 10c635460b6c3a20741d71c3a1b65b0ebec7558b
F Makefile.in 38a45083d4df568eefd31d27d67381850f35a016
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
F Makefile.msc 97a23e910afb8c2d23db0217c343481d59045acb
F Makefile.msc d03cde9073d5752d6ddcaa6807a8bdda277e48f2
F Makefile.vxworks db21ed42a01d5740e656b16f92cb5d8d5e5dd315
F README cd04a36fbc7ea56932a4052d7d0b7f09f27c33d6
F VERSION 05c7bd63b96f31cfdef5c766ed91307ac121f5aa
@ -83,9 +83,12 @@ F ext/fts3/unicode/mkunicode.tcl 7a9bc018e2962abb79563c5a39fe581fcbf2f675
F ext/icu/README.txt d9fbbad0c2f647c3fdf715fc9fd64af53aedfc43
F ext/icu/icu.c eb9ae1d79046bd7871aa97ee6da51eb770134b5a
F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37
F ext/misc/amatch.c 3369b2b544066e620d986f0085d039c77d1ef17f
F ext/misc/closure.c fec0c8537c69843e0b7631d500a14c0527962cd6
F ext/misc/fuzzer.c fb64a15af978ae73fa9075b9b1dfbe82b8defc6f
F ext/misc/regexp.c c0fdb8af86981ff9890d776cfb97fe66297cc3b2
F ext/misc/spellfix.c 8bb699116e36cc5e68d7ddf1810b638a3090c744 w src/test_spellfix.c
F ext/misc/ieee754.c 2565ce373d842977efe0922dc50b8a41b3289556
F ext/misc/regexp.c c25c65fe775f5d9801fb8573e36ebe73f2c0c2e0
F ext/misc/spellfix.c 8bb699116e36cc5e68d7ddf1810b638a3090c744
F ext/misc/wholenumber.c ce362368b9381ea48cbd951ade8df867eeeab014
F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
F ext/rtree/rtree.c 757abea591d4ff67c0ff4e8f9776aeda86b18c14
@ -108,7 +111,7 @@ F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F main.mk a6183110637d782988fdf3735f7d125b19639967
F main.mk 9535835509ac58a1902ba85ed77bcce9840d62d4
F mkdll.sh 7d09b23c05d56532e9d44a50868eb4b12ff4f74a
F mkextu.sh 416f9b7089d80e5590a29692c9d9280a10dbad9f
F mkextw.sh 4123480947681d9b434a5e7b1ee08135abe409ac
@ -195,7 +198,7 @@ F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d
F src/status.c bedc37ec1a6bb9399944024d63f4c769971955a9
F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
F src/tclsqlite.c 2ecec9937e69bc17560ad886da35195daa7261b8
F src/test1.c 1c7fa0a36703508130d7ed65325883a9e453bf72
F src/test1.c e9562428421bf0cdbf11bf5f60ab0cdabee45885
F src/test2.c 29e7154112f7448d64204e8d31179cf497ecf425
F src/test3.c 96aed72a8e1d542fed127e3e8350ae357712fa82
F src/test4.c cea2c55110241e4674e66d476d29c914627999f5
@ -332,6 +335,7 @@ F test/capi3d.test 17b57ca28be3e37e14c2ba8f787d292d84b724a1
F test/capi3e.test f7408dda65c92b9056199fdc180f893015f83dde
F test/cast.test 4c275cbdc8202d6f9c54a3596701719868ac7dc3
F test/check.test 2eb93611139a7dfaed3be80067c7dc5ceb5fb287
F test/closure01.test 6194a899cdbba561d0439c0d6cc7bcdf4fc413e7
F test/coalesce.test cee0dccb9fbd2d494b77234bccf9dc6c6786eb91
F test/collate1.test fd02c4d8afc71879c4bb952586389961a21fb0ce
F test/collate2.test 04cebe4a033be319d6ddbb3bbc69464e01700b49
@ -1054,7 +1058,7 @@ F tool/vdbe-compress.tcl f12c884766bd14277f4fcedcae07078011717381
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381
F tool/win/sqlite.vsix 97894c2790eda7b5bce3cc79cb2a8ec2fde9b3ac
P 680822e892f3efdb702eea3b321bc5785239dd56
R f6325e95c7057e7590cfe9f4379a5b36
P de556add10150140981a2e34b3712e96a7c262e3
R 9481b1895542c76cd21821bef5dac0b1
U drh
Z 2d983c030ad222dceb5985301e41e8c4
Z 540c4ff1b6a2b03d38328f53c71b1af7

View File

@ -1 +1 @@
de556add10150140981a2e34b3712e96a7c262e3
84018099c8715b982cd24ce9221f93c7379e8c08

View File

@ -6057,7 +6057,10 @@ static int tclLoadStaticExtensionCmd(
int objc,
Tcl_Obj *CONST objv[]
){
extern int sqlite3_amatch_init(sqlite3*,char**,const sqlite3_api_routines*);
extern int sqlite3_closure_init(sqlite3*,char**,const sqlite3_api_routines*);
extern int sqlite3_fuzzer_init(sqlite3*,char**,const sqlite3_api_routines*);
extern int sqlite3_ieee_init(sqlite3*,char**,const sqlite3_api_routines*);
extern int sqlite3_regexp_init(sqlite3*,char**,const sqlite3_api_routines*);
extern int sqlite3_spellfix_init(sqlite3*,char**,const sqlite3_api_routines*);
extern int sqlite3_wholenumber_init(sqlite3*,char**,const sqlite3_api_routines*);
@ -6065,7 +6068,10 @@ static int tclLoadStaticExtensionCmd(
const char *zExtName;
int (*pInit)(sqlite3*,char**,const sqlite3_api_routines*);
} aExtension[] = {
{ "amatch", sqlite3_amatch_init },
{ "closure", sqlite3_closure_init },
{ "fuzzer", sqlite3_fuzzer_init },
{ "ieee754", sqlite3_ieee_init },
{ "regexp", sqlite3_regexp_init },
{ "spellfix", sqlite3_spellfix_init },
{ "wholenumber", sqlite3_wholenumber_init },

224
test/closure01.test Normal file
View File

@ -0,0 +1,224 @@
# 2013-04-25
#
# 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.
#
#***********************************************************************
#
# Test cases for transitive_closure virtual table.
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix closure01
load_static_extension db closure
do_execsql_test 1.0 {
BEGIN;
CREATE TABLE t1(x INTEGER PRIMARY KEY, y INTEGER);
CREATE INDEX t1y ON t1(y);
INSERT INTO t1(x) VALUES(1),(2);
INSERT INTO t1(x) SELECT x+2 FROM t1;
INSERT INTO t1(x) SELECT x+4 FROM t1;
INSERT INTO t1(x) SELECT x+8 FROM t1;
INSERT INTO t1(x) SELECT x+16 FROM t1;
INSERT INTO t1(x) SELECT x+32 FROM t1;
INSERT INTO t1(x) SELECT x+64 FROM t1;
INSERT INTO t1(x) SELECT x+128 FROM t1;
INSERT INTO t1(x) SELECT x+256 FROM t1;
INSERT INTO t1(x) SELECT x+512 FROM t1;
INSERT INTO t1(x) SELECT x+1024 FROM t1;
INSERT INTO t1(x) SELECT x+2048 FROM t1;
INSERT INTO t1(x) SELECT x+4096 FROM t1;
INSERT INTO t1(x) SELECT x+8192 FROM t1;
INSERT INTO t1(x) SELECT x+16384 FROM t1;
INSERT INTO t1(x) SELECT x+32768 FROM t1;
INSERT INTO t1(x) SELECT x+65536 FROM t1;
UPDATE t1 SET y=x/2 WHERE x>1;
COMMIT;
CREATE VIRTUAL TABLE cx
USING transitive_closure(tablename=t1, idcolumn=x, parentcolumn=y);
} {}
# The entire table
do_execsql_test 1.1 {
SELECT count(*), depth FROM cx WHERE root=1 GROUP BY depth ORDER BY 1;
} {/1 0 1 17 2 1 4 2 8 3 16 4 .* 65536 16/}
# descendents of 32768
do_execsql_test 1.2 {
SELECT * FROM cx WHERE root=32768 ORDER BY id;
} {32768 0 65536 1 65537 1 131072 2}
# descendents of 16384
do_execsql_test 1.3 {
SELECT * FROM cx WHERE root=16384 AND depth<=2 ORDER BY id;
} {16384 0 32768 1 32769 1 65536 2 65537 2 65538 2 65539 2}
# children of 16384
do_execsql_test 1.4 {
SELECT id, depth, root, tablename, idcolumn, parentcolumn FROM cx
WHERE root=16384
AND depth=1
ORDER BY id;
} {32768 1 {} t1 x y 32769 1 {} t1 x y}
# great-grandparent of 16384
do_execsql_test 1.5 {
SELECT id, depth, root, tablename, idcolumn, parentcolumn FROM cx
WHERE root=16384
AND depth=3
AND idcolumn='Y'
AND parentcolumn='X';
} {2048 3 {} t1 Y X}
# depth<5
do_execsql_test 1.6 {
SELECT count(*), depth FROM cx WHERE root=1 AND depth<5
GROUP BY depth ORDER BY 1;
} {1 0 2 1 4 2 8 3 16 4}
# depth<=5
do_execsql_test 1.7 {
SELECT count(*), depth FROM cx WHERE root=1 AND depth<=5
GROUP BY depth ORDER BY 1;
} {1 0 2 1 4 2 8 3 16 4 32 5}
# depth==5
do_execsql_test 1.8 {
SELECT count(*), depth FROM cx WHERE root=1 AND depth=5
GROUP BY depth ORDER BY 1;
} {32 5}
# depth BETWEEN 3 AND 5
do_execsql_test 1.9 {
SELECT count(*), depth FROM cx WHERE root=1 AND depth BETWEEN 3 AND 5
GROUP BY depth ORDER BY 1;
} {8 3 16 4 32 5}
# depth==5 with min() and max()
do_execsql_test 1.10 {
SELECT count(*), min(id), max(id) FROM cx WHERE root=1 AND depth=5;
} {32 32 63}
# Create a much smaller table t2 with only 32 elements
db eval {
CREATE TABLE t2(x INTEGER PRIMARY KEY, y INTEGER);
INSERT INTO t2 SELECT x, y FROM t1 WHERE x<32;
CREATE INDEX t2y ON t2(y);
CREATE VIRTUAL TABLE c2
USING transitive_closure(tablename=t2, idcolumn=x, parentcolumn=y);
}
# t2 full-table
do_execsql_test 2.1 {
SELECT count(*), min(id), max(id) FROM c2 WHERE root=1;
} {31 1 31}
# t2 root=10
do_execsql_test 2.2 {
SELECT id FROM c2 WHERE root=10;
} {10 20 21}
# t2 root=11
do_execsql_test 2.3 {
SELECT id FROM c2 WHERE root=12;
} {12 24 25}
# t2 root IN [10,12]
do_execsql_test 2.4 {
SELECT id FROM c2 WHERE root IN (10,12) ORDER BY id;
} {10 12 20 21 24 25}
# t2 root IN [10,12] (sorted)
do_execsql_test 2.5 {
SELECT id FROM c2 WHERE root IN (10,12) ORDER BY +id;
} {10 12 20 21 24 25}
# t2 c2up from 20
do_execsql_test 3.0 {
CREATE VIRTUAL TABLE c2up USING transitive_closure(
tablename = t2,
idcolumn = y,
parentcolumn = x
);
SELECT id FROM c2up WHERE root=20;
} {1 2 5 10 20}
# cx as c2up
do_execsql_test 3.1 {
SELECT id FROM cx
WHERE root=20
AND tablename='t2'
AND idcolumn='y'
AND parentcolumn='x';
} {1 2 5 10 20}
# t2 first cousins of 20
do_execsql_test 3.2 {
SELECT DISTINCT id FROM c2
WHERE root IN (SELECT id FROM c2up
WHERE root=20 AND depth<=2)
ORDER BY id;
} {5 10 11 20 21 22 23}
# t2 first cousins of 20
do_execsql_test 3.3 {
SELECT id FROM c2
WHERE root=(SELECT id FROM c2up
WHERE root=20 AND depth=2)
AND depth=2
EXCEPT
SELECT id FROM c2
WHERE root=(SELECT id FROM c2up
WHERE root=20 AND depth=1)
AND depth<=1
ORDER BY id;
} {22 23}
# missing tablename.
do_test 4.1 {
catchsql {
SELECT id FROM cx
WHERE root=20
AND tablename='t3'
AND idcolumn='y'
AND parentcolumn='x';
}
} {1 {no such table: t3}}
# missing idcolumn
do_test 4.2 {
catchsql {
SELECT id FROM cx
WHERE root=20
AND tablename='t2'
AND idcolumn='xyz'
AND parentcolumn='x';
}
} {1 {no such column: t2.xyz}}
# missing parentcolumn
do_test 4.3 {
catchsql {
SELECT id FROM cx
WHERE root=20
AND tablename='t2'
AND idcolumn='x'
AND parentcolumn='pqr';
}
} {1 {no such column: t2.pqr}}
# generic closure
do_execsql_test 5.1 {
CREATE VIRTUAL TABLE temp.closure USING transitive_closure;
SELECT id FROM closure
WHERE root=1
AND depth=3
AND tablename='t1'
AND idcolumn='x'
AND parentcolumn='y'
ORDER BY id;
} {8 9 10 11 12 13 14 15}
finish_test