]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add support for tab-completion (using the ext/misc/completion.c virtual table)
authordrh <drh@noemail.net>
Tue, 11 Jul 2017 13:59:07 +0000 (13:59 +0000)
committerdrh <drh@noemail.net>
Tue, 11 Jul 2017 13:59:07 +0000 (13:59 +0000)
to the command-line shell.

FossilOrigin-Name: 95cd1d9f8baa6be305c9a8bfa26fef2a403f2d5b3b5c9c55382ec04f0bc98d40

ext/misc/completion.c
manifest
manifest.uuid
src/shell.c
src/shell.c.in

index a3189ad154897d9c56aebee7d6479b978ed46e7e..8dc5d7dbafd514e63d7c3def2e10deb9f386d550 100644 (file)
 ** faster than any human can type.
 **
 */
-#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_COMPLETIONVTAB)
-#if !defined(SQLITEINT_H)
 #include "sqlite3ext.h"
-#endif
 SQLITE_EXTENSION_INIT1
 #include <assert.h>
 #include <string.h>
@@ -495,7 +492,6 @@ int sqlite3CompletionVtabInit(sqlite3 *db){
   return rc;
 }
 
-#ifndef SQLITE_CORE
 #ifdef _WIN32
 __declspec(dllexport)
 #endif
@@ -511,5 +507,3 @@ int sqlite3_completion_init(
 #endif
   return rc;
 }
-#endif /* SQLITE_CORE */
-#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_COMPLETIONVTAB) */
index 056c17b4478d946a2ea2a84aba050a52e824fb03..c0ffe7578b61cdc34c1200fa8fa4bed2c9efd4f0 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Change\sthe\ssrc/shell.c\sfile\sso\sthat\sit\sis\sgenerated\sfrom\sa\snew\sscript\sat\ntool/mkshellc.tcl\sand\sthe\stemplate\sfile\ssrc/shell.c.in,\sand\sautomatically\nincludes\sthe\sextensions\sit\sneeds\sout\sof\sext/misc.
-D 2017-07-11T13:34:40.462
+C Add\ssupport\sfor\stab-completion\s(using\sthe\sext/misc/completion.c\svirtual\stable)\nto\sthe\scommand-line\sshell.
+D 2017-07-11T13:59:07.400
 F Makefile.in 081e48dfe7f995d57ce1a88ddf4d2917b4349158648a6cd45b42beae30de3a12
 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
 F Makefile.msc 20850e3e8d4d4791e0531955852d768eb06f24138214870d543abb1a47346fba
@@ -256,7 +256,7 @@ F ext/misc/amatch.c 6db4607cb17c54b853a2d7c7c36046d004853f65b9b733e6f019d543d5df
 F ext/misc/anycollseq.c 5ffdfde9829eeac52219136ad6aa7cd9a4edb3b15f4f2532de52f4a22525eddb
 F ext/misc/carray.c 40c27641010a4dc67e3690bdb7c9d36ca58b3c2d
 F ext/misc/closure.c 0d2a038df8fbae7f19de42e7c7d71f2e4dc88704
-F ext/misc/completion.c 637c016977809c2c3174472e7a416c9dbefc89dadb0efbd33f1516114b43900e
+F ext/misc/completion.c 821141b02a0313ce7dbe72f0260ef755f81d4b57eacb799864e4c40ad5ceb3f3
 F ext/misc/compress.c 122faa92d25033d6c3f07c39231de074ab3d2e83
 F ext/misc/csv.c d91c0388445b08f6e373dd0e8fc024d4551b1fcaf64e876a1c3f4fac8a63adc2
 F ext/misc/dbdump.c 3509fa6b8932d04e932d6b6b827b6a82ca362781b8e8f3c77336f416793e215e
@@ -450,8 +450,8 @@ F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384
 F src/resolve.c 6aa1fb1212e601f65b983ee1215d69a591986c8f97a3805c425c625a53839539
 F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
 F src/select.c 6aa7e8ee53dcb9d0b77b05670bb44a1076d6747bc9b2c1f12a365b553ab6c828
-F src/shell.c 71a338b3a29502a4a7290fa646bc4547d911777aba55ebc040b5940786435833
-F src/shell.c.in 9d75c83c13fb1b96e60c56cce96146d84c4c8b3d18d11bd85e0759ebf744bcb2
+F src/shell.c 19726a6f28f66aa0488f5e9c7aacc399f2bbcd934bffc3a92a5de6d365389eee
+F src/shell.c.in 18510e3fdd9216a6183ff93a48cac7d8d253d39f17750af597d90d3c5f27fc34
 F src/sqlite.h.in 03a422ba13da1dfef7f1aaa1ba344acf18dc867112620b1fdb2a1426cabba634
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h 58fd0676d3111d02e62e5a35992a7d3da5d3f88753acc174f2d37b774fbbdd28
@@ -1631,7 +1631,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 724819b456ca7a5ca6d68415b3b3554741c3ddbb5c416cb6b6b5fc54177f426e
-R 60a762ef308a5ae7a380cb2ce07b0a0c
+P 17e0bb12d82b510b86b6886b9fd0faf39b60b1374027344f89d7b89a32b842b9
+R 951f8f3a9284f3001be7f39a25c50055
 U drh
-Z 1a54bd7eb9eb8ff312901d65383e16cf
+Z 55996074e61f063450435131cf82f298
index b24374cf1b467a3d24ff3b7f39db157ee5110b10..7a51d0481c05298f20e8be6e94f31451812c8961 100644 (file)
@@ -1 +1 @@
-17e0bb12d82b510b86b6886b9fd0faf39b60b1374027344f89d7b89a32b842b9
\ No newline at end of file
+95cd1d9f8baa6be305c9a8bfa26fef2a403f2d5b3b5c9c55382ec04f0bc98d40
\ No newline at end of file
index 8a10fc02b6d31b2e255572ba1c4af49ffa06f1a9..443304616cd7e90ddd019a02ca17b4a40f31db69 100644 (file)
@@ -1629,6 +1629,517 @@ int sqlite3_fileio_init(
 }
 
 /************************* End ../ext/misc/fileio.c ********************/
+/************************* Begin ../ext/misc/completion.c ******************/
+/*
+** 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.
+**
+** 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.
+**
+*/
+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 */
+  sqlite3_stmt *pStmt;       /* Current statement */
+  sqlite3_int64 iRowid;      /* The rowid */
+  int ePhase;                /* Current phase */
+  int j;                     /* inter-phase counter */
+};
+
+/* Values for ePhase:
+*/
+#define COMPLETION_FIRST_PHASE   1
+#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
+#define COMPLETION_TABLES        8
+#define COMPLETION_COLUMNS       9
+#define COMPLETION_MODULES       10
+#define COMPLETION_EOF           11
+
+/*
+** 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;
+
+/* 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 */
+#define COMPLETION_COLUMN_PHASE     3  /* ePhase - used for debugging only */
+
+  rc = sqlite3_declare_vtab(db,
+      "CREATE TABLE x("
+      "  candidate TEXT,"
+      "  prefix TEXT HIDDEN,"
+      "  wholeline TEXT HIDDEN,"
+      "  phase INT HIDDEN"        /* Used for debugging only */
+      ")");
+  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;
+  pCur->j = 0;
+}
+
+/*
+** Destructor for a completion_cursor.
+*/
+static int completionClose(sqlite3_vtab_cursor *cur){
+  completionCursorReset((completion_cursor*)cur);
+  sqlite3_free(cur);
+  return SQLITE_OK;
+}
+
+/*
+** All SQL keywords understood by SQLite
+*/
+static const char *completionKwrds[] = {
+  "ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ANALYZE", "AND", "AS",
+  "ASC", "ATTACH", "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "BY",
+  "CASCADE", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "COMMIT",
+  "CONFLICT", "CONSTRAINT", "CREATE", "CROSS", "CURRENT_DATE",
+  "CURRENT_TIME", "CURRENT_TIMESTAMP", "DATABASE", "DEFAULT", "DEFERRABLE",
+  "DEFERRED", "DELETE", "DESC", "DETACH", "DISTINCT", "DROP", "EACH",
+  "ELSE", "END", "ESCAPE", "EXCEPT", "EXCLUSIVE", "EXISTS", "EXPLAIN",
+  "FAIL", "FOR", "FOREIGN", "FROM", "FULL", "GLOB", "GROUP", "HAVING", "IF",
+  "IGNORE", "IMMEDIATE", "IN", "INDEX", "INDEXED", "INITIALLY", "INNER",
+  "INSERT", "INSTEAD", "INTERSECT", "INTO", "IS", "ISNULL", "JOIN", "KEY",
+  "LEFT", "LIKE", "LIMIT", "MATCH", "NATURAL", "NO", "NOT", "NOTNULL",
+  "NULL", "OF", "OFFSET", "ON", "OR", "ORDER", "OUTER", "PLAN", "PRAGMA",
+  "PRIMARY", "QUERY", "RAISE", "RECURSIVE", "REFERENCES", "REGEXP",
+  "REINDEX", "RELEASE", "RENAME", "REPLACE", "RESTRICT", "RIGHT",
+  "ROLLBACK", "ROW", "SAVEPOINT", "SELECT", "SET", "TABLE", "TEMP",
+  "TEMPORARY", "THEN", "TO", "TRANSACTION", "TRIGGER", "UNION", "UNIQUE",
+  "UPDATE", "USING", "VACUUM", "VALUES", "VIEW", "VIRTUAL", "WHEN", "WHERE",
+  "WITH", "WITHOUT",
+};
+
+/*
+** Advance a completion_cursor to its next row of output.
+**
+** 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.
+*/
+static int completionNext(sqlite3_vtab_cursor *cur){
+  completion_cursor *pCur = (completion_cursor*)cur;
+  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 */
+  pCur->iRowid++;
+  while( pCur->ePhase!=COMPLETION_EOF ){
+    switch( pCur->ePhase ){
+      case COMPLETION_KEYWORDS: {
+        if( pCur->j >=  sizeof(completionKwrds)/sizeof(completionKwrds[0]) ){
+          pCur->zCurrentRow = 0;
+          pCur->ePhase = COMPLETION_DATABASES;
+        }else{
+          pCur->zCurrentRow = completionKwrds[pCur->j++];
+        }
+        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"
+               "SELECT name FROM \"%w\".sqlite_master"
+               " WHERE type='table'",
+               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;
+        break;
+      }
+      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"
+               "SELECT pti.name FROM \"%w\".sqlite_master AS sm"
+                       " 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;
+        break;
+      }
+    }
+    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);
+      }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;
+    if( sqlite3_strnicmp(pCur->zPrefix, pCur->zCurrentRow, pCur->nPrefix)==0 ){
+      break;
+    }
+  }
+
+  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: {
+      sqlite3_result_text(ctx, pCur->zCurrentRow, -1, SQLITE_TRANSIENT);
+      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;
+    }
+    case COMPLETION_COLUMN_PHASE: {
+      sqlite3_result_int(ctx, pCur->ePhase);
+      break;
+    }
+  }
+  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;
+  return pCur->ePhase >= COMPLETION_EOF;
+}
+
+/*
+** 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;
+  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;
+    }
+    iArg++;
+  }
+  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;
+    }
+    iArg++;
+  }
+  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;
+
+  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 */
+};
+
+#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);
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+  rc = sqlite3CompletionVtabInit(db);
+#endif
+  return rc;
+}
+
+/************************* End ../ext/misc/completion.c ********************/
 
 #if defined(SQLITE_ENABLE_SESSION)
 /*
@@ -3724,12 +4235,77 @@ static void open_db(ShellState *p, int keepAlive){
 #endif
     sqlite3_fileio_init(p->db, 0, 0);
     sqlite3_shathree_init(p->db, 0, 0);
+    sqlite3_completion_init(p->db, 0, 0);
     sqlite3_create_function(p->db, "shell_add_schema", 2, SQLITE_UTF8, 0,
                             shellAddSchemaName, 0, 0);
-                            
   }
 }
 
+#if HAVE_READLINE || HAVE_EDITLINE
+/*
+** Readline completion callbacks
+*/
+static char *readline_completion_generator(const char *text, int state){
+  static sqlite3_stmt *pStmt = 0;
+  char *zRet;
+  if( state==0 ){
+    char *zSql;
+    int rc;
+    sqlite3_finalize(pStmt);
+    zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase"
+                           "  FROM completion(%Q) ORDER BY 1", text);
+    sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0);
+    sqlite3_free(zSql);
+  }
+  if( sqlite3_step(pStmt)==SQLITE_ROW ){
+    zRet = strdup(sqlite3_column_text(pStmt, 0));
+  }else{
+    sqlite3_finalize(pStmt);
+    pStmt = 0;
+    zRet = 0;
+  }
+  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, iStart;
+  sqlite3_stmt *pStmt = 0;
+  char *zSql;
+  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;
+  memcpy(zBuf, zLine, iStart);
+  zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase"
+                         "  FROM completion(%Q,%Q) ORDER BY 1",
+                         &zLine[iStart], zLine);
+  sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0);
+  sqlite3_free(zSql);
+  sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */
+  while( sqlite3_step(pStmt)==SQLITE_ROW ){
+    const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0);
+    int nCompletion = sqlite3_column_bytes(pStmt, 0);
+    if( iStart+nCompletion < sizeof(zBuf)-1 ){
+      memcpy(zBuf+iStart, zCompletion, nCompletion+1);
+      linenoiseAddCompletion(lc, zBuf);
+    }
+  }
+  sqlite3_finalize(pStmt);
+}
+#endif
+
 /*
 ** Do C-language style dequoting.
 **
@@ -7777,6 +8353,11 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
         }
       }
       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);
index 70d6c0e5e70337015becd8c13c435f098d586911..72d07799ba5a837c383985427565ed436fad864c 100644 (file)
@@ -795,6 +795,7 @@ static void shellAddSchemaName(
 
 INCLUDE ../ext/misc/shathree.c
 INCLUDE ../ext/misc/fileio.c
+INCLUDE ../ext/misc/completion.c
 
 #if defined(SQLITE_ENABLE_SESSION)
 /*
@@ -2890,12 +2891,77 @@ static void open_db(ShellState *p, int keepAlive){
 #endif
     sqlite3_fileio_init(p->db, 0, 0);
     sqlite3_shathree_init(p->db, 0, 0);
+    sqlite3_completion_init(p->db, 0, 0);
     sqlite3_create_function(p->db, "shell_add_schema", 2, SQLITE_UTF8, 0,
                             shellAddSchemaName, 0, 0);
-                            
   }
 }
 
+#if HAVE_READLINE || HAVE_EDITLINE
+/*
+** Readline completion callbacks
+*/
+static char *readline_completion_generator(const char *text, int state){
+  static sqlite3_stmt *pStmt = 0;
+  char *zRet;
+  if( state==0 ){
+    char *zSql;
+    int rc;
+    sqlite3_finalize(pStmt);
+    zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase"
+                           "  FROM completion(%Q) ORDER BY 1", text);
+    sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0);
+    sqlite3_free(zSql);
+  }
+  if( sqlite3_step(pStmt)==SQLITE_ROW ){
+    zRet = strdup(sqlite3_column_text(pStmt, 0));
+  }else{
+    sqlite3_finalize(pStmt);
+    pStmt = 0;
+    zRet = 0;
+  }
+  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, iStart;
+  sqlite3_stmt *pStmt = 0;
+  char *zSql;
+  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;
+  memcpy(zBuf, zLine, iStart);
+  zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase"
+                         "  FROM completion(%Q,%Q) ORDER BY 1",
+                         &zLine[iStart], zLine);
+  sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0);
+  sqlite3_free(zSql);
+  sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */
+  while( sqlite3_step(pStmt)==SQLITE_ROW ){
+    const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0);
+    int nCompletion = sqlite3_column_bytes(pStmt, 0);
+    if( iStart+nCompletion < sizeof(zBuf)-1 ){
+      memcpy(zBuf+iStart, zCompletion, nCompletion+1);
+      linenoiseAddCompletion(lc, zBuf);
+    }
+  }
+  sqlite3_finalize(pStmt);
+}
+#endif
+
 /*
 ** Do C-language style dequoting.
 **
@@ -6943,6 +7009,11 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
         }
       }
       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);