2013-08-29 13:11:18 +00:00
|
|
|
/*
|
|
|
|
** 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>
|
|
|
|
|
|
|
|
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
|
|
|
|
|
|
|
/*
|
|
|
|
** 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){
|
2019-02-10 15:35:42 +00:00
|
|
|
sqlite3_int64 nIn; /* Size of input string, in bytes */
|
2013-08-29 13:11:18 +00:00
|
|
|
char *zOut; /* Output (dequoted) string */
|
|
|
|
|
2019-02-10 15:35:42 +00:00
|
|
|
nIn = strlen(zIn);
|
|
|
|
zOut = sqlite3_malloc64(nIn+1);
|
2013-08-29 13:11:18 +00:00
|
|
|
if( zOut ){
|
|
|
|
char q = zIn[0]; /* Quote character (if any ) */
|
|
|
|
|
|
|
|
if( q!='[' && q!= '\'' && q!='"' && q!='`' ){
|
2019-05-15 16:49:41 +00:00
|
|
|
memcpy(zOut, zIn, (size_t)(nIn+1));
|
2013-08-29 13:11:18 +00:00
|
|
|
}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;
|
2016-02-23 15:07:39 +00:00
|
|
|
for(i=nKey; isspace((unsigned char)zStr[i]); i++){}
|
2013-08-29 13:11:18 +00:00
|
|
|
if( zStr[i]!='=' ) return 0;
|
|
|
|
i++;
|
2016-02-23 15:07:39 +00:00
|
|
|
while( isspace((unsigned char)zStr[i]) ){ i++; }
|
2013-08-29 13:11:18 +00:00
|
|
|
return zStr+i;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
** xConnect/xCreate method for the closure module. Arguments are:
|
|
|
|
**
|
|
|
|
** argv[0] -> module name ("transitive_closure")
|
|
|
|
** 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;
|
|
|
|
double rCost = 10000000.0;
|
|
|
|
|
|
|
|
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;
|
|
|
|
rCost /= 100.0;
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
rCost /= 5.0;
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
rCost /= 5.0;
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
2018-09-18 16:31:37 +00:00
|
|
|
if( (iPlan&1)==0 ){
|
|
|
|
/* If there is no usable "root=?" term, then set the index-type to 0.
|
|
|
|
** Also clear any argvIndex variables already set. This is necessary
|
|
|
|
** to prevent the core from throwing an "xBestIndex malfunction error"
|
|
|
|
** error (because the argvIndex values are not contiguously assigned
|
|
|
|
** starting from 1). */
|
|
|
|
rCost *= 1e30;
|
|
|
|
for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
|
|
|
|
pIdxInfo->aConstraintUsage[i].argvIndex = 0;
|
|
|
|
}
|
|
|
|
iPlan = 0;
|
|
|
|
}
|
2013-08-29 13:11:18 +00:00
|
|
|
pIdxInfo->idxNum = iPlan;
|
|
|
|
if( pIdxInfo->nOrderBy==1
|
|
|
|
&& pIdxInfo->aOrderBy[0].iColumn==CLOSURE_COL_ID
|
|
|
|
&& pIdxInfo->aOrderBy[0].desc==0
|
|
|
|
){
|
|
|
|
pIdxInfo->orderByConsumed = 1;
|
|
|
|
}
|
|
|
|
pIdxInfo->estimatedCost = rCost;
|
|
|
|
|
|
|
|
return SQLITE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
** A virtual table module that implements the "transitive_closure".
|
|
|
|
*/
|
|
|
|
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 */
|
2018-12-13 00:43:10 +00:00
|
|
|
0, /* xRollbackTo */
|
|
|
|
0 /* xShadowName */
|
2013-08-29 13:11:18 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
#endif /* SQLITE_OMIT_VIRTUALTABLE */
|
|
|
|
|
|
|
|
/*
|
|
|
|
** 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;
|
|
|
|
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
|
|
|
rc = sqlite3_create_module(db, "transitive_closure", &closureModule, 0);
|
|
|
|
#endif /* SQLITE_OMIT_VIRTUALTABLE */
|
|
|
|
return rc;
|
|
|
|
}
|