Index: main.mk ================================================================== --- main.mk +++ main.mk @@ -472,11 +472,11 @@ # TESTOPTS = --verbose=file --output=test-out.txt # Extra compiler options for various shell tools # -SHELL_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS5 +SHELL_OPT += -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS5 SHELL_OPT += -DSQLITE_ENABLE_EXPLAIN_COMMENTS SHELL_OPT += -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION SHELL_OPT += -DSQLITE_ENABLE_STMTVTAB FUZZERSHELL_OPT = -DSQLITE_ENABLE_JSON1 FUZZCHECK_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5 Index: src/hash.h ================================================================== --- src/hash.h +++ src/hash.h @@ -89,8 +89,8 @@ /* #define sqliteHashKeysize(E) ((E)->nKey) // NOT USED */ /* ** Number of entries in a hash table */ -/* #define sqliteHashCount(H) ((H)->count) // NOT USED */ +#define sqliteHashCount(H) ((H)->count) #endif /* SQLITE_HASH_H */ Index: src/shell.c ================================================================== --- src/shell.c +++ src/shell.c @@ -3597,10 +3597,68 @@ shellAddSchemaName, 0, 0); } } +#if HAVE_READLINE || HAVE_EDITLINE +/* +** Readline completion callbacks +*/ +static char *readline_completion_generator(const char *text, int state){ + static char **azCompletions = 0; + static int iCompletion = 0; + char *zRet; + if( state==0 ){ + sqlite3_free(azCompletions); + sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */ + azCompletions = sqlite3_namelist(globalDb, text, -1, 0); + iCompletion = 0; + } + zRet = azCompletions[iCompletion++]; + if( zRet==0 ){ + sqlite3_free(azCompletions); + azCompletions = 0; + }else{ + zRet = strdup(zRet); + } + return zRet; +} +static char **readline_completion(const char *zText, int iStart, int iEnd){ + rl_attempted_completion_over = 1; + return rl_completion_matches(zText, readline_completion_generator); +} + +#elif HAVE_LINENOISE +/* +** Linenoise completion callback +*/ +static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){ + int nLine = (int)strlen(zLine); + int i, n, iStart; + char **az; + char zBuf[1000]; + if( nLine>sizeof(zBuf)-30 ) return; + if( zLine[0]=='.' ) return; + for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){} + if( i==nLine-1 ) return; + iStart = i+1; + sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */ + az = sqlite3_namelist(globalDb, &zLine[iStart], -1, &n); + if( n>0 ){ + qsort(az, n, sizeof(az[0]),(int(*)(const void*,const void*))sqlite3_stricmp); + memcpy(zBuf, zLine, iStart); + for(i=0; az[i]; i++){ + n = (int)strlen(az[i]); + if( iStart+n+1 >= sizeof(zBuf) ) continue; + memcpy(zBuf+iStart, az[i], n+1); + linenoiseAddCompletion(lc, zBuf); + } + } + sqlite3_free(az); +} +#endif + /* ** Do C-language style dequoting. ** ** \a -> alarm ** \b -> backspace @@ -7634,10 +7692,15 @@ if( (zHistory = malloc(nHistory))!=0 ){ sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome); } } if( zHistory ){ shell_read_history(zHistory); } +#if HAVE_READLINE || HAVE_EDITLINE + rl_attempted_completion_function = readline_completion; +#elif HAVE_LINENOISE + linenoiseSetCompletionCallback(linenoise_completion); +#endif rc = process_input(&data, 0); if( zHistory ){ shell_stifle_history(100); shell_write_history(zHistory); free(zHistory); Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -8467,10 +8467,52 @@ int sqlite3_preupdate_count(sqlite3 *); int sqlite3_preupdate_depth(sqlite3 *); int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **); #endif +/* +** CAPI3REF: List Of Symbolic Names +** +** ^The sqlite3_namelist(D,P,F,N) interface returns a list of symbolic names +** and keywords that might appear in valid SQL statements for [connection] +** D and that all begin with the prefix P. ^The F parameter is a bitmask +** that determines the kinds of symbolic names and keywords that are found +** in the list. ^Matching against the prefix P is performed using +** [sqlite3_strnicmp()] and is thus case insensitive for ASCII characters. +** ^If the pointer N is not NULL, then the number of matching names is +** stored in *N. +** +** ^The value returned by sqlite3_namelist() is stored in memory obtained +** from [sqlite3_malloc()] and must be released by the caller using +** [sqlite3_free()]. ^The sqlite3_namelist() interface returns NULL if a +** memory allocation error occurs. +** +** ^Elements of the list are unique but are in no particular order. +** +** This interface is intended to facilitate tab-completion for interactive +** interfaces for SQLite. +*/ +char **sqlite3_namelist(sqlite3 *db, const char *zPrefix, int typeMask, int*); + +/* +** CAPI3REF: Symbolic Name Typemask Values +** +** ^These bitmask values are used to determine the kinds of symbolic names +** and keywords that are returned by [sqlite3_namelist()]. The third +** parameter to [sqlite3_namelist()] should be an OR-ed combination of +** one or more of the following value. +*/ +#define SQLITE_NAMETYPE_KEYWORD 0x0001 +#define SQLITE_NAMETYPE_SCHEMA 0x0002 +#define SQLITE_NAMETYPE_TABLE 0x0004 +#define SQLITE_NAMETYPE_INDEX 0x0008 +#define SQLITE_NAMETYPE_TRIGGER 0x0010 +#define SQLITE_NAMETYPE_FUNCTION 0x0020 +#define SQLITE_NAMETYPE_MODULE 0x0040 +#define SQLITE_NAMETYPE_COLLATION 0x0080 +#define SQLITE_NAMETYPE_COLUMN 0x0100 + /* ** CAPI3REF: Low-level system error code ** ** ^Attempt to return the underlying operating system error code or error ** number that caused the most recent I/O error or failure to open a file. Index: src/tclsqlite.c ================================================================== --- src/tclsqlite.c +++ src/tclsqlite.c @@ -1848,33 +1848,32 @@ "cache", "changes", "close", "collate", "collation_needed", "commit_hook", "complete", "copy", "enable_load_extension", "errorcode", "eval", "exists", "function", "incrblob", "interrupt", - "last_insert_rowid", "nullvalue", "onecolumn", - "preupdate", "profile", "progress", - "rekey", "restore", "rollback_hook", - "status", "timeout", "total_changes", - "trace", "trace_v2", "transaction", - "unlock_notify", "update_hook", "version", - "wal_hook", - 0 + "last_insert_rowid", "namelist", "nullvalue", + "onecolumn", "preupdate", "profile", + "progress", "rekey", "restore", + "rollback_hook", "status", "timeout", + "total_changes", "trace", "trace_v2", + "transaction", "unlock_notify", "update_hook", + "version", "wal_hook", 0 }; enum DB_enum { DB_AUTHORIZER, DB_BACKUP, DB_BUSY, DB_CACHE, DB_CHANGES, DB_CLOSE, DB_COLLATE, DB_COLLATION_NEEDED, DB_COMMIT_HOOK, DB_COMPLETE, DB_COPY, DB_ENABLE_LOAD_EXTENSION, DB_ERRORCODE, DB_EVAL, DB_EXISTS, DB_FUNCTION, DB_INCRBLOB, DB_INTERRUPT, - DB_LAST_INSERT_ROWID, DB_NULLVALUE, DB_ONECOLUMN, - DB_PREUPDATE, DB_PROFILE, DB_PROGRESS, - DB_REKEY, DB_RESTORE, DB_ROLLBACK_HOOK, - DB_STATUS, DB_TIMEOUT, DB_TOTAL_CHANGES, - DB_TRACE, DB_TRACE_V2, DB_TRANSACTION, - DB_UNLOCK_NOTIFY, DB_UPDATE_HOOK, DB_VERSION, - DB_WAL_HOOK, + DB_LAST_INSERT_ROWID, DB_NAMELIST, DB_NULLVALUE, + DB_ONECOLUMN, DB_PREUPDATE, DB_PROFILE, + DB_PROGRESS, DB_REKEY, DB_RESTORE, + DB_ROLLBACK_HOOK, DB_STATUS, DB_TIMEOUT, + DB_TOTAL_CHANGES, DB_TRACE, DB_TRACE_V2, + DB_TRANSACTION, DB_UNLOCK_NOTIFY, DB_UPDATE_HOOK, + DB_VERSION, DB_WAL_HOOK, }; /* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */ if( objc<2 ){ Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ..."); @@ -2662,10 +2661,38 @@ */ case DB_INTERRUPT: { sqlite3_interrupt(pDb->db); break; } + + /* + ** $db namelist PREFIX MASK + */ + case DB_NAMELIST: { + const char *zPrefix; + int mask; + char **azList; + int nList, i; + if( objc!=4 ){ + Tcl_WrongNumArgs(interp, 2, objv, "PREFIX MASK"); + return TCL_ERROR; + } + zPrefix = Tcl_GetString(objv[2]); + if( Tcl_GetIntFromObj(interp, objv[3], &mask) ) return TCL_ERROR; + azList = sqlite3_namelist(pDb->db, zPrefix, mask, &nList); + if( azList ){ + Tcl_Obj *pList = Tcl_NewListObj(nList, 0); + for(i=0; azList[i]; i++){ + Tcl_ListObjAppendElement(interp, pList, + Tcl_NewStringObj(azList[i],-1)); + } + assert( i==nList ); + sqlite3_free(azList); + Tcl_SetObjResult(interp, pList); + } + break; + } /* ** $db nullvalue ?STRING? ** ** Change text used when a NULL comes back from the database. If ?STRING? Index: src/tokenize.c ================================================================== --- src/tokenize.c +++ src/tokenize.c @@ -612,5 +612,154 @@ sqlite3DeleteTable(db, p); } assert( nErr==0 || pParse->rc!=SQLITE_OK ); return nErr; } + +/* An instance of the following object is used to accumulate a list +** of names for the sqlite3_namelist() interface +*/ +struct NameAccum { + Hash x; /* Hash table of all names */ + const char *zPrefix; /* All names must start with this prefix */ + int nPrefix; /* Size of the prefix in bytes */ +}; + +/* +** Examine zName to see if it belongs on the list (if it has a matching +** prefix) and add it to the list if it belongs. nName is the length +** of the name in bytes, or -1 if zName is zero-terminated. +*/ +static void addName(struct NameAccum *p, const char *zName, int nName){ + char *zCopy; + char *zOld; + if( nName<0 ) nName = sqlite3Strlen30(zName); + if( nNamenPrefix ) return; + if( sqlite3StrNICmp(p->zPrefix, zName, p->nPrefix)!=0 ) return; + zCopy = sqlite3_mprintf("%.*s", nName, zName); + zOld = sqlite3HashInsert(&p->x, zCopy, zCopy); + sqlite3_free(zOld); +} + +/* +** Return a list of text words or identifiers that might appear in an +** SQL statement. The typeMask parameter is a bitmask that determines +** specifically what kinds of names should be returned. All names +** returned must begin with zPrefix. +** +** The returned list is from a single memory allocation. The caller owns +** the allocation and should release it using sqlite3_free() when it has +** finished using the list. +** +** The returned list is an array of pointers to strings. The list is +** terminated by a single NULL pointer. +*/ +char **sqlite3_namelist( + sqlite3 *db, /* Database from which to extract names */ + const char *zPrefix, /* Only extract names matching this prefix */ + int typeMask, /* Mask of the types of names to extract */ + int *pCount /* Write the number of names here */ +){ + struct NameAccum x; + int i, n, nEntry; + HashElem *j; + char **azAns; + char *zFree; + x.zPrefix = zPrefix; + x.nPrefix = sqlite3Strlen30(zPrefix); + sqlite3HashInit(&x.x); + if( typeMask & SQLITE_NAMETYPE_KEYWORD ){ + for(i=0; iu.pHash){ + addName(&x, p->zName, -1); + } + } + for(j=sqliteHashFirst(&db->aFunc); j; j=sqliteHashNext(j)){ + FuncDef *p = (FuncDef*)sqliteHashData(j); + addName(&x, p->zName, -1); + } + } + if( typeMask & SQLITE_NAMETYPE_COLLATION ){ + for(j=sqliteHashFirst(&db->aCollSeq); j; j=sqliteHashNext(j)){ + CollSeq *p = (CollSeq*)sqliteHashData(j); + addName(&x, p->zName, -1); + } + } + if( typeMask & SQLITE_NAMETYPE_MODULE ){ + for(j=sqliteHashFirst(&db->aModule); j; j=sqliteHashNext(j)){ + Module *p = (Module*)sqliteHashData(j); + addName(&x, p->zName, -1); + } + } + if( typeMask & (SQLITE_NAMETYPE_SCHEMA| + SQLITE_NAMETYPE_TABLE| + SQLITE_NAMETYPE_INDEX| + SQLITE_NAMETYPE_TRIGGER| + SQLITE_NAMETYPE_COLUMN) ){ + int iDb; + for(iDb=0; iDbnDb; iDb++){ + Db *pDb = &db->aDb[iDb]; + if( typeMask & SQLITE_NAMETYPE_SCHEMA ){ + addName(&x, pDb->zDbSName, -1); + } + if( typeMask & (SQLITE_NAMETYPE_TABLE|SQLITE_NAMETYPE_COLUMN) ){ + for(j=sqliteHashFirst(&pDb->pSchema->tblHash); j; j=sqliteHashNext(j)){ + Table *p = (Table*)sqliteHashData(j); + if( typeMask & SQLITE_NAMETYPE_TABLE ){ + addName(&x, p->zName, -1); + } + if( typeMask & SQLITE_NAMETYPE_COLUMN ){ + int k; + for(k=0; knCol; k++){ + addName(&x, p->aCol[k].zName, -1); + } + } + } + } + if( typeMask & SQLITE_NAMETYPE_INDEX ){ + for(j=sqliteHashFirst(&pDb->pSchema->idxHash); j; j=sqliteHashNext(j)){ + Index *p = (Index*)sqliteHashData(j); + addName(&x, p->zName, -1); + } + } + if( typeMask & SQLITE_NAMETYPE_TRIGGER ){ + for(j=sqliteHashFirst(&pDb->pSchema->trigHash); j; j=sqliteHashNext(j)){ + Trigger *p = (Trigger*)sqliteHashData(j); + addName(&x, p->zName, -1); + } + } + } + if( typeMask & SQLITE_NAMETYPE_COLUMN ){ + addName(&x, "rowid", -1); + } + } + nEntry = sqliteHashCount(&x.x); + if( pCount ) *pCount = nEntry; + n = 0; + for(j=sqliteHashFirst(&x.x); j; j=sqliteHashNext(j)){ + n += sqlite3Strlen30((const char*)sqliteHashData(j)); + } + zFree = sqlite3_malloc( (nEntry+1)*sizeof(char*) + n + nEntry ); + azAns = (char**)zFree; + if( zFree ){ + memset(zFree, 0, (nEntry+1)*sizeof(char*) + n + nEntry ); + zFree += (nEntry+1)*sizeof(char*); + for(i=0, j=sqliteHashFirst(&x.x); j; i++, j=sqliteHashNext(j)){ + const char *zName = (const char*)sqliteHashData(j); + azAns[i] = zFree; + n = sqlite3Strlen30(zName); + memcpy(zFree, zName, n+1); + zFree += n+1; + } + } + for(j=sqliteHashFirst(&x.x); j; j=sqliteHashNext(j)){ + sqlite3_free(sqliteHashData(j)); + } + sqlite3HashClear(&x.x); + return azAns; +}