2017-07-11 00:09:44 +00:00
/*
* * 2017 - 07 - 10
* *
* * 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 implements an eponymous virtual table that returns suggested
* * completions for a partial SQL input .
* *
2017-07-11 02:05:21 +00:00
* * Suggested usage :
* *
* * SELECT DISTINCT candidate COLLATE nocase
* * FROM completion ( $ prefix , $ wholeline )
* * ORDER BY 1 ;
* *
* * The two query parameters are optional . $ prefix is the text of the
* * current word being typed and that is to be completed . $ wholeline is
* * the complete input line , used for context .
* *
* * The raw completion ( ) table might return the same candidate multiple
* * times , for example if the same column name is used to two or more
* * tables . And the candidates are returned in an arbitrary order . Hence ,
* * the DISTINCT and ORDER BY are recommended .
* *
* * This virtual table operates at the speed of human typing , and so there
* * is no attempt to make it fast . Even a slow implementation will be much
* * faster than any human can type .
* *
2017-07-11 00:09:44 +00:00
*/
# include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
# include <assert.h>
# include <string.h>
# include <ctype.h>
# ifndef SQLITE_OMIT_VIRTUALTABLE
/* completion_vtab is a subclass of sqlite3_vtab which will
* * serve as the underlying representation of a completion virtual table
*/
typedef struct completion_vtab completion_vtab ;
struct completion_vtab {
sqlite3_vtab base ; /* Base class - must be first */
sqlite3 * db ; /* Database connection for this completion vtab */
} ;
/* completion_cursor is a subclass of sqlite3_vtab_cursor which will
* * serve as the underlying representation of a cursor that scans
* * over rows of the result
*/
typedef struct completion_cursor completion_cursor ;
struct completion_cursor {
sqlite3_vtab_cursor base ; /* Base class - must be first */
sqlite3 * db ; /* Database connection for this cursor */
int nPrefix , nLine ; /* Number of bytes in zPrefix and zLine */
char * zPrefix ; /* The prefix for the word we want to complete */
char * zLine ; /* The whole that we want to complete */
const char * zCurrentRow ; /* Current output row */
2018-04-25 19:02:48 +00:00
int szRow ; /* Length of the zCurrentRow string */
2017-07-11 00:09:44 +00:00
sqlite3_stmt * pStmt ; /* Current statement */
sqlite3_int64 iRowid ; /* The rowid */
int ePhase ; /* Current phase */
int j ; /* inter-phase counter */
} ;
/* Values for ePhase:
*/
2017-07-11 01:38:45 +00:00
# define COMPLETION_FIRST_PHASE 1
2017-07-11 00:09:44 +00:00
# define COMPLETION_KEYWORDS 1
# define COMPLETION_PRAGMAS 2
# define COMPLETION_FUNCTIONS 3
# define COMPLETION_COLLATIONS 4
# define COMPLETION_INDEXES 5
# define COMPLETION_TRIGGERS 6
# define COMPLETION_DATABASES 7
2018-03-19 19:05:45 +00:00
# define COMPLETION_TABLES 8 /* Also VIEWs and TRIGGERs */
2017-07-11 01:38:45 +00:00
# define COMPLETION_COLUMNS 9
# define COMPLETION_MODULES 10
# define COMPLETION_EOF 11
2017-07-11 00:09:44 +00:00
/*
* * The completionConnect ( ) method is invoked to create a new
* * completion_vtab that describes the completion virtual table .
* *
* * Think of this routine as the constructor for completion_vtab objects .
* *
* * All this routine needs to do is :
* *
* * ( 1 ) Allocate the completion_vtab object and initialize all fields .
* *
* * ( 2 ) Tell SQLite ( via the sqlite3_declare_vtab ( ) interface ) what the
* * result set of queries against completion will look like .
*/
static int completionConnect (
sqlite3 * db ,
void * pAux ,
int argc , const char * const * argv ,
sqlite3_vtab * * ppVtab ,
char * * pzErr
) {
completion_vtab * pNew ;
int rc ;
2017-07-11 18:11:33 +00:00
( void ) ( pAux ) ; /* Unused parameter */
( void ) ( argc ) ; /* Unused parameter */
( void ) ( argv ) ; /* Unused parameter */
( void ) ( pzErr ) ; /* Unused parameter */
2017-07-11 00:09:44 +00:00
/* Column numbers */
# define COMPLETION_COLUMN_CANDIDATE 0 /* Suggested completion of the input */
# define COMPLETION_COLUMN_PREFIX 1 /* Prefix of the word to be completed */
# define COMPLETION_COLUMN_WHOLELINE 2 /* Entire line seen so far */
2017-07-11 02:05:21 +00:00
# define COMPLETION_COLUMN_PHASE 3 /* ePhase - used for debugging only */
2017-07-11 00:09:44 +00:00
2020-01-07 19:45:40 +00:00
sqlite3_vtab_config ( db , SQLITE_VTAB_INNOCUOUS ) ;
2017-07-11 00:09:44 +00:00
rc = sqlite3_declare_vtab ( db ,
" CREATE TABLE x( "
" candidate TEXT, "
" prefix TEXT HIDDEN, "
2017-07-11 02:05:21 +00:00
" wholeline TEXT HIDDEN, "
" phase INT HIDDEN " /* Used for debugging only */
2017-07-11 00:09:44 +00:00
" ) " ) ;
if ( rc = = SQLITE_OK ) {
pNew = sqlite3_malloc ( sizeof ( * pNew ) ) ;
* ppVtab = ( sqlite3_vtab * ) pNew ;
if ( pNew = = 0 ) return SQLITE_NOMEM ;
memset ( pNew , 0 , sizeof ( * pNew ) ) ;
pNew - > db = db ;
}
return rc ;
}
/*
* * This method is the destructor for completion_cursor objects .
*/
static int completionDisconnect ( sqlite3_vtab * pVtab ) {
sqlite3_free ( pVtab ) ;
return SQLITE_OK ;
}
/*
* * Constructor for a new completion_cursor object .
*/
static int completionOpen ( sqlite3_vtab * p , sqlite3_vtab_cursor * * ppCursor ) {
completion_cursor * pCur ;
pCur = sqlite3_malloc ( sizeof ( * pCur ) ) ;
if ( pCur = = 0 ) return SQLITE_NOMEM ;
memset ( pCur , 0 , sizeof ( * pCur ) ) ;
pCur - > db = ( ( completion_vtab * ) p ) - > db ;
* ppCursor = & pCur - > base ;
return SQLITE_OK ;
}
/*
* * Reset the completion_cursor .
*/
static void completionCursorReset ( completion_cursor * pCur ) {
sqlite3_free ( pCur - > zPrefix ) ; pCur - > zPrefix = 0 ; pCur - > nPrefix = 0 ;
sqlite3_free ( pCur - > zLine ) ; pCur - > zLine = 0 ; pCur - > nLine = 0 ;
sqlite3_finalize ( pCur - > pStmt ) ; pCur - > pStmt = 0 ;
2017-07-11 01:38:45 +00:00
pCur - > j = 0 ;
2017-07-11 00:09:44 +00:00
}
/*
* * Destructor for a completion_cursor .
*/
static int completionClose ( sqlite3_vtab_cursor * cur ) {
completionCursorReset ( ( completion_cursor * ) cur ) ;
sqlite3_free ( cur ) ;
return SQLITE_OK ;
}
/*
* * Advance a completion_cursor to its next row of output .
2017-07-11 02:05:21 +00:00
* *
* * The - > ePhase , - > j , and - > pStmt fields of the completion_cursor object
* * record the current state of the scan . This routine sets - > zCurrentRow
* * to the current row of output and then returns . If no more rows remain ,
* * then - > ePhase is set to COMPLETION_EOF which will signal the virtual
* * table that has reached the end of its scan .
* *
* * The current implementation just lists potential identifiers and
* * keywords and filters them by zPrefix . Future enhancements should
* * take zLine into account to try to restrict the set of identifiers and
* * keywords based on what would be legal at the current point of input .
2017-07-11 00:09:44 +00:00
*/
static int completionNext ( sqlite3_vtab_cursor * cur ) {
completion_cursor * pCur = ( completion_cursor * ) cur ;
2017-07-11 02:05:21 +00:00
int eNextPhase = 0 ; /* Next phase to try if current phase reaches end */
int iCol = - 1 ; /* If >=0, step pCur->pStmt and use the i-th column */
2017-07-11 00:09:44 +00:00
pCur - > iRowid + + ;
2017-07-11 01:38:45 +00:00
while ( pCur - > ePhase ! = COMPLETION_EOF ) {
switch ( pCur - > ePhase ) {
case COMPLETION_KEYWORDS : {
2018-04-25 19:02:48 +00:00
if ( pCur - > j > = sqlite3_keyword_count ( ) ) {
2017-07-11 01:38:45 +00:00
pCur - > zCurrentRow = 0 ;
pCur - > ePhase = COMPLETION_DATABASES ;
} else {
2018-04-25 19:02:48 +00:00
sqlite3_keyword_name ( pCur - > j + + , & pCur - > zCurrentRow , & pCur - > szRow ) ;
2017-07-11 01:38:45 +00:00
}
iCol = - 1 ;
break ;
}
case COMPLETION_DATABASES : {
if ( pCur - > pStmt = = 0 ) {
sqlite3_prepare_v2 ( pCur - > db , " PRAGMA database_list " , - 1 ,
& pCur - > pStmt , 0 ) ;
}
iCol = 1 ;
eNextPhase = COMPLETION_TABLES ;
break ;
}
case COMPLETION_TABLES : {
if ( pCur - > pStmt = = 0 ) {
sqlite3_stmt * pS2 ;
char * zSql = 0 ;
const char * zSep = " " ;
sqlite3_prepare_v2 ( pCur - > db , " PRAGMA database_list " , - 1 , & pS2 , 0 ) ;
while ( sqlite3_step ( pS2 ) = = SQLITE_ROW ) {
const char * zDb = ( const char * ) sqlite3_column_text ( pS2 , 1 ) ;
zSql = sqlite3_mprintf (
" %z%s "
2020-06-19 15:24:12 +00:00
" SELECT name FROM \" %w \" .sqlite_schema " ,
2017-07-11 01:38:45 +00:00
zSql , zSep , zDb
) ;
if ( zSql = = 0 ) return SQLITE_NOMEM ;
zSep = " UNION " ;
}
sqlite3_finalize ( pS2 ) ;
sqlite3_prepare_v2 ( pCur - > db , zSql , - 1 , & pCur - > pStmt , 0 ) ;
sqlite3_free ( zSql ) ;
}
iCol = 0 ;
eNextPhase = COMPLETION_COLUMNS ;
2017-07-11 00:09:44 +00:00
break ;
}
2017-07-11 01:38:45 +00:00
case COMPLETION_COLUMNS : {
if ( pCur - > pStmt = = 0 ) {
sqlite3_stmt * pS2 ;
char * zSql = 0 ;
const char * zSep = " " ;
sqlite3_prepare_v2 ( pCur - > db , " PRAGMA database_list " , - 1 , & pS2 , 0 ) ;
while ( sqlite3_step ( pS2 ) = = SQLITE_ROW ) {
const char * zDb = ( const char * ) sqlite3_column_text ( pS2 , 1 ) ;
zSql = sqlite3_mprintf (
" %z%s "
2020-06-19 15:24:12 +00:00
" SELECT pti.name FROM \" %w \" .sqlite_schema AS sm "
2017-07-11 01:38:45 +00:00
" JOIN pragma_table_info(sm.name,%Q) AS pti "
" WHERE sm.type='table' " ,
zSql , zSep , zDb , zDb
) ;
if ( zSql = = 0 ) return SQLITE_NOMEM ;
zSep = " UNION " ;
}
sqlite3_finalize ( pS2 ) ;
sqlite3_prepare_v2 ( pCur - > db , zSql , - 1 , & pCur - > pStmt , 0 ) ;
sqlite3_free ( zSql ) ;
}
iCol = 0 ;
eNextPhase = COMPLETION_EOF ;
2017-07-11 00:09:44 +00:00
break ;
}
}
2017-07-11 01:38:45 +00:00
if ( iCol < 0 ) {
/* This case is when the phase presets zCurrentRow */
if ( pCur - > zCurrentRow = = 0 ) continue ;
} else {
if ( sqlite3_step ( pCur - > pStmt ) = = SQLITE_ROW ) {
/* Extract the next row of content */
pCur - > zCurrentRow = ( const char * ) sqlite3_column_text ( pCur - > pStmt , iCol ) ;
2018-04-25 19:02:48 +00:00
pCur - > szRow = sqlite3_column_bytes ( pCur - > pStmt , iCol ) ;
2017-07-11 01:38:45 +00:00
} else {
/* When all rows are finished, advance to the next phase */
sqlite3_finalize ( pCur - > pStmt ) ;
pCur - > pStmt = 0 ;
pCur - > ePhase = eNextPhase ;
continue ;
}
}
if ( pCur - > nPrefix = = 0 ) break ;
2018-04-25 19:02:48 +00:00
if ( pCur - > nPrefix < = pCur - > szRow
& & sqlite3_strnicmp ( pCur - > zPrefix , pCur - > zCurrentRow , pCur - > nPrefix ) = = 0
) {
2017-07-11 01:38:45 +00:00
break ;
}
2017-07-11 00:09:44 +00:00
}
2017-07-11 01:38:45 +00:00
2017-07-11 00:09:44 +00:00
return SQLITE_OK ;
}
/*
* * Return values of columns for the row at which the completion_cursor
* * is currently pointing .
*/
static int completionColumn (
sqlite3_vtab_cursor * cur , /* The cursor */
sqlite3_context * ctx , /* First argument to sqlite3_result_...() */
int i /* Which column to return */
) {
completion_cursor * pCur = ( completion_cursor * ) cur ;
switch ( i ) {
case COMPLETION_COLUMN_CANDIDATE : {
2018-04-25 19:02:48 +00:00
sqlite3_result_text ( ctx , pCur - > zCurrentRow , pCur - > szRow , SQLITE_TRANSIENT ) ;
2017-07-11 00:09:44 +00:00
break ;
}
case COMPLETION_COLUMN_PREFIX : {
sqlite3_result_text ( ctx , pCur - > zPrefix , - 1 , SQLITE_TRANSIENT ) ;
break ;
}
case COMPLETION_COLUMN_WHOLELINE : {
sqlite3_result_text ( ctx , pCur - > zLine , - 1 , SQLITE_TRANSIENT ) ;
break ;
}
2017-07-11 02:05:21 +00:00
case COMPLETION_COLUMN_PHASE : {
sqlite3_result_int ( ctx , pCur - > ePhase ) ;
break ;
}
2017-07-11 00:09:44 +00:00
}
return SQLITE_OK ;
}
/*
* * Return the rowid for the current row . In this implementation , the
* * rowid is the same as the output value .
*/
static int completionRowid ( sqlite3_vtab_cursor * cur , sqlite_int64 * pRowid ) {
completion_cursor * pCur = ( completion_cursor * ) cur ;
* pRowid = pCur - > iRowid ;
return SQLITE_OK ;
}
/*
* * Return TRUE if the cursor has been moved off of the last
* * row of output .
*/
static int completionEof ( sqlite3_vtab_cursor * cur ) {
completion_cursor * pCur = ( completion_cursor * ) cur ;
2017-07-11 01:38:45 +00:00
return pCur - > ePhase > = COMPLETION_EOF ;
2017-07-11 00:09:44 +00:00
}
/*
* * This method is called to " rewind " the completion_cursor object back
* * to the first row of output . This method is always called at least
* * once prior to any call to completionColumn ( ) or completionRowid ( ) or
* * completionEof ( ) .
*/
static int completionFilter (
sqlite3_vtab_cursor * pVtabCursor ,
int idxNum , const char * idxStr ,
int argc , sqlite3_value * * argv
) {
completion_cursor * pCur = ( completion_cursor * ) pVtabCursor ;
int iArg = 0 ;
2017-07-11 18:11:33 +00:00
( void ) ( idxStr ) ; /* Unused parameter */
( void ) ( argc ) ; /* Unused parameter */
2017-07-11 00:09:44 +00:00
completionCursorReset ( pCur ) ;
if ( idxNum & 1 ) {
pCur - > nPrefix = sqlite3_value_bytes ( argv [ iArg ] ) ;
if ( pCur - > nPrefix > 0 ) {
pCur - > zPrefix = sqlite3_mprintf ( " %s " , sqlite3_value_text ( argv [ iArg ] ) ) ;
if ( pCur - > zPrefix = = 0 ) return SQLITE_NOMEM ;
}
2018-08-21 12:16:33 +00:00
iArg = 1 ;
2017-07-11 00:09:44 +00:00
}
if ( idxNum & 2 ) {
pCur - > nLine = sqlite3_value_bytes ( argv [ iArg ] ) ;
if ( pCur - > nLine > 0 ) {
pCur - > zLine = sqlite3_mprintf ( " %s " , sqlite3_value_text ( argv [ iArg ] ) ) ;
if ( pCur - > zLine = = 0 ) return SQLITE_NOMEM ;
}
}
if ( pCur - > zLine ! = 0 & & pCur - > zPrefix = = 0 ) {
int i = pCur - > nLine ;
while ( i > 0 & & ( isalnum ( pCur - > zLine [ i - 1 ] ) | | pCur - > zLine [ i - 1 ] = = ' _ ' ) ) {
i - - ;
}
pCur - > nPrefix = pCur - > nLine - i ;
if ( pCur - > nPrefix > 0 ) {
pCur - > zPrefix = sqlite3_mprintf ( " %.*s " , pCur - > nPrefix , pCur - > zLine + i ) ;
if ( pCur - > zPrefix = = 0 ) return SQLITE_NOMEM ;
}
}
pCur - > iRowid = 0 ;
pCur - > ePhase = COMPLETION_FIRST_PHASE ;
return completionNext ( pVtabCursor ) ;
}
/*
* * SQLite will invoke this method one or more times while planning a query
* * that uses the completion virtual table . This routine needs to create
* * a query plan for each invocation and compute an estimated cost for that
* * plan .
* *
* * There are two hidden parameters that act as arguments to the table - valued
* * function : " prefix " and " wholeline " . Bit 0 of idxNum is set if " prefix "
* * is available and bit 1 is set if " wholeline " is available .
*/
static int completionBestIndex (
sqlite3_vtab * tab ,
sqlite3_index_info * pIdxInfo
) {
int i ; /* Loop over constraints */
int idxNum = 0 ; /* The query plan bitmask */
int prefixIdx = - 1 ; /* Index of the start= constraint, or -1 if none */
int wholelineIdx = - 1 ; /* Index of the stop= constraint, or -1 if none */
int nArg = 0 ; /* Number of arguments that completeFilter() expects */
const struct sqlite3_index_constraint * pConstraint ;
2017-07-11 18:11:33 +00:00
( void ) ( tab ) ; /* Unused parameter */
2017-07-11 00:09:44 +00:00
pConstraint = pIdxInfo - > aConstraint ;
for ( i = 0 ; i < pIdxInfo - > nConstraint ; i + + , pConstraint + + ) {
if ( pConstraint - > usable = = 0 ) continue ;
if ( pConstraint - > op ! = SQLITE_INDEX_CONSTRAINT_EQ ) continue ;
switch ( pConstraint - > iColumn ) {
case COMPLETION_COLUMN_PREFIX :
prefixIdx = i ;
idxNum | = 1 ;
break ;
case COMPLETION_COLUMN_WHOLELINE :
wholelineIdx = i ;
idxNum | = 2 ;
break ;
}
}
if ( prefixIdx > = 0 ) {
pIdxInfo - > aConstraintUsage [ prefixIdx ] . argvIndex = + + nArg ;
pIdxInfo - > aConstraintUsage [ prefixIdx ] . omit = 1 ;
}
if ( wholelineIdx > = 0 ) {
pIdxInfo - > aConstraintUsage [ wholelineIdx ] . argvIndex = + + nArg ;
pIdxInfo - > aConstraintUsage [ wholelineIdx ] . omit = 1 ;
}
pIdxInfo - > idxNum = idxNum ;
pIdxInfo - > estimatedCost = ( double ) 5000 - 1000 * nArg ;
pIdxInfo - > estimatedRows = 500 - 100 * nArg ;
return SQLITE_OK ;
}
/*
* * This following structure defines all the methods for the
* * completion virtual table .
*/
static sqlite3_module completionModule = {
0 , /* iVersion */
0 , /* xCreate */
completionConnect , /* xConnect */
completionBestIndex , /* xBestIndex */
completionDisconnect , /* xDisconnect */
0 , /* xDestroy */
completionOpen , /* xOpen - open a cursor */
completionClose , /* xClose - close a cursor */
completionFilter , /* xFilter - configure scan constraints */
completionNext , /* xNext - advance a cursor */
completionEof , /* xEof - check for end of scan */
completionColumn , /* xColumn - read data */
completionRowid , /* xRowid - read data */
0 , /* xUpdate */
0 , /* xBegin */
0 , /* xSync */
0 , /* xCommit */
0 , /* xRollback */
0 , /* xFindMethod */
0 , /* xRename */
2017-07-11 18:11:33 +00:00
0 , /* xSavepoint */
0 , /* xRelease */
2018-11-05 23:01:45 +00:00
0 , /* xRollbackTo */
2023-10-06 12:51:05 +00:00
0 , /* xShadowName */
0 /* xIntegrity */
2017-07-11 00:09:44 +00:00
} ;
# endif /* SQLITE_OMIT_VIRTUALTABLE */
int sqlite3CompletionVtabInit ( sqlite3 * db ) {
int rc = SQLITE_OK ;
# ifndef SQLITE_OMIT_VIRTUALTABLE
rc = sqlite3_create_module ( db , " completion " , & completionModule , 0 ) ;
# endif
return rc ;
}
# ifdef _WIN32
__declspec ( dllexport )
# endif
int sqlite3_completion_init (
sqlite3 * db ,
char * * pzErrMsg ,
const sqlite3_api_routines * pApi
) {
int rc = SQLITE_OK ;
SQLITE_EXTENSION_INIT2 ( pApi ) ;
2017-07-11 18:11:33 +00:00
( void ) ( pzErrMsg ) ; /* Unused parameter */
2017-07-11 00:09:44 +00:00
# ifndef SQLITE_OMIT_VIRTUALTABLE
rc = sqlite3CompletionVtabInit ( db ) ;
# endif
return rc ;
}