static void shell_check_oom(const void *p){
if( p==0 ) shell_out_of_memory();
}
++/* Check a SQLite result code for out-of-memory indication.
++** If that is so, terminate with an out-of-memory error.
++*/
++static void shell_check_nomem(int rc){
++ if( SQLITE_NOMEM==rc ) shell_out_of_memory();
++}
+/* This pattern is ubiquitous and subject to change, so encapsulate it. */
+#define SHELL_ASSIGN_OOM_CHECK(lv, pv) \
+ do{ lv = pv; shell_check_oom(lv); }while(0)
+
+static void shell_newstr_assign(char **pLV, char *z){
+ if( !z ) shell_out_of_memory();
+ *pLV = z;
+}
/*
** Write I/O traces to the following stream.
#endif
#if SQLITE_SHELL_HAVE_RECOVER
INCLUDE ../ext/recover/sqlite3recover.h
+ # ifndef SQLITE_HAVE_SQLITE3R
INCLUDE ../ext/recover/dbdata.c
INCLUDE ../ext/recover/sqlite3recover.c
-# endif /* SQLITE_HAVE_SQLITE3R */
++# endif /* !defined(SQLITE_HAVE_SQLITE3R) */
#endif
#ifdef SQLITE_SHELL_EXTSRC
# include SHELL_STRINGIFY(SQLITE_SHELL_EXTSRC)
char zPrefix[100]; /* Graph prefix */
};
- DotCommand *pUnknown; /* .unknown registered for this extension */
+/* By default, omit the extension options that are not done yet.
+ * "SHELL_EXTENSIONS" is short for "Some shell extensions are built in." */
+#ifndef SHELL_OMIT_EXTENSIONS
+# define SHELL_OMIT_EXTENSIONS 4
+# define SHELL_EXTENSIONS 1
+#else
+# define SHELL_EXTENSIONS \
+ (0!=((~SHELL_OMIT_EXTENSIONS) & SHELL_ALL_EXTENSIONS))
+#endif
+
+#ifdef SQLITE_OMIT_LOAD_EXTENSION
+# define SHELL_OMIT_LOAD_EXTENSION 1
+#else
+# define SHELL_OMIT_LOAD_EXTENSION 0
+#endif
+
+/* Selectively omit features with one PP variable. Value is true iff
+** either x is not defined or defined with 0 in bitnum bit position.
+*/
+#define NOT_IFDEF_BIT(x,bitnum) (x? (!(x & (1<<bitnum))) : !(x+0))
+
+/* Whether build will include extended input parsing option */
+#define SHEXT_PARSING_BIT 0
+#define SHELL_EXTENDED_PARSING \
+ NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_PARSING_BIT)
+/* Whether build will include runtime extension via .shxload */
+#define SHEXT_DYNEXT_BIT 1
+#define SHELL_DYNAMIC_EXTENSION ( !SHELL_OMIT_LOAD_EXTENSION \
+ && NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_DYNEXT_BIT) )
+/* Whether build will include expansion of variables in dot-commands */
+#define SHEXT_VAREXP_BIT 2
+#define SHELL_VARIABLE_EXPANSION \
+ NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_VAREXP_BIT)
+
+#define SHELL_ALL_EXTENSIONS \
+ (1<<SHEXT_PARSING_BIT)+(1<<SHEXT_DYNEXT_BIT)+(1<<SHEXT_VAREXP_BIT)
+
+/* Runtime test for shell extended parsing, given ShellInState pointer */
+#if SHELL_EXTENDED_PARSING
+# define SHEXT_PARSING(psi) ((psi->bExtendedDotCmds&(1<<SHEXT_PARSING_BIT))!=0)
+#else
+# define SHEXT_PARSING(psi) 0
+#endif
+
+/* Runtime test for shell variable expansion, given ShellInState pointer */
+#if SHELL_EXTENDED_PARSING
+# define SHEXT_VAREXP(psi) ((psi->bExtendedDotCmds&(1<<SHEXT_VAREXP_BIT))!=0)
+#else
+# define SHEXT_VAREXP(psi) 0
+#endif
+
+/* Enable use of ExportHandler and ImportHandler interfaces for built-in I/O */
+#define SHELL_DATAIO_EXT 1
+
+#if SHELL_DYNAMIC_EXTENSION
+
+/* This is only used to support extensions that need this information.
+ * For example, they might need to locate and load related files. */
+# if defined(_WIN32) || defined(WIN32)
+static char startupDir[MAX_PATH+1] = {0};
+# define initStartupDir() (_getcwd(startupDir,MAX_PATH)!=0)
+# define IS_PATH_SEP(c) ((c)=='/'||(c)=='\\')
+# else
+static char startupDir[PATH_MAX+1] = {0};
+# define initStartupDir() (getcwd(startupDir, sizeof(startupDir))!=0)
+ /* Above useless expression avoids an "unused result" warning. */
+# define IS_PATH_SEP(c) ((c)=='/')
+# endif
+
+# ifndef SHELL_OMIT_EXTBYNAME
+/* Is a program invocation name one used for a shell to start as extensible? */
+static int isExtendedBasename(const char *zPgm){
+ int ixe = (zPgm)? (int)strlen(zPgm)-1 : 0;
+ if( ixe==0 ) return 0;
+ while( ixe>=0 && !IS_PATH_SEP(zPgm[ixe]) ) --ixe;
+ /* ixe is just before the basename with extension(s) */
+ return sqlite3_strnicmp(&zPgm[ixe+1], "sqlite3x", 8)==0;
+}
+# else
+# define isExtendedBasename(pathname) 0
+# endif
+
+/* Tracking and use info for loaded shell extensions
+ * An instance is kept for each shell extension that is currently loaded.
+ * They are kept in a simple list (aka dynamic array), index into which
+ * is used internally to get the extension's object. These indices are
+ * kept in the dbShell and updated there as the list content changes.
+ */
+typedef struct ShExtInfo {
+ ExtensionId extId; /* The xInit function pointer */
+ void (*extDtor)(void *); /* Extension shutdown on exit or unload */
+ void *pvExtObj; /* Passed to extDtor(...) at shutdown */
+ /* Each shell extension library registers 0 or more of its extension
+ * implementations, interfaces to which are kept in below dynamic.
+ * arrays. The dbShell DB keeps indices into these arrays and into
+ * an array of this struct's instances to facilitate lookup by name
+ * of pointers to the implementations. */
+ int numDotCommands;
+ DotCommand **ppDotCommands;
+ int numExportHandlers;
+ ExportHandler **ppExportHandlers;
+ int numImportHandlers;
+ ImportHandler **ppImportHandlers;
++ DotCommand *pUnknown; /* .unknown registered for this extension (unowned) */
+} ShExtInfo;
++#define SHEXT_INFO_INIT {0,0,NULL, 0,NULL, 0,NULL, 0,NULL, NULL}
+#endif
+
/* Parameters affecting columnar mode result display (defaulting together) */
typedef struct ColModeOpts {
int iWrap; /* In columnar modes, wrap lines reaching this limit */
/* Reflect the use or absence of --unsafe-testing invocation. */
{
- int testmode_on = ShellHasFlag(p,SHFLG_TestingMode);
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_TRUSTED_SCHEMA, testmode_on,0);
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0);
+ int testmode_on = ShellHasFlag(psx,SHFLG_TestingMode);
+ sqlite3_db_config(globalDb, SQLITE_DBCONFIG_TRUSTED_SCHEMA,testmode_on,0);
+ sqlite3_db_config(globalDb, SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0);
}
-
#ifndef SQLITE_OMIT_LOAD_EXTENSION
- sqlite3_enable_load_extension(p->db, 1);
-#endif
- sqlite3_shathree_init(p->db, 0, 0);
- sqlite3_uint_init(p->db, 0, 0);
- sqlite3_decimal_init(p->db, 0, 0);
- sqlite3_base64_init(p->db, 0, 0);
- sqlite3_base85_init(p->db, 0, 0);
- sqlite3_regexp_init(p->db, 0, 0);
- sqlite3_ieee_init(p->db, 0, 0);
- sqlite3_series_init(p->db, 0, 0);
+ sqlite3_enable_load_extension(globalDb, 1);
+#endif
+ sqlite3_shathree_init(globalDb, 0, 0);
+ sqlite3_uint_init(globalDb, 0, 0);
+ sqlite3_decimal_init(globalDb, 0, 0);
+ sqlite3_base64_init(globalDb, 0, 0);
+ sqlite3_base85_init(globalDb, 0, 0);
+ sqlite3_regexp_init(globalDb, 0, 0);
+ sqlite3_ieee_init(globalDb, 0, 0);
+ sqlite3_series_init(globalDb, 0, 0);
#ifndef SQLITE_SHELL_FIDDLE
- sqlite3_fileio_init(p->db, 0, 0);
- sqlite3_completion_init(p->db, 0, 0);
+ sqlite3_fileio_init(globalDb, 0, 0);
+ sqlite3_completion_init(globalDb, 0, 0);
#endif
- #if SQLITE_SHELL_HAVE_RECOVER
- sqlite3_dbdata_init(globalDb, 0, 0);
- #endif
#ifdef SQLITE_HAVE_ZLIB
- if( !p->bSafeModePersist ){
- sqlite3_zipfile_init(p->db, 0, 0);
- sqlite3_sqlar_init(p->db, 0, 0);
+ if( !psi->bSafeModeFuture ){
+ sqlite3_zipfile_init(globalDb, 0, 0);
+ sqlite3_sqlar_init(globalDb, 0, 0);
}
#endif
+
#ifdef SQLITE_SHELL_EXTFUNCS
/* Create a preprocessing mechanism for extensions to make
* their own provisions for being built into the shell.
sqlite3_finalize(pStmt);
if( rc!=SQLITE_DONE ) rc_err_oom_die(SQLITE_NOMEM);
}
- assert(db_int(*pDb, zHasDupes)==0); /* Consider: remove this */
- rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0);
- rc_err_oom_die(rc);
- rc = sqlite3_step(pStmt);
- if( rc==SQLITE_ROW ){
- zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
- }else{
- zColsSpec = 0;
+ /* This assert is maybe overly cautious for above de-dup DML, but that can
+ * be replaced via #define's. So this check is made for debug builds. */
+ assert(db_int(*pDb, zHasDupes)==0);
+ rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0);
+ rc_err_oom_die(rc);
+ rc = sqlite3_step(pStmt);
+ if( rc==SQLITE_ROW ){
+ zColsSpec = smprintf("%s", sqlite3_column_text(pStmt, 0));
+ }else{
+ zColsSpec = 0;
+ }
+ if( pzRenamed!=0 ){
+ if( !hasDupes ) *pzRenamed = 0;
+ else{
+ sqlite3_finalize(pStmt);
+ if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0)
+ && SQLITE_ROW==sqlite3_step(pStmt) ){
+ *pzRenamed = smprintf("%s", sqlite3_column_text(pStmt, 0));
+ }else
+ *pzRenamed = 0;
+ }
+ }
+ sqlite3_finalize(pStmt);
+ sqlite3_close(*pDb);
+ *pDb = 0;
+ return zColsSpec;
+ }
+}
+
+#if SHELL_DATAIO_EXT
+
+/*
+** Standard ExportHandlers
+** These implement the built-in renderers of query results.
+** Two are provided, one for freeform results, the other for columnar results.
+*/
+
+static void EH_FF_destruct(ExportHandler *pMe);
+static void EH_CM_destruct(ExportHandler *pMe);
+static const char *EH_FF_name(ExportHandler *pMe);
+static const char *EH_CM_name(ExportHandler *pMe);
+static const char *EH_help(ExportHandler *pMe, const char *zWhat);
+static int EH_openResultsOutStream(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ int numArgs, char *azArgs[],
+ const char * zName);
+static int EH_FF_prependResultsOut(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ sqlite3_stmt *pStmt);
+static int EH_CM_prependResultsOut(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ sqlite3_stmt *pStmt);
+static int EH_FF_rowResultsOut(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ sqlite3_stmt *pStmt);
+static int EH_CM_rowResultsOut(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ sqlite3_stmt *pStmt);
+static int EH_FF_appendResultsOut(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ sqlite3_stmt *pStmt);
+static int EH_CM_appendResultsOut(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ sqlite3_stmt *pStmt);
+static void EH_closeResultsOutStream(ExportHandler *pMe,
+ ShellExState *pSES,
+ char **pzErr);
+
+static VTABLE_NAME(ExportHandler) exporter_Vtab_FF = {
+ EH_FF_destruct,
+ EH_FF_name,
+ EH_help,
+ EH_openResultsOutStream,
+ EH_FF_prependResultsOut,
+ EH_FF_rowResultsOut,
+ EH_FF_appendResultsOut,
+ EH_closeResultsOutStream
+};
+
+static VTABLE_NAME(ExportHandler) exporter_Vtab_CM = {
+ EH_CM_destruct,
+ EH_CM_name,
+ EH_help,
+ EH_openResultsOutStream,
+ EH_CM_prependResultsOut,
+ EH_CM_rowResultsOut,
+ EH_CM_appendResultsOut,
+ EH_closeResultsOutStream
+};
+
+typedef struct {
+ char **azCols; /* Names of result columns */
+ char **azVals; /* Results */
+ int *aiTypes; /* Result types */
+} ColumnsInfo;
+
+typedef struct {
+ VTABLE_NAME(ExportHandler) *pMethods;
+ ShellInState *psi;
+ int nCol;
+ sqlite3_uint64 nRow;
+ void *pData;
+ void *pRowInfo;
+ ColumnsInfo colInfo;
+} BuiltInFFExporter;
+#define BI_FF_EXPORTER_INIT(psi) { & exporter_Vtab_FF, psi, 0, 0, 0, 0 }
+
+typedef struct {
+ VTABLE_NAME(ExportHandler) *pMethods;
+ ShellInState *psi;
+ int nCol;
+ sqlite3_uint64 nRow;
+ void *pData;
+ void *pRowInfo;
+ const char *colSep;
+ const char *rowSep;
+} BuiltInCMExporter;
+#define BI_CM_EXPORTER_INIT(psi) { & exporter_Vtab_CM, psi, 0, 0, 0, 0 }
+
+static void EH_FF_destruct(ExportHandler *pMe){
+ /* This serves two purposes: idempotent reinitialize, and final takedown */
+ BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
+ if( pbie->nCol!=0 ){
+ sqlite3_free(pbie->pData);
+ pbie->pData = 0;
+ }
+ pbie->nRow = 0;
+ pbie->nCol = 0;
+}
+
+static const char *zEmpty = "";
+
+static void EH_CM_destruct(ExportHandler *pMe){
+ /* This serves two purposes: idempotent reinitialize, and final takedown */
+ BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
+ if( pbie->nCol!=0 ){
+ sqlite3_uint64 nData = pbie->nCol * (pbie->nRow + 1), i;
+ const char *zNull = pbie->psi->nullValue;
+ char **azData = (char**)pbie->pData;
+ for(i=0; i<nData; i++){
+ char *z = azData[i];
+ if( z!=zEmpty && z!=zNull ) sqlite3_free(z);
+ }
+ sqlite3_free(pbie->pData);
+ sqlite3_free(pbie->pRowInfo);
+ pbie->pData = 0;
+ pbie->pRowInfo = 0;
+ }
+ pbie->nCol = 0;
+ pbie->nRow = 0;
+ pbie->colSep = 0;
+ pbie->rowSep = 0;
+}
+
+static const char *zModeName(ShellInState *psi){
+ int mi = psi->mode;
+ return (mi>=0 && mi<MODE_COUNT_OF)? modeDescr[mi].zModeName : 0;
+}
+static const char *EH_FF_name(ExportHandler *pMe){
+ return zModeName(((BuiltInFFExporter*)pMe)->psi);
+}
+static const char *EH_CM_name(ExportHandler *pMe){
+ return zModeName(((BuiltInCMExporter*)pMe)->psi);
+}
+
+static const char *EH_help(ExportHandler *pMe, const char *zWhat){
+ (void)(pMe);
+ (void)(zWhat);
+ return 0;
+}
+
+static int EH_openResultsOutStream(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ int numArgs, char *azArgs[],
+ const char * zName){
+ /* The built-in exporters have a predetermined destination, and their
+ * action is set by the shell state .mode member, so this method has
+ * nothing to do. For similar reasons, the shell never calls it. That
+ * could change if .mode command functionality is moved to here.
+ */
+ (void)(pMe);
+ (void)(pSES);
+ (void)(pzErr);
+ (void)(numArgs);
+ (void)(azArgs);
+ (void)(zName);
+ return 0;
+}
+
+static int EH_CM_prependResultsOut(ExportHandler *pMe,
+ ShellExState *psx, char **pzErr,
+ sqlite3_stmt *pStmt){
+ BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
+ ShellInState *psi = ISS(psx);
+ sqlite3_int64 nRow = 0;
+ char **azData = 0;
+ sqlite3_int64 nAlloc = 0;
+ char *abRowDiv = 0;
+ const unsigned char *uz;
+ const char *z;
+ char **azQuoted = 0;
+ int rc;
+ sqlite3_int64 i, nData;
+ int j, w, n;
+ const unsigned char **azNextLine = 0;
+ int bNextLine = 0;
+ int bMultiLineRowExists = 0;
+ int bw = psi->cmOpts.bWordWrap;
+ int nColumn = sqlite3_column_count(pStmt);
+
+ if( nColumn==0 ){
+ rc = sqlite3_step(pStmt);
+ assert(rc!=SQLITE_ROW);
+ return rc;
+ }
+ EH_CM_destruct(pMe);
+
+ nAlloc = nColumn*4;
+ if( nAlloc<=0 ) nAlloc = 1;
+ azData = sqlite3_malloc64( nAlloc*sizeof(char*) );
+ shell_check_oom(azData);
+ azNextLine = sqlite3_malloc64( nColumn*sizeof(char*) );
+ shell_check_oom((void*)azNextLine);
+ memset((void*)azNextLine, 0, nColumn*sizeof(char*) );
+ if( psi->cmOpts.bQuote ){
+ azQuoted = sqlite3_malloc64( nColumn*sizeof(char*) );
+ shell_check_oom(azQuoted);
+ memset(azQuoted, 0, nColumn*sizeof(char*) );
+ }
+ abRowDiv = sqlite3_malloc64( nAlloc/nColumn );
+ shell_check_oom(abRowDiv);
+ if( nColumn>psx->numWidths ){
+ psx->pSpecWidths = realloc(psx->pSpecWidths, (nColumn+1)*2*sizeof(int));
+ shell_check_oom(psx->pSpecWidths);
+ for(i=psx->numWidths; i<nColumn; i++) psx->pSpecWidths[i] = 0;
+ psx->numWidths = nColumn;
+ psx->pHaveWidths = &psx->pSpecWidths[nColumn];
+ }
+ memset(psx->pHaveWidths, 0, nColumn*sizeof(int));
+ for(i=0; i<nColumn; i++){
+ w = psx->pSpecWidths[i];
+ if( w<0 ) w = -w;
+ psx->pHaveWidths[i] = w;
+ }
+ for(i=0; i<nColumn; i++){
+ const unsigned char *zNotUsed;
+ int wx = psx->pSpecWidths[i];
+ if( wx==0 ){
+ wx = psi->cmOpts.iWrap;
+ }
+ if( wx<0 ) wx = -wx;
+ uz = (const unsigned char*)sqlite3_column_name(pStmt,i);
+ azData[i] = translateForDisplayAndDup(uz, &zNotUsed, wx, bw);
+ }
+ while( bNextLine || SQLITE_ROW==sqlite3_step(pStmt) ){
+ int useNextLine = bNextLine;
+ bNextLine = 0;
+ if( (nRow+2)*nColumn >= nAlloc ){
+ nAlloc *= 2;
+ azData = sqlite3_realloc64(azData, nAlloc*sizeof(char*));
+ shell_check_oom(azData);
+ abRowDiv = sqlite3_realloc64(abRowDiv, nAlloc/nColumn);
+ shell_check_oom(abRowDiv);
+ }
+ abRowDiv[nRow] = 1;
+ nRow++;
+ for(i=0; i<nColumn; i++){
+ int wx = psx->pSpecWidths[i];
+ if( wx==0 ){
+ wx = psi->cmOpts.iWrap;
+ }
+ if( wx<0 ) wx = -wx;
+ if( useNextLine ){
+ uz = azNextLine[i];
+ if( uz==0 ) uz = (u8*)zEmpty;
+ }else if( psi->cmOpts.bQuote ){
+ sqlite3_free(azQuoted[i]);
+ azQuoted[i] = quoted_column(pStmt,i);
+ uz = (const unsigned char*)azQuoted[i];
+ }else{
+ uz = (const unsigned char*)sqlite3_column_text(pStmt,i);
+ if( uz==0 ) uz = (u8*)psi->nullValue;
+ }
+ azData[nRow*nColumn + i]
+ = translateForDisplayAndDup(uz, &azNextLine[i], wx, bw);
+ if( azNextLine[i] ){
+ bNextLine = 1;
+ abRowDiv[nRow-1] = 0;
+ bMultiLineRowExists = 1;
+ }
+ }
+ }
+ sqlite3_free((void*)azNextLine);
+ if( azQuoted ){
+ for(i=0; i<nColumn; i++) sqlite3_free(azQuoted[i]);
+ sqlite3_free(azQuoted);
+ }
+ if( nRow==0 ){
+ EH_CM_destruct(pMe);
+ return SQLITE_DONE;
+ }
+
+ nData = nColumn*(nRow+1);
+
+ for(i=0; i<nData; i++){
+ z = azData[i];
+ if( z==0 ) z = (char*)zEmpty;
+ n = strlenChar(z);
+ j = i%nColumn;
+ if( n>psx->pHaveWidths[j] ) psx->pHaveWidths[j] = n;
+ }
+ if( seenInterrupt ) goto done;
+ switch( psi->cMode ){
+ case MODE_Column: {
+ pbie->colSep = " ";
+ pbie->rowSep = "\n";
+ if( psi->showHeader ){
+ for(i=0; i<nColumn; i++){
+ w = psx->pHaveWidths[i];
+ if( psx->pSpecWidths[i]<0 ) w = -w;
+ utf8_width_print(psi->out, w, azData[i]);
+ fputs(i==nColumn-1?"\n":" ", psi->out);
+ }
+ for(i=0; i<nColumn; i++){
+ print_dashes(psi->out, psx->pHaveWidths[i]);
+ fputs(i==nColumn-1?"\n":" ", psi->out);
+ }
+ }
+ break;
+ }
+ case MODE_Table: {
+ pbie->colSep = " | ";
+ pbie->rowSep = " |\n";
+ print_row_separator(psx, nColumn, "+");
+ fputs("| ", psi->out);
+ for(i=0; i<nColumn; i++){
+ w = psx->pHaveWidths[i];
+ n = strlenChar(azData[i]);
+ utf8_printf(psi->out, "%*s%s%*s",
+ (w-n)/2, "", azData[i], (w-n+1)/2, "");
+ fputs(i==nColumn-1?" |\n":" | ", psi->out);
+ }
+ print_row_separator(psx, nColumn, "+");
+ break;
+ }
+ case MODE_Markdown: {
+ pbie->colSep = " | ";
+ pbie->rowSep = " |\n";
+ fputs("| ", psi->out);
+ for(i=0; i<nColumn; i++){
+ w = psx->pHaveWidths[i];
+ n = strlenChar(azData[i]);
+ utf8_printf(psi->out, "%*s%s%*s",
+ (w-n)/2, "", azData[i], (w-n+1)/2, "");
+ fputs(i==nColumn-1?" |\n":" | ", psi->out);
+ }
+ print_row_separator(psx, nColumn, "|");
+ break;
+ }
+ case MODE_Box: {
+ pbie->colSep = " " BOX_13 " ";
+ pbie->rowSep = " " BOX_13 "\n";
+ print_box_row_separator(psx, nColumn, BOX_23, BOX_234, BOX_34);
+ utf8_printf(psi->out, BOX_13 " ");
+ for(i=0; i<nColumn; i++){
+ w = psx->pHaveWidths[i];
+ n = strlenChar(azData[i]);
+ utf8_printf(psi->out, "%*s%s%*s%s",
+ (w-n)/2, "", azData[i], (w-n+1)/2, "",
+ i==nColumn-1?" "BOX_13"\n":" "BOX_13" ");
+ }
+ print_box_row_separator(psx, nColumn, BOX_123, BOX_1234, BOX_134);
+ break;
+ }
+ }
+ done:
+ pbie->nCol = nColumn;
+ pbie->pData = azData;
+ pbie->nRow = nRow;
+ if( bMultiLineRowExists ){
+ pbie->pRowInfo = abRowDiv;
+ }else{
+ pbie->pRowInfo = 0;
+ sqlite3_free(abRowDiv);
+ }
+ if( seenInterrupt ){
+ EH_CM_destruct(pMe);
+ return SQLITE_INTERRUPT;
+ }
+ return SQLITE_OK;
+}
+
+static int EH_CM_rowResultsOut(ExportHandler *pMe,
+ ShellExState *psx, char **pzErr,
+ sqlite3_stmt *pStmt){
+ BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
+ ShellInState *psi = pbie->psi;
+ sqlite3_int64 nRow = pbie->nRow;
+ int nColumn = pbie->nCol, j, w;
+ char **azData = (char**)(pbie->pData);
+ sqlite3_int64 nData = (nRow+1)*nColumn, i;
+ char *abRowDiv = pbie->pRowInfo;
+ const char *z;
+
+ (void)(pzErr);
+ (void)(pStmt);
+ if( nRow==0 || nColumn==0 ) return SQLITE_INTERRUPT;
+ for(i=nColumn, j=0; i<nData; i++, j++){
+ if( j==0 && psi->cMode!=MODE_Column ){
+ utf8_printf(psi->out, "%s", psi->cMode==MODE_Box?BOX_13" ":"| ");
+ }
+ z = azData[i];
+ if( z==0 ) z = zEmpty;
+ w = psx->pHaveWidths[j];
+ if( psx->pSpecWidths[j]<0 ) w = -w;
+ utf8_width_print(psi->out, w, z);
+ if( j==nColumn-1 ){
+ utf8_printf(psi->out, "%s", pbie->rowSep);
+ if( abRowDiv!=0 && abRowDiv[i/nColumn-1] && i+1<nData ){
+ if( psi->cMode==MODE_Table ){
+ print_row_separator(psx, nColumn, "+");
+ }else if( psi->cMode==MODE_Box ){
+ print_box_row_separator(psx, nColumn, BOX_123, BOX_1234, BOX_134);
+ }else if( psi->cMode==MODE_Column ){
+ raw_printf(psi->out, "\n");
+ }
+ }
+ j = -1;
+ if( seenInterrupt ){
+ EH_CM_destruct(pMe);
+ return SQLITE_INTERRUPT;
+ }
+ }else{
+ utf8_printf(psi->out, "%s", pbie->colSep);
+ }
+ }
+ return SQLITE_DONE;
+}
+
+static int EH_CM_appendResultsOut(ExportHandler *pMe,
+ ShellExState *psx, char **pzErr,
+ sqlite3_stmt *pStmt){
+ BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
+ ShellInState *psi = ISS(psx);
+ sqlite3_int64 nRow = pbie->nRow;
+ int nColumn = pbie->nCol;
+ char **azData = (char**)(pbie->pData);
+ sqlite3_int64 nData = (nRow+1)*nColumn;
+
+ if( nRow==0 || nColumn==0 ) return SQLITE_INTERRUPT;
+
+ if( psi->cMode==MODE_Table ){
+ print_row_separator(psx, nColumn, "+");
+ }else if( psi->cMode==MODE_Box ){
+ print_box_row_separator(psx, nColumn, BOX_12, BOX_124, BOX_14);
+ }
+ EH_CM_destruct(pMe);
+ return SQLITE_OK;
+}
+
+static int EH_FF_prependResultsOut(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ sqlite3_stmt *pStmt){
+ BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
+ int nc = sqlite3_column_count(pStmt);
+ int rc;
+
+ pbie->pMethods->destruct(pMe);
+ if( nc>0 ){
+ /* allocate space for col name ptr, value ptr, and type */
+ pbie->pData = sqlite3_malloc64(3*nc*sizeof(const char*) + 1);
+ if( !pbie->pData ){
+ shell_out_of_memory();
+ }else{
+ ColumnsInfo ci
+ = { (char **)pbie->pData, &ci.azCols[nc], (int *)&ci.azVals[nc] };
+ int i;
+ assert(sizeof(int) <= sizeof(char *));
+ pbie->nCol = nc;
+ pbie->colInfo = ci;
+ /* save off ptrs to column names */
+ for(i=0; i<nc; i++){
+ pbie->colInfo.azCols[i] = (char *)sqlite3_column_name(pStmt, i);
+ }
+ }
+ return SQLITE_OK;
+ }
+ rc = sqlite3_step(pStmt);
+ assert(rc!=SQLITE_ROW);
+ return rc;
+}
+
+static int EH_FF_rowResultsOut(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ sqlite3_stmt *pStmt){
+ BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
+ ShellInState *psi = ISS(pSES);
+ int rc = sqlite3_step(pStmt);
+ int i, x, nc = pbie->nCol;
+ if( rc==SQLITE_ROW ){
+ ColumnsInfo *pc = &pbie->colInfo;
+ sqlite3_uint64 nr = ++(pbie->nRow);
+ for( i=0; i<nc; ++i ){
+ pc->aiTypes[i] = x = sqlite3_column_type(pStmt, i);
+ if( x==SQLITE_BLOB
+ && (psi->cMode==MODE_Insert || psi->cMode==MODE_Quote) ){
+ pc->azVals[i] = "";
+ }else{
+ pc->azVals[i] = (char*)sqlite3_column_text(pStmt, i);
+ }
+ if( !pc->azVals[i] && (x!=SQLITE_NULL) ){
+ rc = SQLITE_NOMEM;
+ break; /* from for */
+ }
+ }
+ /* if data and types extracted successfully... */
+ if( SQLITE_ROW==rc ){
+ /* call the supplied callback with the result row data */
+ if( shell_callback(pSES, nc, pc->azVals, pc->azCols, pc->aiTypes) ){
+ rc = SQLITE_ABORT;
+ }
+ }
+ }
+ return rc;
+}
+
+static int EH_FF_appendResultsOut(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ sqlite3_stmt *pStmt){
+ BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
+ ShellInState *psi = ISS(pSES);
+ if( psi->cMode==MODE_Json ){
+ fputs("]\n", psi->out);
+ }else if( psi->cMode==MODE_Count ){
+ utf8_printf(psi->out, "%llu row%s\n", pbie->nRow, pbie->nRow!=1 ? "s" : "");
+ }
+ EH_FF_destruct(pMe);
+ return SQLITE_OK;
+}
+
+static void EH_closeResultsOutStream(ExportHandler *pMe,
+ ShellExState *pSES,
+ char **pzErr){
+ /* The built-in exporters have a predetermined destination which is
+ * never "closed", so this method has nothing to do. For similar
+ * reasons, it is not called by the shell.
+ */
+ (void)(pMe);
+ (void)(pSES);
+ (void)(pzErr);
+}
+#endif /* SHELL_DATAIO_EXT */
+
+#if SHELL_DYNAMIC_EXTENSION
+
+/* Ensure there is room in loaded extension info list for one being loaded.
+ * On exit, psi->ixExtPending can be used to index into psi->pShxLoaded.
+ */
+static ShExtInfo *pending_ext_info(ShellInState *psi){
+ int ixpe = psi->ixExtPending;
+ assert(ixpe!=0);
+ if( ixpe >= psi->numExtLoaded ){
+ psi->pShxLoaded = sqlite3_realloc(psi->pShxLoaded,
+ (ixpe+1)*sizeof(ShExtInfo));
+ shell_check_oom(psi->pShxLoaded);
+ ++psi->numExtLoaded;
+ memset(psi->pShxLoaded+ixpe, 0, sizeof(ShExtInfo));
+ }
+ return &psi->pShxLoaded[ixpe];
+}
+
+/* Register a dot-command, to be called during extension load/init. */
+static int register_dot_command(ShellExState *p,
+ ExtensionId eid, DotCommand *pMC){
+ ShellInState *psi = ISS(p);
+ ShExtInfo *psei = pending_ext_info(psi);
+ const char *zSql
+ = "INSERT INTO "SHELL_DISP_TAB"(name, extIx, cmdIx) VALUES(?, ?, ?)";
+ int ie = psi->ixExtPending;
+ assert(psi->pShxLoaded!=0 && p->dbShell!=0);
+ if( pMC==0 ) return SQLITE_ERROR;
+ else{
+ const char *zName = pMC->pMethods->name(pMC);
+ sqlite3_stmt *pStmt;
+ int nc = psei->numDotCommands;
+ int rc;
+ if( psei->extId!=0 && psei->extId!=eid ) return SQLITE_MISUSE;
+ psei->extId = eid;
+ rc = sqlite3_prepare_v2(p->dbShell, zSql, -1, &pStmt, 0);
+ if( rc!=SQLITE_OK ) return rc;
+ psei->ppDotCommands
+ = sqlite3_realloc(psei->ppDotCommands, (nc+1)*sizeof(DotCommand *));
+ shell_check_oom(psei->ppDotCommands);
+ sqlite3_bind_text(pStmt, 1, zName, -1, 0);
+ sqlite3_bind_int(pStmt, 2, ie);
+ sqlite3_bind_int(pStmt, 3, nc);
+ rc = sqlite3_step(pStmt);
+ sqlite3_finalize(pStmt);
+ if( rc==SQLITE_DONE ){
+ psei->ppDotCommands[nc++] = pMC;
+ psei->numDotCommands = nc;
+ notify_subscribers(psi, NK_NewDotCommand, pMC);
+ if( cli_strcmp("unknown", zName)==0 ){
+ psi->pUnknown = pMC;
+ psei->pUnknown = pMC;
+ }
+ return SQLITE_OK;
+ }else{
+ psei->ppDotCommands[nc] = 0;
+ }
+ }
+ return SQLITE_ERROR;
+}
+
+/* Register an output data display (or other disposition) mode */
+static int register_exporter(ShellExState *p,
+ ExtensionId eid, ExportHandler *pEH){
+ return SQLITE_ERROR;
+}
+
+/* Register an import variation from (various sources) for .import */
+static int register_importer(ShellExState *p,
+ ExtensionId eid, ImportHandler *pIH){
+ return SQLITE_ERROR;
+}
+
+/* See registerScripting API in shext_linkage.h */
+static int register_scripting(ShellExState *p, ExtensionId eid,
+ ScriptSupport *pSS){
+ ShellInState *psi = ISS(p);
+ if( psi->scriptXid!=0 || psi->script!=0 ){
+ /* Scripting support already provided. Only one provider is allowed. */
+ return SQLITE_BUSY;
+ }
+ if( eid==0 || pSS==0 || psi->ixExtPending==0 ){
+ /* Scripting addition allowed only when sqlite3_*_init() runs. */
+ return SQLITE_MISUSE;
+ }
+ psi->script = pSS;
+ psi->scriptXid = eid;
+ return SQLITE_OK;
+}
+
+/* See registerAdHocCommand API in shext_linkage.h re detailed behavior.
+ * Depending on zHelp==0, either register or unregister ad-hoc treatment
+ * of zName for this extension (identified by eid.)
+ */
+static int register_adhoc_command(ShellExState *p, ExtensionId eid,
+ const char *zName, const char *zHelp){
+ ShellInState *psi = ISS(p);
+ u8 bRegNotRemove = zHelp!=0;
+ const char *zSql = bRegNotRemove
+ ? "INSERT OR REPLACE INTO "SHELL_AHELP_TAB
+ "(name, extIx, helpText) VALUES(?, ?, ?||?||?)"
+ : "DELETE FROM "SHELL_AHELP_TAB" WHERE name=? AND extIx=?";
+ sqlite3_stmt *pStmt;
+ int rc, ie;
+
+ assert(psi->pShxLoaded!=0 && p->dbShell!=0);
+ for( ie=psi->numExtLoaded-1; ie>0; --ie ){
+ if( psi->pShxLoaded[ie].extId==eid ) break;
+ }
+ if( !zName || ie==0 || psi->pShxLoaded[ie].pUnknown==0 ) return SQLITE_MISUSE;
+ rc = sqlite3_prepare_v2(p->dbShell, zSql, -1, &pStmt, 0);
+ if( rc!=SQLITE_OK ) return rc;
+ sqlite3_bind_text(pStmt, 1, zName, -1, 0);
+ sqlite3_bind_int(pStmt, 2, ie);
+ if( bRegNotRemove ){
+ int nc = strlen30(zHelp);
+ char cLead = *zHelp;
+ /* Add leading '.' if no help classifier present. */
+ const char *zCL = (cLead!='.' && cLead!=',')? "." : "";
+ /* Add trailing newline if not already there. */
+ const char *zLE = (nc>0 && zHelp[nc-1]!='\n')? "\n" : "";
+ sqlite3_bind_text(pStmt, 3, zCL, -1, 0);
+ sqlite3_bind_text(pStmt, 4, zHelp, -1, 0);
+ sqlite3_bind_text(pStmt, 5, zLE, -1, 0);
+ }
+ rc = sqlite3_step(pStmt);
+ sqlite3_finalize(pStmt);
+ return (rc==SQLITE_DONE)? SQLITE_OK : SQLITE_ERROR;
+}
+
+/*
+ * Subscribe to (or unsubscribe from) messages about various changes.
+ * Unsubscribe, effected when nkMin==NK_Unsubscribe, always succeeds.
+ * Return SQLITE_OK on success, or one of these error codes:
+ * SQLITE_ERROR when the nkMin value is unsupported by this host;
+ * SQLITE_NOMEM when a required allocation failed; or
+ * SQLITE_MISUSE when the provided eid or eventHandler is invalid.
+ */
+static int subscribe_events(ShellExState *p, ExtensionId eid, void *pvUserData,
+ NoticeKind nkMin, ShellEventNotify eventHandler){
+ ShellInState *psi = ISS(p);
+ struct EventSubscription *pes = psi->pSubscriptions;
+ struct EventSubscription *pesLim = pes + psi->numSubscriptions;
+ if( nkMin==NK_Unsubscribe ){
+ /* unsubscribe (if now subscribed) */
+ while( pes < pesLim ){
+ if( (eventHandler==0 || eventHandler==pes->eventHandler)
+ && (pes->eid==0 || pes->eid==eid)
+ && (eid!=0 || eventHandler!=0 ||/*for shell use*/ pvUserData==p ) ){
+ int nLeft = pesLim - pes;
+ assert(pes->eventHandler!=0);
+ pes->eventHandler(pes->pvUserData, NK_Unsubscribe, pes->eid, p);
+ if( nLeft>1 ) memmove(pes, pes+1, (nLeft-1)*sizeof(*pes));
+ --pesLim;
+ --psi->numSubscriptions;
+ }else{
+ ++pes;
+ }
+ }
+ if( psi->numSubscriptions==0 ){
+ sqlite3_free(psi->pSubscriptions);
+ psi->pSubscriptions = 0;
+ }
+ return SQLITE_OK;
+ }else{
+ /* subscribe only if minimum NoticeKind supported by this host */
+ if( nkMin > NK_CountOf ) return SQLITE_ERROR;
+ if( eventHandler==0 || eid==0 ) return SQLITE_MISUSE;
+ while( pes < pesLim ){
+ /* Never add duplicate handlers, but may renew their user data. */
+ if( pes->eid==eid && pes->eventHandler==eventHandler ){
+ pes->pvUserData = pvUserData;
+ return SQLITE_OK;
+ }
+ ++pes;
+ }
+ assert(pes==pesLim);
+ pes = sqlite3_realloc(psi->pSubscriptions,
+ (psi->numSubscriptions+1)*sizeof(*pes));
+ if( pes==0 ) return SQLITE_NOMEM;
+ psi->pSubscriptions = pes;
+ pes += (psi->numSubscriptions++);
+ pes->eid = eid;
+ pes->pvUserData = pvUserData;
+ pes->eventHandler = eventHandler;
+ return SQLITE_OK;
+ }
+}
+
+/*
+ * Unsubscribe all event listeners having an ExtensionId > 0. This is
+ * done just prior closing the shell DB (when dynamic extensions will
+ * be unloaded and accessing them in any way is good for a crash.)
+ */
+static void unsubscribe_extensions(ShellInState *psi){
+ ShellExState *psx = XSS(psi);
+ int esix = 0;
+
+ if( psi->numExtLoaded<=1 ) return; /* Ignore shell pseudo-extension. */
+ while( esix<psi->numSubscriptions ){
+ struct EventSubscription *pes = psi->pSubscriptions+esix;
+ if( pes->eid > 0 ){
+ int nsin = psi->numSubscriptions;
+ subscribe_events(psx, pes->eid, psx, NK_Unsubscribe, 0);
+ esix = esix + 1 + (psi->numSubscriptions - nsin);
+ }else ++esix;
+ }
+}
+
+static struct InSource *currentInputSource(ShellExState *p){
+ return ISS(p)->pInSource;
+}
+
+static int nowInteractive(ShellExState *p){
+ return INSOURCE_IS_INTERACTIVE(ISS(p)->pInSource);
+}
+
+static const char *shellInvokedAs(void){
+ return Argv0;
+}
+
+static const char *shellStartupDir(void){
+ return startupDir;
+}
+
+static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths);
+static DotCommand * findDotCommand(const char *, ShellExState *, int *);
+static DotCmdRC runDotCommand(DotCommand*, char *[], int na, ShellExState*);
+
+static ExtensionHelpers extHelpers = {
+ 13,
+ {
+ failIfSafeMode,
+ utf8_out_printf,
+ currentInputSource,
+ strLineGet,
+ findDotCommand,
+ runDotCommand,
+ setColumnWidths,
+ nowInteractive,
+ shellInvokedAs,
+ shellStartupDir,
+ one_input_line,
+ free_input_line,
+ sqlite3_enable_load_extension,
+ 0
+ }
+};
+
+static ShellExtensionAPI shellExtAPI = {
+ &extHelpers, 6, {
+ register_dot_command,
+ register_exporter,
+ register_importer,
+ register_scripting,
+ subscribe_events,
+ register_adhoc_command,
+ 0
+ }
+};
+
+/* This SQL function provides a way for a just-loaded shell extension to
+ * obtain a ShellExtensionLink pointer from the shell core while using
+ * the same sqlite3_load_extension API used for SQLite extensions.
+ *
+ * (It is also useful for debugging a shell extension, as a breakpoint
+ * on it will be hit soon after loading and before real work is done.)
+ */
+static void shell_linkage(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ int linkKind = 0;
+ void *pv;
+ if( argc>0 ){
+ linkKind = sqlite3_value_int(argv[0]);
+ }
+ switch (linkKind){
+ case 0:
+ pv = sqlite3_user_data(context);
+ break;
+ case 1:
+ pv = &extHelpers;
+ break;
+ case 2:
+ pv = &shellExtAPI;
+ break;
+ default:
+ pv = 0;
+ }
+ if( pv==0 ) sqlite3_result_null(context);
+ else sqlite3_result_pointer(context, pv, SHELLEXT_API_POINTERS, 0);
+}
+
++/* Free the memory held by a ShExtInfo object but not the object itself.
++ * No notifications associated with takedown and termination are done. */
++static void free_ShExtInfo( ShExtInfo *psei ){
++ if( psei ){
++ if( psei->ppDotCommands ) sqlite3_free(psei->ppDotCommands);
++ if( psei->ppExportHandlers ) sqlite3_free(psei->ppExportHandlers);
++ if( psei->ppImportHandlers ) sqlite3_free(psei->ppImportHandlers);
++ memset(psei, 0, sizeof(ShExtInfo));
++ }
++}
++
+/* Do the initialization needed for use of dbShell for command lookup
+ * and dispatch and for I/O handler lookup and dispatch.
+ */
+static int begin_db_dispatch(ShellExState *psx){
+ ShellInState *psi = ISS(psx);
+ sqlite3_stmt *pStmt = 0;
+ int ic, rc1, rc2;
+ int rc = 0;
+ char *zErr = 0;
+ const char *zSql;
- ShExtInfo sei = {0};
++ ShExtInfo sei = SHEXT_INFO_INIT;
++ AnyResourceHolder arh_sei = {&sei, (GenericFreer)free_ShExtInfo};
++ ResourceMark mark = holder_mark();
++
++ sstr_ptr_holder(&zErr);
+ /* Consider: Store these dynamic arrays in the DB as indexed-into blobs. */
+ assert(psx->dbShell==0 || (psi->numExtLoaded==0 && psi->pShxLoaded==0));
+ rc = ensure_shell_db(psx);
+ if( rc!=SQLITE_OK ){
+ utf8_printf(STD_ERR, "Error: Shell DB uncreatable. Best to .quit soon.\n");
+ return SQLITE_ERROR;
+ }
+ if( ensure_dispatch_table(psx)!=SQLITE_OK ) return 1;
+
+ psi->pShxLoaded = (ShExtInfo *)sqlite3_malloc(2*sizeof(ShExtInfo));
++ shell_check_oom(psi->pShxLoaded);
++ /* The ShellInState object now owns above allocation, so initialize it. */
++ memset(psi->pShxLoaded, 0, 2*sizeof(ShExtInfo));
++ any_ref_holder(&arh_sei); /* protect against early aborts */
+ sei.ppDotCommands
+ = (DotCommand **)sqlite3_malloc((numCommands+2)*sizeof(DotCommand *));
+ sei.ppExportHandlers
+ = (ExportHandler **)sqlite3_malloc(2*sizeof(ExportHandler *));
+ sei.ppImportHandlers
+ = (ImportHandler **)sqlite3_malloc(2*sizeof(ImportHandler *));
+ if( sei.ppDotCommands==0||sei.ppExportHandlers==0||sei.ppImportHandlers==0
+ || psi->pShxLoaded==0 ){
+ shell_out_of_memory();
+ }
+ sei.numExportHandlers = 0;
+ sei.numImportHandlers = 0;
+ for( ic=0; ic<(int)numCommands; ++ic ){
+ sei.ppDotCommands[ic] = builtInCommand(ic);
+ }
+ sei.numDotCommands = ic;
- psi->pShxLoaded[psi->numExtLoaded++] = sei;
+ zSql = "INSERT INTO "SHELL_DISP_TAB"(name, extIx, cmdIx) VALUES(?, 0, ?)";
- rc1 = sqlite3_prepare_v2(psx->dbShell, zSql, -1, &pStmt, 0);
++ shell_check_nomem(rc1=sqlite3_prepare_v2(psx->dbShell, zSql, -1, &pStmt, 0));
++ stmt_holder(pStmt);
+ rc2 = sqlite3_exec(psx->dbShell, "BEGIN TRANSACTION", 0, 0, &zErr);
- if( rc1!=SQLITE_OK || rc2!=SQLITE_OK ) return 1;
- assert(sei.numDotCommands>0);
- for( ic=0; ic<sei.numDotCommands; ++ic ){
- DotCommand *pmc = sei.ppDotCommands[ic];
- const char *zName = pmc->pMethods->name(pmc);
- sqlite3_reset(pStmt);
- sqlite3_bind_text(pStmt, 1, zName, -1, 0);
- sqlite3_bind_int(pStmt, 2, ic);
- rc = sqlite3_step(pStmt);
++ shell_check_nomem(rc2);
++ if( rc1!=SQLITE_OK || rc2!=SQLITE_OK ){
++ rc = SQLITE_ERROR;
++ }else{
++ assert(sei.numDotCommands>0);
++ for( ic=0; ic<sei.numDotCommands; ++ic ){
++ DotCommand *pmc = sei.ppDotCommands[ic];
++ const char *zName = pmc->pMethods->name(pmc);
++ sqlite3_reset(pStmt);
++ shell_check_nomem(sqlite3_bind_text(pStmt, 1, zName, -1, 0));
++ sqlite3_bind_int(pStmt, 2, ic);
++ shell_check_nomem(rc = sqlite3_step(pStmt));
++ if( rc!=SQLITE_DONE ){
++ sqlite3_exec(psx->dbShell, "ABORT", 0, 0, 0);
++ break;
++ }
++ }
+ if( rc!=SQLITE_DONE ){
- sqlite3_exec(psx->dbShell, "ABORT", 0, 0, 0);
- break;
++ rc = SQLITE_ERROR;
++ zSql = "ABORT";
++ }else{
++ rc = SQLITE_OK;
++ zSql = "COMMIT";
++ }
++ shell_check_nomem(rc2 = sqlite3_exec(psx->dbShell, zSql, 0, 0, &zErr));
++ if( SQLITE_OK==rc ){
++ /* Transfer just-built ShExtInfo to ShellInState use and ownership. */
++ psi->pShxLoaded[psi->numExtLoaded++] = sei;
++ arh_sei.pAny = 0;
++ sqlite3_enable_load_extension(psx->dbShell, 1);
++ psi->bDbDispatch = 1;
+ }
+ }
- sqlite3_finalize(pStmt);
- if( rc!=SQLITE_DONE ) return 1;
- rc = sqlite3_exec(psx->dbShell, "COMMIT", 0, 0, &zErr);
- sqlite3_enable_load_extension(psx->dbShell, 1);
- sqlite3_free(zErr);
- psi->bDbDispatch = 1;
++ holder_free(mark);
+
- return SQLITE_OK;
++ return rc;
+}
+
- /* Call one loaded extension's destructors, in reverse order
- * of their objects' creation, then free the tracking dyna-arrays.
++/* Call one loaded extension's destructors, in reverse order of their
++ * objects' creation.
+ */
- static void free_one_shext_tracking(ShExtInfo *psei){
++static void run_one_shext_dtors(ShExtInfo *psei){
+ int j;
+ if( psei->ppDotCommands!=0 ){
+ for( j=psei->numDotCommands; j>0; --j ){
+ DotCommand *pmc = psei->ppDotCommands[j-1];
+ if( pmc->pMethods->destruct!=0 ) pmc->pMethods->destruct(pmc);
+ }
- sqlite3_free(psei->ppDotCommands);
+ }
+ if( psei->ppExportHandlers!=0 ){
+ for( j=psei->numExportHandlers; j>0; --j ){
+ ExportHandler *peh = psei->ppExportHandlers[j-1];
+ if( peh->pMethods->destruct!=0 ) peh->pMethods->destruct(peh);
+ }
- sqlite3_free(psei->ppExportHandlers);
+ }
+ if( psei->ppImportHandlers!=0 ){
+ for( j=psei->numImportHandlers; j>0; --j ){
+ ImportHandler *pih = psei->ppImportHandlers[j-1];
+ if( pih->pMethods->destruct!=0 ) pih->pMethods->destruct(pih);
+ }
- sqlite3_free(psei->ppImportHandlers);
+ }
+ if( psei->extDtor!=0 ){
+ psei->extDtor(psei->pvExtObj);
+ }
+}
+
+/* Call all existent loaded extension destructors, in reverse order of their
+ * objects' creation, except for scripting support which is done last,
+ * then free the tracking dyna-arrays.
+ */
+static void free_all_shext_tracking(ShellInState *psi){
+ if( psi->pShxLoaded!=0 ){
+ int i = psi->numExtLoaded;
+ while( i>1 ){
+ ShExtInfo *psei = &psi->pShxLoaded[--i];
- free_one_shext_tracking(psei);
++ run_one_shext_dtors(psei);
++ free_ShExtInfo(psei);
+ if( psi->scriptXid!=0 && psi->scriptXid==psei->extId ){
+ assert(psi->script!=0);
+ if (psi->script->pMethods->destruct){
+ psi->script->pMethods->destruct(psi->script);
+ }
+ psi->script = 0;
+ psi->scriptXid = 0;
+ }
+ }
++ free_ShExtInfo(psi->pShxLoaded);
+ sqlite3_free(psi->pShxLoaded);
+ psi->pShxLoaded = 0;
+ psi->numExtLoaded = 0;
+ }
+}
+
+static DotCommand *command_by_index(ShellInState *psi, int extIx, int cmdIx){
+ assert(extIx>=0);
+ if( extIx>=0 && extIx<psi->numExtLoaded ){
+ ShExtInfo *psei = & psi->pShxLoaded[extIx];
+ if( cmdIx>=0 && cmdIx<psei->numDotCommands ){
+ return psei->ppDotCommands[cmdIx];
+ }
+ }
+ return 0;
+}
+
+static int load_shell_extension(ShellExState *psx, const char *zFile,
+ const char *zProc, char **pzErr,
+ int nLoadArgs, char **azLoadArgs){
+ ShellExtensionLink shxLink = {
+ sizeof(ShellExtensionLink),
+ &shellExtAPI,
+ psx, /* pSXS */
+ 0, /* zErrMsg */
+ 0, /* ExtensionId */
+ 0, /* Extension destructor */
+ 0, /* Extension data ref */
+ nLoadArgs, azLoadArgs /* like-named members */
+ }; //extDtor(pvExtObj)
+ ShellInState *psi = ISS(psx);
+ /* save script support state for possible fallback if load fails */
+ ScriptSupport *pssSave = psi->script;
+ ExtensionId ssiSave = psi->scriptXid;
+ int rc;
+
+ if( pzErr ) *pzErr = 0;
+ if( psx->dbShell==0 || ISS(psx)->numExtLoaded==0 ){
+ rc = begin_db_dispatch(psx);
+ if( rc!=SQLITE_OK ) return rc;
+ assert(ISS(psx)->numExtLoaded==1 && psx->dbShell!=0);
+ }
+ psi->ixExtPending = psi->numExtLoaded;
+ sqlite3_create_function(psx->dbShell, "shext_pointer", 1,
+ SQLITE_DIRECTONLY|SQLITE_UTF8,
+ &shxLink, shell_linkage, 0, 0);
+ rc = sqlite3_load_extension(psx->dbShell, zFile, zProc, &shxLink.zErrMsg);
+ sqlite3_create_function(psx->dbShell, "shext_pointer", 1,
+ SQLITE_DIRECTONLY|SQLITE_UTF8,
+ 0, 0, 0, 0); /* deregister */
+ if( pzErr!=0 ) *pzErr = shxLink.zErrMsg;
+ if( rc==SQLITE_OK ){
+ /* Keep extension's id and destructor for later disposal. */
+ ShExtInfo *psei = pending_ext_info(psi);
+ if( psei->extId!=0 && psei->extId!=shxLink.eid ) rc = SQLITE_MISUSE;
+ psei->extId = shxLink.eid;
+ psei->extDtor = shxLink.extensionDestruct;
+ psei->pvExtObj = shxLink.pvExtensionObject;
+ }else{
+ /* Release all resources extension might have registered before failing. */
+ if( psi->ixExtPending < psi->numExtLoaded ){
- free_one_shext_tracking(psi->pShxLoaded+psi->ixExtPending);
++ run_one_shext_dtors(psi->pShxLoaded+psi->ixExtPending);
++ free_ShExtInfo(psi->pShxLoaded+psi->ixExtPending);
+ --psi->numExtLoaded;
+ }
+ /* And make it unwind any scripting linkage it might have setup. */
+ if( psi->script!=0 ) psi->script->pMethods->destruct(psi->script);
+ psi->script = pssSave;
+ psi->scriptXid = ssiSave;
+ }
+ psi->ixExtPending = 0;
+ if( rc!=SQLITE_OK ){
+ if( rc==SQLITE_MISUSE && pzErr!=0 ){
+ *pzErr = sqlite3_mprintf("extension id mismatch %z\n", *pzErr);
+ }
+ rc = SQLITE_ERROR;
+ }
+ return rc;
+}
+#endif
+
+/* Dot-command implementation functions are defined in this section.
+COMMENT Define dot-commands and provide for their dispatch and .help text.
+COMMENT These should be kept in command name order for coding convenience
+COMMENT except where dot-commands share implementation. (The ordering
+COMMENT required for dispatch and help text is effected regardless.) The
+COMMENT effect of this configuration can be seen in generated output or by
+COMMENT executing tool/mkshellc.tcl --parameters (or --details or --help).
+COMMENT Generally, this section defines dispatchable functions inline and
+COMMENT causes collection of command_table entry initializers, to be later
+COMMENT emitted by a mkshellc macro. (See EMIT_DOTCMD_INIT further on.)
+** All dispatchable dot-command execute functions have this signature:
+static int someCommand(char *azArg[], int nArg, ShellExState *p, char **pzErr);
+*/
+DISPATCH_CONFIG[
+ RETURN_TYPE=DotCmdRC
+ STORAGE_CLASS=static
+ ARGS_SIGNATURE=char *$arg4\[\], int $arg5, ShellExState *$arg6, char **$arg7
+ DISPATCH_ENTRY={ "$cmd", ${cmd}Command, $arg1, $arg2, $arg3 },
+ DOTCMD_INIT={ DOT_CMD_INFO(${cmd}, $arg1,$arg2,$arg3), {<HT0>, <HT1>}, 0 },
+ CMD_CAPTURE_RE=^\s*{\s*"(\w+)"
+ DISPATCHEE_NAME=${cmd}Command
+ DC_ARG1_DEFAULT=[string length $cmd]
+ DC_ARG2_DEFAULT=0
+ DC_ARG3_DEFAULT=0
+ DC_ARG4_DEFAULT=azArg
+ DC_ARG5_DEFAULT=nArg
+ DC_ARG6_DEFAULT=p
+ DC_ARG7_DEFAULT=pzErr
+ DC_ARG_COUNT=8
+];
+
+CONDITION_COMMAND(seeargs !defined(SHELL_OMIT_SEEARGS));
+/*****************
+ * The .seeargs command
+ */
+COLLECT_HELP_TEXT[
+ ",seeargs Echo arguments suffixed with |",
+];
+DISPATCHABLE_COMMAND( seeargs ? 0 0 azArg nArg p ){
+ int ia = 0;
+ for (ia=1; ia<nArg; ++ia)
+ raw_printf(ISS(p)->out, "%s%s", azArg[ia], (ia==nArg-1)? "|\n" : "|");
+ return DCR_Ok;
+}
+
+#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
+# define ARCHIVE_ENABLE 1
+#else
+# define ARCHIVE_ENABLE 0
+#endif
+
+CONDITION_COMMAND(archive ARCHIVE_ENABLE && !defined(SQLITE_SHELL_FIDDLE));
+/*****************
+ * The .archive command
+ */
+COLLECT_HELP_TEXT[
+ ".archive ... Manage SQL archives",
+ " Each command must have exactly one of the following options:",
+ " -c, --create Create a new archive",
+ " -u, --update Add or update files with changed mtime",
+ " -i, --insert Like -u but always add even if unchanged",
+ " -r, --remove Remove files from archive",
+ " -t, --list List contents of archive",
+ " -x, --extract Extract files from archive",
+ " Optional arguments:",
+ " -v, --verbose Print each filename as it is processed",
+ " -f FILE, --file FILE Use archive FILE (default is current db)",
+ " -a FILE, --append FILE Open FILE using the apndvfs VFS",
+ " -C DIR, --directory DIR Read/extract files from directory DIR",
+ " -g, --glob Use glob matching for names in archive",
+ " -n, --dryrun Show the SQL that would have occurred",
+ " Examples:",
+ " .ar -cf ARCHIVE foo bar # Create ARCHIVE from files foo and bar",
+ " .ar -tf ARCHIVE # List members of ARCHIVE",
+ " .ar -xvf ARCHIVE # Verbosely extract files from ARCHIVE",
+ " See also:",
+ " http://sqlite.org/cli.html#sqlite_archive_support",
+];
+DISPATCHABLE_COMMAND( archive ? 0 0 azArg nArg p ){
+ open_db(p, 0);
+ if( ISS(p)->bSafeMode ) return DCR_AbortError;
+ return arDotCommand(p, 0, azArg, nArg);
+}
+
+/*****************
+ * The .auth command
+ */
+CONDITION_COMMAND(auth !defined(SQLITE_OMIT_AUTHORIZATION));
+COLLECT_HELP_TEXT[
+ ".auth ON|OFF Show authorizer callbacks",
+];
+DISPATCHABLE_COMMAND( auth 3 2 2 azArg nArg p ){
+ open_db(p, 0);
+ if( booleanValue(azArg[1]) ){
+ sqlite3_set_authorizer(DBX(p), shellAuth, p);
+ }else if( ISS(p)->bSafeModeFuture ){
+ sqlite3_set_authorizer(DBX(p), safeModeAuth, p);
+ }else{
+ sqlite3_set_authorizer(DBX(p), 0, 0);
+ }
+ return DCR_Ok;
+}
+
+/*****************
+ * The .backup and .save commands (aliases for each other)
+ * These defer to writeDb in the dispatch table, so are not here.
+ */
+CONDITION_COMMAND(backup !defined(SQLITE_SHELL_FIDDLE));
+CONDITION_COMMAND(save !defined(SQLITE_SHELL_FIDDLE) );
+COLLECT_HELP_TEXT[
+ ".backup ?DB? FILE Backup DB (default \"main\") to FILE",
+ " Options:",
+ " --append Use the appendvfs",
+ " --async Write the FILE without journal and fsync()",
+ ".save ?DB? FILE Write DB (default \"main\") to FILE",
+ " Options:",
+ " --append Use the appendvfs",
+ " --async Write the FILE without journal and fsync()",
+];
+DISPATCHABLE_COMMAND( backup 4 2 5 ){
+ return writeDb( azArg, nArg, p, pzErr);
+}
+DISPATCHABLE_COMMAND( save 3 2 5 ){
+ return writeDb( azArg, nArg, p, pzErr);
+}
+
+/*****************
+ * The .bail command
+ */
+COLLECT_HELP_TEXT[
+ ".bail on|off Stop after hitting an error. Default OFF",
+];
+DISPATCHABLE_COMMAND( bail 3 2 2 ){
+ bail_on_error = booleanValue(azArg[1]);
+ return DCR_Ok;
+}
+
+CONDITION_COMMAND(cd !defined(SQLITE_SHELL_FIDDLE));
+/*****************
+ * The .binary and .cd commands
+ */
+COLLECT_HELP_TEXT[
+ ".binary on|off Turn binary output on or off. Default OFF",
+ ".cd DIRECTORY Change the working directory to DIRECTORY",
+];
+DISPATCHABLE_COMMAND( binary 3 2 2 ){
+ if( booleanValue(azArg[1]) ){
+ setBinaryMode(ISS(p)->out, 1);
+ }else{
+ setTextMode(ISS(p)->out, 1);
+ }
+ return DCR_Ok;
+}
+
+DISPATCHABLE_COMMAND( cd ? 2 2 ){
+ int rc=0;
+ if( ISS(p)->bSafeMode ) return DCR_AbortError;
+#if defined(_WIN32) || defined(WIN32)
+ wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
+ rc = !SetCurrentDirectoryW(z);
+ sqlite3_free(z);
+#else
+ rc = chdir(azArg[1]);
+#endif
+ if( rc ){
+ utf8_printf(STD_ERR, "Cannot change to directory \"%s\"\n", azArg[1]);
+ rc = 1;
+ }
+ return DCR_Ok|rc;
+}
+
+/* The ".breakpoint" command causes a call to the no-op routine named
+ * test_breakpoint(). It is undocumented.
+*/
+COLLECT_HELP_TEXT[
+ ",breakpoint calls test_breakpoint(). (a debugging aid)",
+];
+DISPATCHABLE_COMMAND( breakpoint 3 1 1 ){
+ test_breakpoint();
+ return DCR_Ok;
+}
+
+CONDITION_COMMAND(check !defined(SQLITE_SHELL_FIDDLE));
+CONDITION_COMMAND(clone !defined(SQLITE_SHELL_FIDDLE));
+/*****************
+ * The .changes, .check, .clone and .connection commands
+ */
+COLLECT_HELP_TEXT[
+ ".changes on|off Show number of rows changed by SQL",
+ ",check GLOB Fail if output since .testcase does not match",
+ ".clone NEWDB Clone data into NEWDB from the existing database",
+ ".connection [close] [#] Open or close an auxiliary database connection",
+];
+DISPATCHABLE_COMMAND( changes 3 2 2 ){
+ setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( check 3 0 0 ){
+ /* Cancel output redirection, if it is currently set (by .testcase)
+ ** Then read the content of the testcase-out.txt file and compare against
+ ** azArg[1]. If there are differences, report an error and exit.
+ */
+ char *zRes = 0;
+ DotCmdRC rv = DCR_Ok;
+ output_reset(ISS(p));
+ if( nArg!=2 ){
+ return DCR_ArgWrong;
+ }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){
+ *pzErr = smprintf("Error: cannot read 'testcase-out.txt'\n");
+ rv = DCR_Return;
+ }else if( testcase_glob(azArg[1],zRes)==0 ){
+ *pzErr =
+ smprintf("testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n",
+ ISS(p)->zTestcase, azArg[1], zRes);
+ rv = DCR_Error;
+ }else{
+ utf8_printf(STD_OUT, "testcase-%s ok\n", ISS(p)->zTestcase);
+ ISS(p)->nCheck++;
+ }
+ sqlite3_free(zRes);
+ return (zRes==0)? DCR_Abort : rv;
+}
+DISPATCHABLE_COMMAND( clone ? 2 2 ){
+ if( ISS(p)->bSafeMode ) return DCR_AbortError;
+ tryToClone(p, azArg[1]);
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( connection ? 1 4 ){
+ ShellInState *psi = ISS(p);
+ if( nArg==1 ){
+ /* List available connections */
+ int i;
+ for(i=0; i<ArraySize(psi->aAuxDb); i++){
+ const char *zFile = psi->aAuxDb[i].zDbFilename;
+ if( psi->aAuxDb[i].db==0 && psi->pAuxDb!=&psi->aAuxDb[i] ){
+ zFile = "(not open)";
+ }else if( zFile==0 ){
+ zFile = "(memory)";
+ }else if( zFile[0]==0 ){
+ zFile = "(temporary-file)";
+ }
+ if( psi->pAuxDb == &psi->aAuxDb[i] ){
+ utf8_printf(STD_OUT, "ACTIVE %d: %s\n", i, zFile);
+ }else if( psi->aAuxDb[i].db!=0 ){
+ utf8_printf(STD_OUT, " %d: %s\n", i, zFile);
+ }
+ }
+ }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){
+ int i = azArg[1][0] - '0';
+ if( psi->pAuxDb != &psi->aAuxDb[i] && i>=0 && i<ArraySize(psi->aAuxDb) ){
+ psi->pAuxDb->db = DBI(psi);
+ psi->pAuxDb = &psi->aAuxDb[i];
+#if SHELL_DYNAMIC_EXTENSION
+ if( DBI(psi)!=0 ) notify_subscribers(psi, NK_DbUserVanishing, DBI(psi));
+#endif
+ globalDb = DBI(psi) = psi->pAuxDb->db;
+#if SHELL_DYNAMIC_EXTENSION
+ if( DBI(psi)!=0 ) notify_subscribers(psi, NK_DbUserAppeared, DBI(psi));
+#endif
+ psi->pAuxDb->db = 0;
+ }
+ }else if( nArg==3 && cli_strcmp(azArg[1], "close")==0
+ && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){
+ int i = azArg[2][0] - '0';
+ if( i<0 || i>=ArraySize(psi->aAuxDb) ){
+ /* No-op */
+ }else if( psi->pAuxDb == &psi->aAuxDb[i] ){
+ raw_printf(STD_ERR, "cannot close the active database connection\n");
+ return DCR_Error;
+ }else if( psi->aAuxDb[i].db ){
+ session_close_all(psi, i);
+ close_db(psi->aAuxDb[i].db);
+ psi->aAuxDb[i].db = 0;
+ }
+ }else{
+ return DCR_ArgWrong;
+ }
+ return DCR_Ok;
+}
+
+CONDITION_COMMAND(dbinfo SQLITE_SHELL_HAVE_RECOVER);
+/*****************
+ * The .databases, .dbconfig and .dbinfo commands
+ */
+COLLECT_HELP_TEXT[
+ ".databases List names and files of attached databases",
+ ".dbconfig ?op? ?val? List or change sqlite3_db_config() options",
+ ".dbinfo ?DB? Show status information about the database",
+];
+/* Allow garbage arguments on this, to be ignored. */
+DISPATCHABLE_COMMAND( databases 2 1 0 ){
+ int rc;
+ char **azName = 0;
+ int nName = 0;
+ sqlite3_stmt *pStmt;
+ sqlite3 *db;
+ int i;
+ open_db(p, 0);
+ db = DBX(p);
+ rc = sqlite3_prepare_v2(db, "PRAGMA database_list", -1, &pStmt, 0);
+ if( rc ){
+ *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
+ rc = 1;
+ }else{
+ while( sqlite3_step(pStmt)==SQLITE_ROW ){
+ const char *zSchema = (const char *)sqlite3_column_text(pStmt,1);
+ const char *zFile = (const char*)sqlite3_column_text(pStmt,2);
+ if( zSchema==0 || zFile==0 ) continue;
+ azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*));
+ shell_check_oom(azName);
+ azName[nName*2] = strdup(zSchema);
+ shell_check_oom(azName[nName*2]);
+ azName[nName*2+1] = strdup(zFile);
+ shell_check_oom(azName[nName*2+1]);
+ nName++;
+ }
+ }
+ sqlite3_finalize(pStmt);
+ for(i=0; i<nName; i++){
+ int eTxn = sqlite3_txn_state(db, azName[i*2]);
+ int bRdonly = sqlite3_db_readonly(db, azName[i*2]);
+ const char *z = azName[i*2+1];
+ utf8_printf(ISS(p)->out, "%s: %s %s%s\n",
+ azName[i*2],
+ z && z[0] ? z : "\"\"",
+ bRdonly ? "r/o" : "r/w",
+ eTxn==SQLITE_TXN_NONE ? "" :
+ eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
+ free(azName[i*2]);
+ free(azName[i*2+1]);
+ }
+ sqlite3_free(azName);
+ return DCR_Ok|(rc!=0);
+}
+DISPATCHABLE_COMMAND( dbconfig 3 1 3 ){
+ static const struct DbConfigChoices {
+ const char *zName;
+ int op;
+ } aDbConfig[] = {
+ { "defensive", SQLITE_DBCONFIG_DEFENSIVE },
+ { "dqs_ddl", SQLITE_DBCONFIG_DQS_DDL },
+ { "dqs_dml", SQLITE_DBCONFIG_DQS_DML },
+ { "enable_fkey", SQLITE_DBCONFIG_ENABLE_FKEY },
+ { "enable_qpsg", SQLITE_DBCONFIG_ENABLE_QPSG },
+ { "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER },
+ { "enable_view", SQLITE_DBCONFIG_ENABLE_VIEW },
+ { "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER },
+ { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE },
+ { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT },
+ { "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION },
+ { "no_ckpt_on_close", SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE },
+ { "reset_database", SQLITE_DBCONFIG_RESET_DATABASE },
+ { "reverse_scanorder", SQLITE_DBCONFIG_REVERSE_SCANORDER },
+ { "stmt_scanstatus", SQLITE_DBCONFIG_STMT_SCANSTATUS },
+ { "trigger_eqp", SQLITE_DBCONFIG_TRIGGER_EQP },
+ { "trusted_schema", SQLITE_DBCONFIG_TRUSTED_SCHEMA },
+ { "writable_schema", SQLITE_DBCONFIG_WRITABLE_SCHEMA },
+ };
+ int ii, v;
+ open_db(p, 0);
+ for(ii=0; ii<ArraySize(aDbConfig); ii++){
+ if( nArg>1 && cli_strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue;
+ if( nArg>=3 ){
+ sqlite3_db_config(DBX(p), aDbConfig[ii].op, booleanValue(azArg[2]), 0);
+ }
+ sqlite3_db_config(DBX(p), aDbConfig[ii].op, -1, &v);
+ utf8_printf(ISS(p)->out, "%19s %s\n",
+ aDbConfig[ii].zName, v ? "on" : "off");
+ if( nArg>1 ) break;
+ }
+ if( nArg>1 && ii==ArraySize(aDbConfig) ){
+ *pzErr = smprintf("Error: unknown dbconfig \"%s\"\n"
+ "Enter \".dbconfig\" with no arguments for a list\n",
+ azArg[1]);
+ return DCR_ArgWrong;
+ }
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( dbinfo 3 1 2 ){
+ return shell_dbinfo_command(p, nArg, azArg);
+}
+
+/*****************
+ * The .dump, .echo and .eqp commands
+ */
+COLLECT_HELP_TEXT[
+ ".dump ?OBJECTS? Render database content as SQL",
+ " Options:",
+ " --data-only Output only INSERT statements",
+ " --newlines Allow unescaped newline characters in output",
+ " --nosys Omit system tables (ex: \"sqlite_stat1\")",
+ " --preserve-rowids Include ROWID values in the output",
+ " --schema SCHEMA Dump table(s) from given SCHEMA",
+ " OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump",
+ " Additional LIKE patterns can be given in subsequent arguments",
+ ".echo on|off Turn command echo on or off",
+ ".eqp on|off|full|... Enable or disable automatic EXPLAIN QUERY PLAN",
+ " Other Modes:",
+#ifdef SQLITE_DEBUG
+ " test Show raw EXPLAIN QUERY PLAN output",
+ " trace Like \"full\" but enable \"PRAGMA vdbe_trace\"",
+#endif
+ " trigger Like \"full\" but also show trigger bytecode",
+];
+DISPATCHABLE_COMMAND( dump ? 1 2 ){
+ ShellInState *psi = ISS(p);
+ char *zLike = 0;
+ char *zSchema = "main";
+ char *zSql;
+ int i;
+ int savedShowHeader = psi->showHeader;
+ int savedShellFlags = psi->shellFlgs;
+ ShellClearFlag(p,
+ SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo
+ |SHFLG_DumpDataOnly|SHFLG_DumpNoSys);
+ for(i=1; i<nArg; i++){
+ if( azArg[i][0]=='-' ){
+ const char *z = azArg[i]+1;
+ if( z[0]=='-' ) z++;
+ if( cli_strcmp(z,"preserve-rowids")==0 ){
+#ifdef SQLITE_OMIT_VIRTUALTABLE
+ *pzErr = smprintf("The --preserve-rowids option is not compatible"
+ " with SQLITE_OMIT_VIRTUALTABLE\n");
+ sqlite3_free(zLike);
+ return DCR_ArgWrong;
+#else
+ ShellSetFlag(p, SHFLG_PreserveRowid);
+#endif
+ }else{
+ if( cli_strcmp(z,"newlines")==0 ){
+ ShellSetFlag(p, SHFLG_Newlines);
+ }else if( cli_strcmp(z,"data-only")==0 ){
+ ShellSetFlag(p, SHFLG_DumpDataOnly);
+ }else if( cli_strcmp(z,"nosys")==0 ){
+ ShellSetFlag(p, SHFLG_DumpNoSys);
+ }else if( cli_strcmp(z,"schema")==0 && ++i<nArg ){
+ zSchema = azArg[i];
+ }else{
+ *pzErr = smprintf("Unknown option \"%s\" on \".dump\"\n", azArg[i]);
+ sqlite3_free(zLike);
+ return DCR_ArgWrong;
+ }
+ }
+ }else{
+ /* azArg[i] contains a LIKE pattern. This ".dump" request should
+ ** only dump data for tables for which either the table name matches
+ ** the LIKE pattern, or the table appears to be a shadow table of
+ ** a virtual table for which the name matches the LIKE pattern.
+ */
+ char *zExpr = smprintf(
+ "name LIKE %Q ESCAPE '\\' OR EXISTS ("
+ " SELECT 1 FROM %w.sqlite_schema WHERE "
+ " name LIKE %Q ESCAPE '\\' AND"
+ " sql LIKE 'CREATE VIRTUAL TABLE%%' AND"
+ " substr(o.name, 1, length(name)+1) == (name||'_')"
+ ")", azArg[i], zSchema, azArg[i]
+ );
+
+ if( zLike ){
+ zLike = smprintf("%z OR %z", zLike, zExpr);
+ }else{
+ zLike = zExpr;
+ }
+ }
+ }
+
+ open_db(p, 0);
+
+ if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
+ /* When playing back a "dump", the content might appear in an order
+ ** which causes immediate foreign key constraints to be violated.
+ ** So disable foreign-key constraint enforcement to prevent problems. */
+ raw_printf(psi->out, "PRAGMA foreign_keys=OFF;\n");
+ raw_printf(psi->out, "BEGIN TRANSACTION;\n");
+ }
+ psi->writableSchema = 0;
+ psi->showHeader = 0;
+ /* Set writable_schema=ON since doing so forces SQLite to initialize
+ ** as much of the schema as it can even if the sqlite_schema table is
+ ** corrupt. */
+ sqlite3_exec(DBX(p), "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
+ psi->nErr = 0;
+ if( zLike==0 ) zLike = smprintf("true");
+ zSql = smprintf("SELECT name, type, sql FROM %w.sqlite_schema AS o "
+ "WHERE (%s) AND type=='table' AND sql NOT NULL"
+ " ORDER BY tbl_name='sqlite_sequence', rowid",
+ zSchema, zLike);
+ run_schema_dump_query(psi,zSql);
+ sqlite3_free(zSql);
+ if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
+ zSql = smprintf(
+ "SELECT sql FROM sqlite_schema AS o "
+ "WHERE (%s) AND sql NOT NULL"
+ " AND type IN ('index','trigger','view')",
+ zLike
+ );
+ run_table_dump_query(psi, zSql);
+ sqlite3_free(zSql);
+ }
+ sqlite3_free(zLike);
+ if( psi->writableSchema ){
+ raw_printf(psi->out, "PRAGMA writable_schema=OFF;\n");
+ psi->writableSchema = 0;
+ }
+ sqlite3_exec(DBX(p), "PRAGMA writable_schema=OFF;", 0, 0, 0);
+ sqlite3_exec(DBX(p), "RELEASE dump;", 0, 0, 0);
+ if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
+ raw_printf(psi->out, psi->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n");
+ }
+ psi->showHeader = savedShowHeader;
+ psi->shellFlgs = savedShellFlags;
+
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( echo ? 2 2 ){
+ setOrClearFlag(p, SHFLG_Echo, azArg[1]);
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( eqp ? 0 0 ){
+ ShellInState *psi = ISS(p);
+ if( nArg==2 ){
+ psi->autoEQPtest = 0;
+ if( psi->autoEQPtrace ){
+ if( DBX(p) ) sqlite3_exec(DBX(p), "PRAGMA vdbe_trace=OFF;", 0, 0, 0);
+ psi->autoEQPtrace = 0;
+ }
+ if( cli_strcmp(azArg[1],"full")==0 ){
+ psi->autoEQP = AUTOEQP_full;
+ }else if( cli_strcmp(azArg[1],"trigger")==0 ){
+ psi->autoEQP = AUTOEQP_trigger;
+#ifdef SQLITE_DEBUG
+ }else if( cli_strcmp(azArg[1],"test")==0 ){
+ psi->autoEQP = AUTOEQP_on;
+ psi->autoEQPtest = 1;
+ }else if( cli_strcmp(azArg[1],"trace")==0 ){
+ psi->autoEQP = AUTOEQP_full;
+ psi->autoEQPtrace = 1;
+ open_db(p, 0);
+ sqlite3_exec(DBX(p), "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0);
+ sqlite3_exec(DBX(p), "PRAGMA vdbe_trace=ON;", 0, 0, 0);
+#endif
+ }else{
+ psi->autoEQP = (u8)booleanValue(azArg[1]);
+ }
+ }else{
+ return DCR_ArgWrong;
+ }
+ return DCR_Ok;
+}
+
+CONDITION_COMMAND(cease !defined(SQLITE_SHELL_FIDDLE));
+CONDITION_COMMAND(exit !defined(SQLITE_SHELL_FIDDLE));
+CONDITION_COMMAND(quit !defined(SQLITE_SHELL_FIDDLE));
+/*****************
+ * The .cease, .exit and .quit commands
+ * These are together so that their differing effects are apparent.
+ */
+CONDITION_COMMAND(cease defined(SHELL_CEASE));
+COLLECT_HELP_TEXT[
+ ".cease ?CODE? Cease shell operation, with optional return code",
+ " Return code defaults to 0, otherwise is limited to non-signal values",
+ ".exit ?CODE? Exit shell program, maybe with return-code CODE",
+ " Exit immediately if CODE != 0, else functions as \"quit this input\"",
+ ".quit Stop interpreting input stream, exit if primary.",
+];
+DISPATCHABLE_COMMAND( cease 4 1 2 ){
+ /* .cease effects an exit, always. Only the exit code is variable. */
+ int rc = 0;
+ if( nArg>1 ){
+ rc = (int)integerValue(azArg[1]);
+ if( rc>0x7f ) rc = 0x7f;
+ }
+ p->shellAbruptExit = 0x100|rc;
+ return DCR_Exit;
+}
+DISPATCHABLE_COMMAND( exit 3 1 0 ){
+ /* .exit acts like .quit with no argument or a zero argument,
+ * only returning. With a non-zero argument, it effects an exit. */
+ int rc;
+ if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ){
+ rc &= 0xff; /* Mimic effect of legacy call to exit(). */
+ p->shellAbruptExit = 0x100|rc;
+ }
+ return DCR_Return;
+}
+DISPATCHABLE_COMMAND( quit 1 1 0 ){
+ /* .quit would be more aptly named .return, as it does nothing more. */
+ return DCR_Return;
+}
+
+/*****************
+ * The .expert and .explain commands
+ */
+CONDITION_COMMAND( expert !defined(SQLITE_OMIT_VIRTUALTABLE) );
+COLLECT_HELP_TEXT[
+ ".expert Suggest indexes for queries",
+ ".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto",
+];
+DISPATCHABLE_COMMAND( expert ? 1 1 ){
+ ShellInState *psi = ISS(p);
+ int rv = DCR_Ok;
+ char *zErr = 0;
+ int i;
+ int iSample = 0;
+
+ if( psi->bSafeMode ) return DCR_AbortError;
+ assert( psi->expert.pExpert==0 );
+ memset(&psi->expert, 0, sizeof(ExpertInfo));
+
+ open_db(p, 0);
+
+ for(i=1; i<nArg; i++){
+ char *z = azArg[i];
+ int n;
+ if( z[0]=='-' && z[1]=='-' ) z++;
+ n = strlen30(z);
+ if( n>=2 && 0==cli_strncmp(z, "-verbose", n) ){
+ psi->expert.bVerbose = 1;
+ }
+ else if( n>=2 && 0==cli_strncmp(z, "-sample", n) ){
+ if( i==(nArg-1) ){
+ return DCR_Unpaired|i;
+ }else{
+ iSample = (int)integerValue(azArg[++i]);
+ if( iSample<0 || iSample>100 ){
+ *pzErr = smprintf("value out of range: %s\n", azArg[i]);
+ return DCR_ArgWrong|i;
+ }
+ }
+ }
+ else{
+ return DCR_Unknown|i;
+ }
+ }
+
+ psi->expert.pExpert = sqlite3_expert_new(DBI(psi), &zErr);
+ if( psi->expert.pExpert==0 ){
+ *pzErr = smprintf("sqlite3_expert_new: %s\n",
+ zErr ? zErr : "out of memory");
+ return DCR_Error;
+ }else{
+ sqlite3_expert_config(psi->expert.pExpert, EXPERT_CONFIG_SAMPLE, iSample);
+ }
+
+ return DCR_Ok;
+}
+
+DISPATCHABLE_COMMAND( explain ? 1 2 ){
+ /* The ".explain" command is automatic now. It is largely
+ ** pointless, retained purely for backwards compatibility */
+ ShellInState *psi = ISS(p);
+ int val = 1;
+ if( nArg>1 ){
+ if( cli_strcmp(azArg[1],"auto")==0 ){
+ val = 99;
+ }else{
+ val = booleanValue(azArg[1]);
+ }
+ }
+ if( val==1 && psi->mode!=MODE_Explain ){
+ psi->normalMode = psi->mode;
+ psi->mode = MODE_Explain;
+ psi->autoExplain = 0;
+ }else if( val==0 ){
+ if( psi->mode==MODE_Explain ) psi->mode = psi->normalMode;
+ psi->autoExplain = 0;
+ }else if( val==99 ){
+ if( psi->mode==MODE_Explain ) psi->mode = psi->normalMode;
+ psi->autoExplain = 1;
+ }
+ return DCR_Ok;
+}
+
+/*****************
+ * The .excel, .once and .output commands
+ * These share much implementation, so they stick together.
+ */
+CONDITION_COMMAND(excel !defined(SQLITE_SHELL_FIDDLE));
+CONDITION_COMMAND(once !defined(SQLITE_SHELL_FIDDLE));
+CONDITION_COMMAND(output !defined(SQLITE_SHELL_FIDDLE));
+
+COLLECT_HELP_TEXT[
+ ".excel Display the output of next command in spreadsheet",
+ " --bom Prefix the file with a UTF8 byte-order mark",
+ ".once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE",
+ " If FILE begins with '|' then open it as a command to be piped into.",
+ " Options:",
+ " --bom Prefix output with a UTF8 byte-order mark",
+ " -e Send output to the system text editor",
+ " -x Send output as CSV to a spreadsheet (same as \".excel\")",
+ ".output ?FILE? Send output to FILE or stdout if FILE is omitted",
+ " If FILE begins with '|' then open it as a command to be piped into.",
+ " Options:",
+ " --bom Prefix output with a UTF8 byte-order mark",
+ " -e Send output to the system text editor",
+ " -x Send output as CSV to a spreadsheet (same as \".excel\")",
+];
+#ifndef SQLITE_SHELL_FIDDLE
+/* Shared implementation of .excel, .once and .output */
+static DotCmdRC outputRedirs(char *azArg[], int nArg,
+ ShellInState *psi, char **pzErr,
+ int bOnce, int eMode){
+ /* bOnce => 0: .output, 1: .once, 2: .excel */
+ /* eMode => 'x' for excel, else 0 */
+ int rc = 0;
+ char *zFile = 0;
+ u8 bTxtMode = 0;
+ u8 bPutBOM = 0;
+ int i;
+ static unsigned const char zBOM[4] = {0xef,0xbb,0xbf,0};
+ if( psi->bSafeMode ) return DCR_AbortError;
+ for(i=1; i<nArg; i++){
+ char *z = azArg[i];
+ if( z[0]=='-' ){
+ if( z[1]=='-' ) z++;
+ if( cli_strcmp(z,"-bom")==0 ){
+ bPutBOM = 1;
+ }else if( bOnce!=2 && cli_strcmp(z,"-x")==0 ){
+ eMode = 'x'; /* spreadsheet */
+ }else if( bOnce!=2 && cli_strcmp(z,"-e")==0 ){
+ eMode = 'e'; /* text editor */
+ }else{
+ return DCR_Unknown|i;
+ }
+ }else if( zFile==0 && eMode!='e' && eMode!='x' ){
+ zFile = smprintf("%s", z);
+ shell_check_oom(zFile);
+ if( zFile[0]=='|' ){
+ while( i+1<nArg ){
+ zFile = smprintf("%z %s", zFile, azArg[++i]);
+ shell_check_oom(zFile);
+ }
+ break;
+ }
+ }else{
+ sqlite3_free(zFile);
+ return DCR_TooMany|i;
+ }
+ }
+ if( zFile==0 ){
+ zFile = smprintf("stdout");
+ shell_check_oom(zFile);
+ }
+ if( bOnce ){
+ psi->outCount = 2;
+ }else{
+ psi->outCount = 0;
+ }
+ output_reset(psi);
+#ifndef SQLITE_NOHAVE_SYSTEM
+ if( eMode=='e' || eMode=='x' ){
+ psi->doXdgOpen = 1;
+ outputModePush(psi);
+ if( eMode=='x' ){
+ /* spreadsheet mode. Output as CSV. */
+ newTempFile(psi, "csv");
+ psi->shellFlgs &= ~SHFLG_Echo;
+ psi->mode = MODE_Csv;
+ sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator, SEP_Comma);
+ sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, SEP_CrLf);
+ }else{
+ /* text editor mode */
+ newTempFile(psi, "txt");
+ bTxtMode = 1;
+ }
+ sqlite3_free(zFile);
+ zFile = smprintf("%s", psi->zTempFile);
+ }
+#endif /* SQLITE_NOHAVE_SYSTEM */
+ shell_check_oom(zFile);
+ if( zFile[0]=='|' ){
+#ifdef SQLITE_OMIT_POPEN
+ *pzErr = smprintf("pipes are not supported in this OS\n");
+ rc = 1;
+ psi->out = STD_OUT;
+#else
+ psi->out = popen(zFile + 1, "w");
+ if( psi->out==0 ){
+ *pzErr = smprintf("cannot open pipe \"%s\"\n", zFile + 1);
+ psi->out = STD_OUT;
+ rc = 1;
+ }else{
+ if( bPutBOM ) fwrite(zBOM, 1, 3, psi->out);
+ sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile);
+ }
+#endif
+ }else{
+ psi->out = output_file_open(zFile, bTxtMode);
+ if( psi->out==0 ){
+ if( cli_strcmp(zFile,"off")!=0 ){
+ *pzErr = smprintf("cannot write to \"%s\"\n", zFile);
+ }
+ psi->out = STD_OUT;
+ rc = 1;
+ } else {
+ if( bPutBOM ) fwrite(zBOM, 1, 3, psi->out);
+ sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile);
+ }
+ }
+ sqlite3_free(zFile);
+ return DCR_Ok|rc;
+}
+#endif /* !defined(SQLITE_SHELL_FIDDLE)*/
+
+DISPATCHABLE_COMMAND( excel ? 1 2 ){
+ return outputRedirs(azArg, nArg, ISS(p), pzErr, 2, 'x');
+}
+DISPATCHABLE_COMMAND( once ? 1 6 ){
+ return outputRedirs(azArg, nArg, ISS(p), pzErr, 1, 0);
+}
+DISPATCHABLE_COMMAND( output ? 1 6 ){
+ return outputRedirs(azArg, nArg, ISS(p), pzErr, 0, 0);
+}
+
+
+/*****************
+ * The .filectrl and fullschema commands
+ */
+COLLECT_HELP_TEXT[
+ ".filectrl CMD ... Run various sqlite3_file_control() operations",
+ " --schema SCHEMA Use SCHEMA instead of \"main\"",
+ " --help Show CMD details",
+ ".fullschema ?--indent? Show schema and the content of sqlite_stat tables",
+];
+DISPATCHABLE_COMMAND( filectrl ? 2 0 ){
+ static const struct {
+ const char *zCtrlName; /* Name of a test-control option */
+ int ctrlCode; /* Integer code for that option */
+ const char *zUsage; /* Usage notes */
+ } aCtrl[] = {
+ { "chunk_size", SQLITE_FCNTL_CHUNK_SIZE, "SIZE" },
+ { "data_version", SQLITE_FCNTL_DATA_VERSION, "" },
+ { "has_moved", SQLITE_FCNTL_HAS_MOVED, "" },
+ { "lock_timeout", SQLITE_FCNTL_LOCK_TIMEOUT, "MILLISEC" },
+ { "persist_wal", SQLITE_FCNTL_PERSIST_WAL, "[BOOLEAN]" },
+ /* { "pragma", SQLITE_FCNTL_PRAGMA, "NAME ARG" },*/
+ { "psow", SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]" },
+ { "reserve_bytes", SQLITE_FCNTL_RESERVE_BYTES, "[N]" },
+ { "size_limit", SQLITE_FCNTL_SIZE_LIMIT, "[LIMIT]" },
+ { "tempfilename", SQLITE_FCNTL_TEMPFILENAME, "" },
+ /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY, "COUNT DELAY" },*/
+ };
+ ShellInState *psi = ISS(p);
+ int filectrl = -1;
+ int iCtrl = -1;
+ sqlite3_int64 iRes = 0; /* Integer result to display if rc2==1 */
+ int isOk = 0; /* 0: usage 1: %lld 2: no-result */
+ int n2, i;
+ const char *zCmd = 0;
+ const char *zSchema = 0;
+
+ open_db(p, 0);
+ zCmd = nArg>=2 ? azArg[1] : "help";
+
+ if( zCmd[0]=='-'
+ && (cli_strcmp(zCmd,"--schema")==0 || cli_strcmp(zCmd,"-schema")==0)
+ && nArg>=4
+ ){
+ zSchema = azArg[2];
+ for(i=3; i<nArg; i++) azArg[i-2] = azArg[i];
+ nArg -= 2;
+ zCmd = azArg[1];
+ }
+
+ /* The argument can optionally begin with "-" or "--" */
+ if( zCmd[0]=='-' && zCmd[1] ){
+ zCmd++;
+ if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
+ }
+
+ /* --help lists all file-controls */
+ if( cli_strcmp(zCmd,"help")==0 ){
+ utf8_printf(psi->out, "Available file-controls:\n");
+ for(i=0; i<ArraySize(aCtrl); i++){
+ utf8_printf(psi->out, " .filectrl %s %s\n",
+ aCtrl[i].zCtrlName, aCtrl[i].zUsage);
+ }
+ return DCR_Error;
+ }
+
+ /* Convert filectrl text option to value. Allow any
+ ** unique prefix of the option name, or a numerical value. */
+ n2 = strlen30(zCmd);
+ for(i=0; i<ArraySize(aCtrl); i++){
+ if( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
+ if( filectrl<0 ){
+ filectrl = aCtrl[i].ctrlCode;
+ iCtrl = i;
+ }else{
+ *pzErr = smprintf("ambiguous file-control: \"%s\"\n"
+ "Use \".filectrl --help\" for help\n", zCmd);
+ return DCR_ArgWrong;
+ }
+ }
+ }
+ if( filectrl<0 ){
+ *pzErr = smprintf("unknown file-control: %s\n"
+ "Use \".filectrl --help\" for help\n", zCmd);
+ return DCR_ArgWrong;
+ }else{
+ switch(filectrl){
+ case SQLITE_FCNTL_SIZE_LIMIT: {
+ if( nArg!=2 && nArg!=3 ) break;
+ iRes = nArg==3 ? integerValue(azArg[2]) : -1;
+ sqlite3_file_control(DBX(p), zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes);
+ isOk = 1;
+ break;
+ }
+ case SQLITE_FCNTL_LOCK_TIMEOUT:
+ case SQLITE_FCNTL_CHUNK_SIZE: {
+ int x;
+ if( nArg!=3 ) break;
+ x = (int)integerValue(azArg[2]);
+ sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
+ isOk = 2;
+ break;
+ }
+ case SQLITE_FCNTL_PERSIST_WAL:
+ case SQLITE_FCNTL_POWERSAFE_OVERWRITE: {
+ int x;
+ if( nArg!=2 && nArg!=3 ) break;
+ x = nArg==3 ? booleanValue(azArg[2]) : -1;
+ sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
+ iRes = x;
+ isOk = 1;
+ break;
+ }
+ case SQLITE_FCNTL_DATA_VERSION:
+ case SQLITE_FCNTL_HAS_MOVED: {
+ int x;
+ if( nArg!=2 ) break;
+ sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
+ iRes = x;
+ isOk = 1;
+ break;
+ }
+ case SQLITE_FCNTL_TEMPFILENAME: {
+ char *z = 0;
+ if( nArg!=2 ) break;
+ sqlite3_file_control(DBX(p), zSchema, filectrl, &z);
+ if( z ){
+ utf8_printf(psi->out, "%s\n", z);
+ sqlite3_free(z);
+ }
+ isOk = 2;
+ break;
+ }
+ case SQLITE_FCNTL_RESERVE_BYTES: {
+ int x;
+ if( nArg>=3 ){
+ x = atoi(azArg[2]);
+ sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
+ }
+ x = -1;
+ sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
+ utf8_printf(psi->out,"%d\n", x);
+ isOk = 2;
+ break;
+ }
+ }
+ }
+ if( isOk==0 && iCtrl>=0 ){
+ utf8_printf(psi->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
+ return DCR_CmdErred;
+ }else if( isOk==1 ){
+ char zBuf[100];
+ sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes);
+ raw_printf(psi->out, "%s\n", zBuf);
+ }
+ return DCR_Ok;
+}
+
+DISPATCHABLE_COMMAND( fullschema ? 1 2 ){
+ int rc;
+ ShellInState data;
+ ShellExState datax;
+ int doStats = 0;
+ /* Consider some refactoring to avoid this wholesale copying. */
+ memcpy(&data, ISS(p), sizeof(data));
+ memcpy(&datax, p, sizeof(datax));
+ data.pSXS = &datax;
+ datax.pSIS = &data;
+ data.showHeader = 0;
+ data.cMode = data.mode = MODE_Semi;
+ if( nArg==2 && optionMatch(azArg[1], "indent") ){
+ data.cMode = data.mode = MODE_Pretty;
+ nArg = 1;
+ }
+ if( nArg!=1 ){
+ return DCR_TooMany|1;
+ }
+ open_db(p, 0);
+ rc = sqlite3_exec(datax.dbUser,
+ "SELECT sql FROM"
+ " (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x"
+ " FROM sqlite_schema UNION ALL"
+ " SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) "
+ "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' "
+ "ORDER BY x",
+ callback, &datax, 0
+ );
+ if( rc==SQLITE_OK ){
+ sqlite3_stmt *pStmt;
+ rc = sqlite3_prepare_v2(datax.dbUser,
+ "SELECT rowid FROM sqlite_schema"
+ " WHERE name GLOB 'sqlite_stat[134]'",
+ -1, &pStmt, 0);
+ doStats = sqlite3_step(pStmt)==SQLITE_ROW;
+ sqlite3_finalize(pStmt);
+ }
+ if( doStats==0 ){
+ raw_printf(data.out, "/* No STAT tables available */\n");
+ }else{
+ raw_printf(data.out, "ANALYZE sqlite_schema;\n");
+ data.cMode = data.mode = MODE_Insert;
+ datax.zDestTable = "sqlite_stat1";
+ shell_exec(&datax, "SELECT * FROM sqlite_stat1", 0);
+ datax.zDestTable = "sqlite_stat4";
+ shell_exec(&datax, "SELECT * FROM sqlite_stat4", 0);
+ raw_printf(data.out, "ANALYZE sqlite_schema;\n");
+ }
+ return rc > 0;
+}
+
+/*****************
+ * The .headers command
+ */
+COLLECT_HELP_TEXT[
+ ".headers on|off Turn display of headers on or off",
+];
+DISPATCHABLE_COMMAND( headers 6 2 2 ){
+ ISS(p)->showHeader = booleanValue(azArg[1]);
+ ISS(p)->shellFlgs |= SHFLG_HeaderSet;
+ return DCR_Ok;
+}
+
+/*****************
+ * The .help command
+ */
+
+/* This literal's value AND address are used for help's workings. */
+static const char *zHelpAll = "-all";
+
+COLLECT_HELP_TEXT[
+ ".help ?PATTERN?|?-all? Show help for PATTERN or everything, or summarize",
+ " Repeat -all to see undocumented commands",
+];
+DISPATCHABLE_COMMAND( help 3 1 3 ){
+ const char *zPat = 0;
+ FILE *out = ISS(p)->out;
+ if( nArg>1 ){
+ char *z = azArg[1];
+ if( nArg==3 && cli_strcmp(z, zHelpAll)==0
+ && cli_strcmp(azArg[2], zHelpAll)==0 ){
+ /* Show the undocumented command help */
+ zPat = zHelpAll;
+ }else if( cli_strcmp(z,"-a")==0 || optionMatch(z, "all") ){
+ zPat = "";
+ }else{
+ zPat = z;
+ }
+ }
+ if( showHelp(out, zPat, p)==0 && nArg>1 ){
+ utf8_printf(out, "Nothing matches '%s'\n", azArg[1]);
+ }
+ /* Help pleas never fail! */
+ return DCR_Ok;
+}
+
+CONDITION_COMMAND(import !defined(SQLITE_SHELL_FIDDLE));
+/*****************
+ * The .import command
+ */
+COLLECT_HELP_TEXT[
+ ".import FILE TABLE Import data from FILE into TABLE",
+ " Options:",
+ " --ascii Use \\037 and \\036 as column and row separators",
+ " --csv Use , and \\n as column and row separators",
+ " --skip N Skip the first N rows of input",
+ " --schema S Target table to be S.TABLE",
+ " -v \"Verbose\" - increase auxiliary output",
+ " Notes:",
+ " * If TABLE does not exist, it is created. The first row of input",
+ " determines the column names.",
+ " * If neither --csv or --ascii are used, the input mode is derived",
+ " from the \".mode\" output mode",
+ " * If FILE begins with \"|\" then it is a command that generates the",
+ " input text.",
+];
+DISPATCHABLE_COMMAND( import ? 3 7 ){
+ char *zTable = 0; /* Insert data into this table */
+ char *zSchema = 0; /* within this schema (may default to "main") */
+ char *zFile = 0; /* Name of file to extra content from */
+ sqlite3_stmt *pStmt = NULL; /* A statement */
+ int nCol; /* Number of columns in the table */
+ int nByte; /* Number of bytes in an SQL string */
+ int i, j; /* Loop counters */
+ int needCommit; /* True to COMMIT or ROLLBACK at end */
+ int nSep; /* Number of bytes in psi->colSeparator[] */
+ char *zSql; /* An SQL statement */
+ char *zFullTabName; /* Table name with schema if applicable */
+ ImportCtx sCtx; /* Reader context */
+ char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
+ int eVerbose = 0; /* Larger for more console output */
+ int nSkip = 0; /* Initial lines to skip */
+ int useOutputMode = 1; /* Use output mode to determine separators */
+ FILE *out = ISS(p)->out; /* output stream */
+ char *zCreate = 0; /* CREATE TABLE statement text */
+ ShellInState *psi = ISS(p);
+ int rc = 0;
+
+ if(psi->bSafeMode) return DCR_AbortError;
+ memset(&sCtx, 0, sizeof(sCtx));
+ if( psi->mode==MODE_Ascii ){
+ xRead = ascii_read_one_field;
+ }else{
+ xRead = csv_read_one_field;
+ }
+ for(i=1; i<nArg; i++){
+ char *z = azArg[i];
+ if( z[0]=='-' && z[1]=='-' ) z++;
+ if( z[0]!='-' ){
+ if( zFile==0 ){
+ zFile = z;
+ }else if( zTable==0 ){
+ zTable = z;
+ }else{
+ return DCR_TooMany|i;
+ }
+ }else if( cli_strcmp(z,"-v")==0 ){
+ eVerbose++;
+ }else if( cli_strcmp(z,"-schema")==0 && i<nArg-1 ){
+ zSchema = azArg[++i];
+ }else if( cli_strcmp(z,"-skip")==0 && i<nArg-1 ){
+ nSkip = integerValue(azArg[++i]);
+ }else if( cli_strcmp(z,"-ascii")==0 ){
+ sCtx.cColSep = SEP_Unit[0];
+ sCtx.cRowSep = SEP_Record[0];
+ xRead = ascii_read_one_field;
+ useOutputMode = 0;
+ }else if( cli_strcmp(z,"-csv")==0 ){
+ sCtx.cColSep = ',';
+ sCtx.cRowSep = '\n';
+ xRead = csv_read_one_field;
+ useOutputMode = 0;
+ }else{
+ return DCR_Unknown|i;
+ }
+ }
+ if( zTable==0 ){
+ *pzErr = smprintf("missing %s argument.\n", zFile==0 ? "FILE" : "TABLE");
+ return DCR_Missing;
+ }
+ seenInterrupt = 0;
+ open_db(p, 0);
+ if( useOutputMode ){
+ const char *zYap = 0;
+ /* If neither the --csv or --ascii options are specified, then set
+ ** the column and row separator characters from the output mode. */
+ nSep = strlen30(psi->colSeparator);
+ if( nSep==0 ){
+ zYap = "non-null column separator required for import";
+ }
+ if( nSep>1 ){
+ zYap = "multi-character or multi-byte column separators"
+ " not allowed for import";
+ }
+ nSep = strlen30(psi->rowSeparator);
+ if( nSep==0 ){
+ zYap = "non-null row separator required for import";
+ }
+ if( zYap!=0 ){
+ *pzErr = smprintf("%s\n", zYap);
+ return DCR_Error;
+ }
+ if( nSep==2 && psi->mode==MODE_Csv
+ && cli_strcmp(psi->rowSeparator,SEP_CrLf)==0 ){
+ /* When importing CSV (only), if the row separator is set to the
+ ** default output row separator, change it to the default input
+ ** row separator. This avoids having to maintain different input
+ ** and output row separators. */
+ sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, SEP_Row);
+ nSep = strlen30(psi->rowSeparator);
+ }
+ if( nSep>1 ){
+ *pzErr
+ = smprintf("multi-character row separators not allowed for import\n");
+ return DCR_Error;
+ }
+ sCtx.cColSep = (u8)psi->colSeparator[0];
+ sCtx.cRowSep = (u8)psi->rowSeparator[0];
+ }
+ sCtx.zFile = zFile;
+ sCtx.nLine = 1;
+ if( sCtx.zFile[0]=='|' ){
+#ifdef SQLITE_OMIT_POPEN
+ *pzErr = smprintf("pipes are not supported in this OS\n");
+ return DCR_Error;
+#else
+ sCtx.in = popen(sCtx.zFile+1, "r");
+ sCtx.zFile = "<pipe>";
+ sCtx.xCloser = pclose;
+#endif
+ }else{
+ sCtx.in = fopen(sCtx.zFile, "rb");
+ sCtx.xCloser = fclose;
+ }
+ if( sCtx.in==0 ){
+ *pzErr = smprintf("cannot open \"%s\"\n", zFile);
+ return DCR_Error;
+ }
+ sCtx.z = sqlite3_malloc64(120);
+ if( sCtx.z==0 ){
+ import_cleanup(&sCtx);
+ shell_out_of_memory();
+ }
+ /* Here and below, resources must be freed before exit. */
+ if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
+ char zSep[2];
+ zSep[1] = 0;
+ zSep[0] = sCtx.cColSep;
+ utf8_printf(out, "Column separator ");
+ output_c_string(out, zSep);
+ utf8_printf(out, ", row separator ");
+ zSep[0] = sCtx.cRowSep;
+ output_c_string(out, zSep);
+ utf8_printf(out, "\n");
+ }
+ while( (nSkip--)>0 ){
+ while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
+ }
+ if( zSchema!=0 ){
+ zFullTabName = smprintf("\"%w\".\"%w\"", zSchema, zTable);
+ }else{
+ zFullTabName = smprintf("\"%w\"", zTable);
+ }
+ zSql = smprintf("SELECT * FROM %s", zFullTabName);
+ if( zSql==0 || zFullTabName==0 ){
+ import_cleanup(&sCtx);
+ shell_out_of_memory();
+ }
+ nByte = strlen30(zSql);
+ rc = sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0);
+ import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */
+ if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(DBX(p)))==0 ){
+ zCreate = smprintf("CREATE TABLE %s", zFullTabName);
+ sqlite3 *dbCols = 0;
+ char *zRenames = 0;
+ char *zColDefs;
+ while( xRead(&sCtx) ){
+ zAutoColumn(sCtx.z, &dbCols, 0);
+ if( sCtx.cTerm!=sCtx.cColSep ) break;
+ }
+ zColDefs = zAutoColumn(0, &dbCols, &zRenames);
+ if( zRenames!=0 ){
+ FILE *fh = INSOURCE_IS_INTERACTIVE(psi->pInSource)? out : STD_ERR;
+ utf8_printf(fh, "Columns renamed during .import %s due to duplicates:\n"
+ "%s\n", sCtx.zFile, zRenames);
+ sqlite3_free(zRenames);
+ }
+ assert(dbCols==0);
+ if( zColDefs==0 ){
+ *pzErr = smprintf("%s: empty file\n", sCtx.zFile);
+ sqlite3_free(zCreate);
+ import_fail: /* entry from outer blocks */
+ sqlite3_free(zSql);
+ sqlite3_free(zFullTabName);
+ import_cleanup(&sCtx);
+ return DCR_Error;
+ }
+ zCreate = smprintf("%z%z\n", zCreate, zColDefs);
+ if( eVerbose>=1 ){
+ utf8_printf(out, "%s\n", zCreate);
+ }
+ rc = sqlite3_exec(DBX(p), zCreate, 0, 0, 0);
+ sqlite3_free(zCreate);
+ if( rc ){
+ *pzErr = smprintf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(DBX(p)));
+ goto import_fail;
+ }
+ rc = sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0);
+ }
+ if( rc ){
+ if (pStmt) sqlite3_finalize(pStmt);
+ *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
+ goto import_fail;
+ }
+ sqlite3_free(zSql);
+ nCol = sqlite3_column_count(pStmt);
+ sqlite3_finalize(pStmt);
+ pStmt = 0;
+ if( nCol==0 ) return DCR_Ok; /* no columns, no error */
+ zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 );
+ if( zSql==0 ){
+ import_cleanup(&sCtx);
+ shell_out_of_memory();
+ }
+ sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName);
+ j = strlen30(zSql);
+ for(i=1; i<nCol; i++){
+ zSql[j++] = ',';
+ zSql[j++] = '?';
+ }
+ zSql[j++] = ')';
+ zSql[j] = 0;
+ if( eVerbose>=2 ){
+ utf8_printf(psi->out, "Insert using: %s\n", zSql);
+ }
+ rc = sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0);
+ if( rc ){
+ *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
+ if (pStmt) sqlite3_finalize(pStmt);
+ goto import_fail;
+ }
+ sqlite3_free(zSql);
+ sqlite3_free(zFullTabName);
+ needCommit = sqlite3_get_autocommit(DBX(p));
+ if( needCommit ) sqlite3_exec(DBX(p), "BEGIN", 0, 0, 0);
+ do{
+ int startLine = sCtx.nLine;
+ for(i=0; i<nCol; i++){
+ char *z = xRead(&sCtx);
+ /*
+ ** Did we reach end-of-file before finding any columns?
+ ** If so, stop instead of NULL filling the remaining columns.
+ */
+ if( z==0 && i==0 ) break;
+ /*
+ ** Did we reach end-of-file OR end-of-line before finding any
+ ** columns in ASCII mode? If so, stop instead of NULL filling
+ ** the remaining columns.
+ */
+ if( psi->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
+ sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
+ if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
+ utf8_printf(STD_ERR, "%s:%d: expected %d columns but found %d - "
+ "filling the rest with NULL\n",
+ sCtx.zFile, startLine, nCol, i+1);
+ i += 2;
+ while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; }
+ }
+ }
+ if( sCtx.cTerm==sCtx.cColSep ){
+ do{
+ xRead(&sCtx);
+ i++;
+ }while( sCtx.cTerm==sCtx.cColSep );
+ utf8_printf(STD_ERR, "%s:%d: expected %d columns but found %d - "
+ "extras ignored\n",
+ sCtx.zFile, startLine, nCol, i);
+ }
+ if( i>=nCol ){
+ sqlite3_step(pStmt);
+ rc = sqlite3_reset(pStmt);
+ if( rc!=SQLITE_OK ){
+ utf8_printf(STD_ERR, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
+ startLine, sqlite3_errmsg(DBX(p)));
+ sCtx.nErr++;
+ }else{
+ sCtx.nRow++;
+ }
+ }
+ }while( sCtx.cTerm!=EOF );
+
+ import_cleanup(&sCtx);
+ sqlite3_finalize(pStmt);
+ if( needCommit ) sqlite3_exec(DBX(p), "COMMIT", 0, 0, 0);
+ if( eVerbose>0 ){
+ utf8_printf(out,
+ "Added %d rows with %d errors using %d lines of input\n",
+ sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
+ }
+ return DCR_Ok|(sCtx.nErr>0);
+}
+
+/*****************
+ * The .keyword command
+ */
+CONDITION_COMMAND( keyword !defined(NO_KEYWORD_COMMAND) );
+COLLECT_HELP_TEXT[
+ ".keyword ?KW? List keywords, or say whether KW is one.",
+];
+DISPATCHABLE_COMMAND( keyword ? 1 2 ){
+ FILE *out = ISS(p)->out;
+ if( nArg<2 ){
+ int i = 0;
+ int nk = sqlite3_keyword_count();
+ int nCol = 0;
+ int szKW;
+ while( i<nk ){
+ const char *zKW = 0;
+ if( SQLITE_OK==sqlite3_keyword_name(i++, &zKW, &szKW) ){
+ char kwBuf[50];
+ if( szKW < sizeof(kwBuf) ){
+ const char *zSep = " ";
+ if( (nCol += (1+szKW))>75){
+ zSep = "\n";
+ nCol = 0;
+ }
+ memcpy(kwBuf, zKW, szKW);
+ kwBuf[szKW] = 0;
+ utf8_printf(out, "%s%s", kwBuf, zSep);
+ }
+ }
+ }
+ if( nCol>0 ) utf8_printf(out, "\n");
+ }else{
+ int szKW = strlen30(azArg[1]);
+ int isKeyword = sqlite3_keyword_check(azArg[1], szKW);
+ utf8_printf(out, "%s is%s a keyword\n",
+ azArg[1], (isKeyword)? "" : " not");
+ }
+ return DCR_Ok;
+}
+
+/*****************
+ * The .imposter, .iotrace, .limit, .lint and .log commands
+ */
+#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE)
+# define LOAD_ENABLE 1
+#else
+# define LOAD_ENABLE 0
+#endif
+CONDITION_COMMAND( imposter !defined(SQLITE_OMIT_TEST_CONTROL) );
+CONDITION_COMMAND( iotrace defined(SQLITE_ENABLE_IOTRACE) );
+CONDITION_COMMAND( load LOAD_ENABLE );
+COLLECT_HELP_TEXT[
+ ",imposter INDEX TABLE Create imposter table TABLE on index INDEX",
+ ",iotrace FILE Enable I/O diagnostic logging to FILE",
+ ".limit ?LIMIT? ?VAL? Display or change the value of an SQLITE_LIMIT",
+ ".lint OPTIONS Report potential schema issues.",
+ " Options:",
+ " fkey-indexes Find missing foreign key indexes",
+];
+COLLECT_HELP_TEXT[
+#if !defined(SQLITE_SHELL_FIDDLE)
+ ".log FILE|on|off Turn logging on or off. FILE can be stderr/stdout",
+#else
+ ".log on|off Turn logging on or off.",
+#endif
+];
+DISPATCHABLE_COMMAND( imposter ? 3 3 ){
+ int rc = 0;
+ char *zSql;
+ char *zCollist = 0;
+ sqlite3_stmt *pStmt;
+ sqlite3 *db;
+ int tnum = 0;
+ int isWO = 0; /* True if making an imposter of a WITHOUT ROWID table */
+ int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */
+ int i;
+ if( !ShellHasFlag(p,SHFLG_TestingMode) ){
+ utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n",
+ "imposter");
+ return DCR_Error;
+ }
+ if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){
+ *pzErr = smprintf("Usage: .imposter INDEX IMPOSTER\n"
+ " .imposter off\n");
+ /* Also allowed, but not documented:
+ **
+ ** .imposter TABLE IMPOSTER
+ **
+ ** where TABLE is a WITHOUT ROWID table. In that case, the
+ ** imposter is another WITHOUT ROWID table with the columns in
+ ** storage order. */
+ return DCR_SayUsage;
+ }
+ open_db(p, 0);
+ db = DBX(p);
+ if( nArg==2 ){
+ sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 1);
+ return DCR_Ok;
+ }
+ zSql = smprintf("SELECT rootpage, 0 FROM sqlite_schema"
+ " WHERE name='%q' AND type='index'"
+ "UNION ALL "
+ "SELECT rootpage, 1 FROM sqlite_schema"
+ " WHERE name='%q' AND type='table'"
+ " AND sql LIKE '%%without%%rowid%%'",
+ azArg[1], azArg[1]);
+ sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ sqlite3_free(zSql);
+ if( sqlite3_step(pStmt)==SQLITE_ROW ){
+ tnum = sqlite3_column_int(pStmt, 0);
+ isWO = sqlite3_column_int(pStmt, 1);
+ }
+ sqlite3_finalize(pStmt);
+ zSql = smprintf("PRAGMA index_xinfo='%q'", azArg[1]);
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ sqlite3_free(zSql);
+ i = 0;
+ while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
+ char zLabel[20];
+ const char *zCol = (const char*)sqlite3_column_text(pStmt,2);
+ i++;
+ if( zCol==0 ){
+ if( sqlite3_column_int(pStmt,1)==-1 ){
+ zCol = "_ROWID_";
+ }else{
+ sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i);
+ zCol = zLabel;
+ }
+ }
+ if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){
+ lenPK = (int)strlen(zCollist);
+ }
+ if( zCollist==0 ){
+ zCollist = smprintf("\"%w\"", zCol);
+ }else{
+ zCollist = smprintf("%z,\"%w\"", zCollist, zCol);
+ }
+ }
+ sqlite3_finalize(pStmt);
+ if( i==0 || tnum==0 ){
+ *pzErr = smprintf("no such index: \"%s\"\n", azArg[1]);
+ sqlite3_free(zCollist);
+ return DCR_Error;
+ }
+ if( lenPK==0 ) lenPK = 100000;
+ zSql = smprintf("CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))"
+ "WITHOUT ROWID", azArg[2], zCollist, lenPK, zCollist);
+ sqlite3_free(zCollist);
+ rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 1, tnum);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_exec(db, zSql, 0, 0, 0);
+ sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 0);
+ if( rc ){
+ *pzErr = smprintf("Error in [%s]: %s\n", zSql, sqlite3_errmsg(db));
+ }else{
+ utf8_printf(STD_OUT, "%s;\n", zSql);
+ raw_printf(STD_OUT, "WARNING: "
+ "writing to an imposter table will corrupt the \"%s\" %s!\n",
+ azArg[1], isWO ? "table" : "index"
+ );
+ }
+ }else{
+ *pzErr = smprintf("SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
+ }
+ sqlite3_free(zSql);
+ return rc != 0;
+}
+DISPATCHABLE_COMMAND( iotrace ? 2 2 ){
+ SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...);
+ if( iotrace && iotrace!=STD_OUT ) fclose(iotrace);
+ iotrace = 0;
+ if( nArg<2 ){
+ sqlite3IoTrace = 0;
+ }else if( cli_strcmp(azArg[1], "-")==0 ){
+ sqlite3IoTrace = iotracePrintf;
+ iotrace = STD_OUT;
+ }else{
+ iotrace = fopen(azArg[1], "w");
+ if( iotrace==0 ){
+ *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
+ sqlite3IoTrace = 0;
+ return DCR_Error;
+ }else{
+ sqlite3IoTrace = iotracePrintf;
+ }
+ }
+ return DCR_Ok;
+}
+
+/*****************
+ * The .limits and .load commands
+ */
+COLLECT_HELP_TEXT[
+ ",limits ?LIMIT_NAME? Display limit selected by its name, or all limits",
+ ".load FILE ?ENTRY? Load a SQLite extension library",
+ " If ENTRY is provided, the entry point \"sqlite_ENTRY_init\" is called.",
+ " Otherwise, the entry point name is derived from the FILE's name.",
+];
+
+DISPATCHABLE_COMMAND( limits 5 1 3 ){
+ static const struct {
+ const char *zLimitName; /* Name of a limit */
+ int limitCode; /* Integer code for that limit */
+ } aLimit[] = {
+ { "length", SQLITE_LIMIT_LENGTH },
+ { "sql_length", SQLITE_LIMIT_SQL_LENGTH },
+ { "column", SQLITE_LIMIT_COLUMN },
+ { "expr_depth", SQLITE_LIMIT_EXPR_DEPTH },
+ { "compound_select", SQLITE_LIMIT_COMPOUND_SELECT },
+ { "vdbe_op", SQLITE_LIMIT_VDBE_OP },
+ { "function_arg", SQLITE_LIMIT_FUNCTION_ARG },
+ { "attached", SQLITE_LIMIT_ATTACHED },
+ { "like_pattern_length", SQLITE_LIMIT_LIKE_PATTERN_LENGTH },
+ { "variable_number", SQLITE_LIMIT_VARIABLE_NUMBER },
+ { "trigger_depth", SQLITE_LIMIT_TRIGGER_DEPTH },
+ { "worker_threads", SQLITE_LIMIT_WORKER_THREADS },
+ };
+ int i, n2;
+ open_db(p, 0);
+ if( nArg==1 ){
+ for(i=0; i<ArraySize(aLimit); i++){
+ fprintf(STD_OUT, "%20s %d\n", aLimit[i].zLimitName,
+ sqlite3_limit(DBX(p), aLimit[i].limitCode, -1));
+ }
+ }else if( nArg>3 ){
+ return DCR_TooMany;
+ }else{
+ int iLimit = -1;
+ n2 = strlen30(azArg[1]);
+ for(i=0; i<ArraySize(aLimit); i++){
+ if( sqlite3_strnicmp(aLimit[i].zLimitName, azArg[1], n2)==0 ){
+ if( iLimit<0 ){
+ iLimit = i;
+ }else{
+ *pzErr = smprintf("ambiguous limit: \"%s\"\n", azArg[1]);
+ return DCR_Error;
+ }
+ }
+ }
+ if( iLimit<0 ){
+ *pzErr = smprintf("unknown limit: \"%s\"\n"
+ "enter \".limits\" with no arguments for a list.\n",
+ azArg[1]);
+ return DCR_ArgWrong;
+ }
+ if( nArg==3 ){
+ sqlite3_limit(DBX(p), aLimit[iLimit].limitCode,
+ (int)integerValue(azArg[2]));
+ }
+ fprintf(STD_OUT, "%20s %d\n", aLimit[iLimit].zLimitName,
+ sqlite3_limit(DBX(p), aLimit[iLimit].limitCode, -1));
+ }
+ return DCR_Ok;
+}
+
+DISPATCHABLE_COMMAND( lint 3 1 0 ){
+ sqlite3 *db; /* Database handle to query "main" db of */
+ FILE *out = ISS(p)->out; /* Stream to write non-error output to */
+ int bVerbose = 0; /* If -verbose is present */
+ int bGroupByParent = 0; /* If -groupbyparent is present */
+ int i; /* To iterate through azArg[] */
+ const char *zIndent = ""; /* How much to indent CREATE INDEX by */
+ int rc; /* Return code */
+ sqlite3_stmt *pSql = 0; /* Compiled version of SQL statement below */
+
+ i = (nArg>=2 ? strlen30(azArg[1]) : 0);
+ if( i==0 || 0!=sqlite3_strnicmp(azArg[1], "fkey-indexes", i) ){
+ *pzErr = smprintf
+ ("Usage %s sub-command ?switches...?\n"
+ "Where sub-commands are:\n"
+ " fkey-indexes\n", azArg[0]);
+ return DCR_SayUsage;
+ }
+ open_db(p, 0);
+ db = DBX(p);
+
+ /*
+ ** This SELECT statement returns one row for each foreign key constraint
+ ** in the schema of the main database. The column values are:
+ **
+ ** 0. The text of an SQL statement similar to:
+ **
+ ** "EXPLAIN QUERY PLAN SELECT 1 FROM child_table WHERE child_key=?"
+ **
+ ** This SELECT is similar to the one that the foreign keys implementation
+ ** needs to run internally on child tables. If there is an index that can
+ ** be used to optimize this query, then it can also be used by the FK
+ ** implementation to optimize DELETE or UPDATE statements on the parent
+ ** table.
+ **
+ ** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by
+ ** the EXPLAIN QUERY PLAN command matches this pattern, then the schema
+ ** contains an index that can be used to optimize the query.
+ **
+ ** 2. Human readable text that describes the child table and columns. e.g.
+ **
+ ** "child_table(child_key1, child_key2)"
+ **
+ ** 3. Human readable text that describes the parent table and columns. e.g.
+ **
+ ** "parent_table(parent_key1, parent_key2)"
+ **
+ ** 4. A full CREATE INDEX statement for an index that could be used to
+ ** optimize DELETE or UPDATE statements on the parent table. e.g.
+ **
+ ** "CREATE INDEX child_table_child_key ON child_table(child_key)"
+ **
+ ** 5. The name of the parent table.
+ **
+ ** These six values are used by the C logic below to generate the report.
+ */
+ const char *zSql =
+ "SELECT "
+ " 'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '"
+ " || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' "
+ " || fkey_collate_clause("
+ " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')"
+ ", "
+ " 'SEARCH ' || s.name || ' USING COVERING INDEX*('"
+ " || group_concat('*=?', ' AND ') || ')'"
+ ", "
+ " s.name || '(' || group_concat(f.[from], ', ') || ')'"
+ ", "
+ " f.[table] || '(' || group_concat(COALESCE(f.[to], p.[name])) || ')'"
+ ", "
+ " 'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))"
+ " || ' ON ' || quote(s.name) || '('"
+ " || group_concat(quote(f.[from]) ||"
+ " fkey_collate_clause("
+ " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')"
+ " || ');'"
+ ", "
+ " f.[table] "
+ "FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f "
+ "LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) "
+ "GROUP BY s.name, f.id "
+ "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)"
+ ;
+ const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)";
+
+ for(i=2; i<nArg; i++){
+ int n = strlen30(azArg[i]);
+ if( n>1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){
+ bVerbose = 1;
+ }
+ else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){
+ bGroupByParent = 1;
+ zIndent = " ";
}
- if( pzRenamed!=0 ){
- if( !hasDupes ) *pzRenamed = 0;
- else{
- sqlite3_finalize(pStmt);
- if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0)
- && SQLITE_ROW==sqlite3_step(pStmt) ){
- *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
- }else
- *pzRenamed = 0;
- }
+ else{
+ raw_printf(STD_ERR, "Usage: %s %s ?-verbose? ?-groupbyparent?\n",
+ azArg[0], azArg[1]
+ );
+ return DCR_Unknown|i;
}
- sqlite3_finalize(pStmt);
- sqlite3_close(*pDb);
- *pDb = 0;
- return zColsSpec;
}
-}
-/*
-** If an input line begins with "." then invoke this routine to
-** process that line.
-**
-** Return 1 on error, 2 to exit, and 0 otherwise.
-*/
-static int do_meta_command(char *zLine, ShellState *p){
- int h = 1;
- int nArg = 0;
- int n, c;
- int rc = 0;
- char *azArg[52];
+ /* Register the fkey_collate_clause() SQL function */
+ rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8,
+ 0, shellFkeyCollateClause, 0, 0
+ );
-#ifndef SQLITE_OMIT_VIRTUALTABLE
- if( p->expert.pExpert ){
- expertFinish(p, 1, 0);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pSql, 0);
}
-#endif
-
- /* Parse the input line into tokens.
- */
- while( zLine[h] && nArg<ArraySize(azArg)-1 ){
- while( IsSpace(zLine[h]) ){ h++; }
- if( zLine[h]==0 ) break;
- if( zLine[h]=='\'' || zLine[h]=='"' ){
- int delim = zLine[h++];
- azArg[nArg++] = &zLine[h];
- while( zLine[h] && zLine[h]!=delim ){
- if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++;
- h++;
- }
- if( zLine[h]==delim ){
- zLine[h++] = 0;
- }
- if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
- }else{
- azArg[nArg++] = &zLine[h];
- while( zLine[h] && !IsSpace(zLine[h]) ){ h++; }
- if( zLine[h] ) zLine[h++] = 0;
- resolve_backslashes(azArg[nArg-1]);
- }
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_int(pSql, 1, bGroupByParent);
}
- azArg[nArg] = 0;
- /* Process the input line.
- */
- if( nArg==0 ) return 0; /* no tokens, no error */
- n = strlen30(azArg[0]);
- c = azArg[0][0];
- clearTempFile(p);
+ if( rc==SQLITE_OK ){
+ int rc2;
+ char *zPrev = 0;
+ while( SQLITE_ROW==sqlite3_step(pSql) ){
+ int res = -1;
+ sqlite3_stmt *pExplain = 0;
+ const char *zEQP = (const char*)sqlite3_column_text(pSql, 0);
+ const char *zGlob = (const char*)sqlite3_column_text(pSql, 1);
+ const char *zFrom = (const char*)sqlite3_column_text(pSql, 2);
+ const char *zTarget = (const char*)sqlite3_column_text(pSql, 3);
+ const char *zCI = (const char*)sqlite3_column_text(pSql, 4);
+ const char *zParent = (const char*)sqlite3_column_text(pSql, 5);
-#ifndef SQLITE_OMIT_AUTHORIZATION
- if( c=='a' && cli_strncmp(azArg[0], "auth", n)==0 ){
- if( nArg!=2 ){
- raw_printf(stderr, "Usage: .auth ON|OFF\n");
- rc = 1;
- goto meta_command_exit;
- }
- open_db(p, 0);
- if( booleanValue(azArg[1]) ){
- sqlite3_set_authorizer(p->db, shellAuth, p);
- }else if( p->bSafeModePersist ){
- sqlite3_set_authorizer(p->db, safeModeAuth, p);
- }else{
- sqlite3_set_authorizer(p->db, 0, 0);
- }
- }else
-#endif
+ if( zEQP==0 || zGlob==0 ) continue;
+ rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
+ if( rc!=SQLITE_OK ) break;
+ if( SQLITE_ROW==sqlite3_step(pExplain) ){
+ const char *zPlan = (const char*)sqlite3_column_text(pExplain, 3);
+ res = zPlan!=0 && ( 0==sqlite3_strglob(zGlob, zPlan)
+ || 0==sqlite3_strglob(zGlobIPK, zPlan));
+ }
+ rc = sqlite3_finalize(pExplain);
+ if( rc!=SQLITE_OK ) break;
-#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) \
- && !defined(SQLITE_SHELL_FIDDLE)
- if( c=='a' && cli_strncmp(azArg[0], "archive", n)==0 ){
- open_db(p, 0);
- failIfSafeMode(p, "cannot run .archive in safe mode");
- rc = arDotCommand(p, 0, azArg, nArg);
- }else
-#endif
+ if( res<0 ){
+ raw_printf(STD_ERR, "Error: internal error");
+ break;
+ }else{
+ if( bGroupByParent
+ && (bVerbose || res==0)
+ && (zPrev==0 || sqlite3_stricmp(zParent, zPrev))
+ ){
+ raw_printf(out, "-- Parent table %s\n", zParent);
+ sqlite3_free(zPrev);
+ zPrev = smprintf("%s", zParent);
+ }
-#ifndef SQLITE_SHELL_FIDDLE
- if( (c=='b' && n>=3 && cli_strncmp(azArg[0], "backup", n)==0)
- || (c=='s' && n>=3 && cli_strncmp(azArg[0], "save", n)==0)
- ){
- const char *zDestFile = 0;
- const char *zDb = 0;
- sqlite3 *pDest;
- sqlite3_backup *pBackup;
- int j;
- int bAsync = 0;
- const char *zVfs = 0;
- failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
- for(j=1; j<nArg; j++){
- const char *z = azArg[j];
- if( z[0]=='-' ){
- if( z[1]=='-' ) z++;
- if( cli_strcmp(z, "-append")==0 ){
- zVfs = "apndvfs";
- }else
- if( cli_strcmp(z, "-async")==0 ){
- bAsync = 1;
- }else
- {
- utf8_printf(stderr, "unknown option: %s\n", azArg[j]);
- return 1;
+ if( res==0 ){
+ raw_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget);
+ }else if( bVerbose ){
+ raw_printf(out, "%s/* no extra indexes required for %s -> %s */\n",
+ zIndent, zFrom, zTarget);
}
- }else if( zDestFile==0 ){
- zDestFile = azArg[j];
- }else if( zDb==0 ){
- zDb = zDestFile;
- zDestFile = azArg[j];
- }else{
- raw_printf(stderr, "Usage: .backup ?DB? ?OPTIONS? FILENAME\n");
- return 1;
}
}
- if( zDestFile==0 ){
- raw_printf(stderr, "missing FILENAME argument on .backup\n");
- return 1;
- }
- if( zDb==0 ) zDb = "main";
- rc = sqlite3_open_v2(zDestFile, &pDest,
- SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs);
+ sqlite3_free(zPrev);
+
if( rc!=SQLITE_OK ){
- utf8_printf(stderr, "Error: cannot open \"%s\"\n", zDestFile);
- close_db(pDest);
- return 1;
+ *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
}
- if( bAsync ){
- sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
- 0, 0, 0);
- }
- open_db(p, 0);
- pBackup = sqlite3_backup_init(pDest, "main", p->db, zDb);
- if( pBackup==0 ){
- utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
- close_db(pDest);
- return 1;
- }
- while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
- sqlite3_backup_finish(pBackup);
- if( rc==SQLITE_DONE ){
- rc = 0;
- }else{
- utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
- rc = 1;
- }
- close_db(pDest);
- }else
-#endif /* !defined(SQLITE_SHELL_FIDDLE) */
- if( c=='b' && n>=3 && cli_strncmp(azArg[0], "bail", n)==0 ){
- if( nArg==2 ){
- bail_on_error = booleanValue(azArg[1]);
- }else{
- raw_printf(stderr, "Usage: .bail on|off\n");
- rc = 1;
+ rc2 = sqlite3_finalize(pSql);
+ if( rc==SQLITE_OK && rc2!=SQLITE_OK ){
+ rc = rc2;
+ *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
}
- }else
+ }else{
+ *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
+ }
- if( c=='b' && n>=3 && cli_strncmp(azArg[0], "binary", n)==0 ){
- if( nArg==2 ){
- if( booleanValue(azArg[1]) ){
- setBinaryMode(p->out, 1);
- }else{
- setTextMode(p->out, 1);
- }
- }else{
- raw_printf(stderr, "Usage: .binary on|off\n");
- rc = 1;
- }
- }else
+ return DCR_Ok|(rc!=0);
+}
- /* The undocumented ".breakpoint" command causes a call to the no-op
- ** routine named test_breakpoint().
- */
- if( c=='b' && n>=3 && cli_strncmp(azArg[0], "breakpoint", n)==0 ){
- test_breakpoint();
- }else
+DISPATCHABLE_COMMAND( load ? 2 3 ){
+ const char *zFile = 0, *zProc = 0;
+ int ai = 1, rc;
+ if( ISS(p)->bSafeMode ) return DCR_AbortError;
+ if( nArg<2 || azArg[1][0]==0 ){
+ /* Must have a non-empty FILE. (Will not load self.) */
+ return DCR_SayUsage;
+ }
+ while( ai<nArg ){
+ const char *zA = azArg[ai++];
+ if( zFile==0 ) zFile = zA;
+ else if( zProc==0 ) zProc = zA;
+ else return DCR_TooMany|ai;
+ }
+ open_db(p, 0);
+ rc = sqlite3_load_extension(DBX(p), zFile, zProc, pzErr);
+ return DCR_Ok|(rc!=SQLITE_OK);
+}
-#ifndef SQLITE_SHELL_FIDDLE
- if( c=='c' && cli_strcmp(azArg[0],"cd")==0 ){
- failIfSafeMode(p, "cannot run .cd in safe mode");
- if( nArg==2 ){
-#if defined(_WIN32) || defined(WIN32)
- wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
- rc = !SetCurrentDirectoryW(z);
- sqlite3_free(z);
+DISPATCHABLE_COMMAND( log ? 2 2 ){
+ const char *zFile = azArg[1];
+ int bOn = cli_strcmp(zFile,"on")==0;
+ int bOff = cli_strcmp(zFile,"off")==0;
+ if( ISS(p)->bSafeMode && !bOn && !bOff ){
+ return DCR_AbortError;
+ }
+ output_file_close(ISS(p)->pLog);
+ if( bOff ){
+ ISS(p)->pLog = 0;
+ return DCR_Ok;
+ }
+#if defined(SQLITE_SHELL_FIDDLE)
+ if( !bOn ) return DCR_SayUsage;
#else
- rc = chdir(azArg[1]);
+ if( bOn ) zFile = "stdout";
#endif
- if( rc ){
- utf8_printf(stderr, "Cannot change to directory \"%s\"\n", azArg[1]);
- rc = 1;
- }
- }else{
- raw_printf(stderr, "Usage: .cd DIRECTORY\n");
- rc = 1;
+ ISS(p)->pLog = output_file_open(zFile, 0);
+ return DCR_Ok;
+}
+
+static void effectMode(ShellInState *psi, u8 modeRequest, u8 modeNominal){
+ /* Effect the specified mode change. */
+ const char *zColSep = 0, *zRowSep = 0;
+ assert(modeNominal!=MODE_COUNT_OF);
+ switch( modeRequest ){
+ case MODE_Line:
+ zRowSep = SEP_Row;
+ break;
+ case MODE_Column:
+ if( (psi->shellFlgs & SHFLG_HeaderSet)==0 ){
+ psi->showHeader = 1;
}
- }else
-#endif /* !defined(SQLITE_SHELL_FIDDLE) */
+ zRowSep = SEP_Row;
+ break;
+ case MODE_List:
+ zColSep = SEP_Column;
+ zRowSep = SEP_Row;
+ break;
+ case MODE_Html:
+ break;
+ case MODE_Tcl:
+ zColSep = SEP_Space;
+ zRowSep = SEP_Row;
+ break;
+ case MODE_Csv:
+ zColSep = SEP_Comma;
+ zRowSep = SEP_CrLf;
+ break;
+ case MODE_Tab:
+ zColSep = SEP_Tab;
+ break;
+ case MODE_Insert:
+ break;
+ case MODE_Quote:
+ zColSep = SEP_Comma;
+ zRowSep = SEP_Row;
+ break;
+ case MODE_Ascii:
+ zColSep = SEP_Unit;
+ zRowSep = SEP_Record;
+ break;
+ case MODE_Markdown:
+ /* fall-thru */
+ case MODE_Table:
+ /* fall-thru */
+ case MODE_Box:
+ break;
+ case MODE_Count:
+ /* fall-thru */
+ case MODE_Off:
+ /* fall-thru */
+ case MODE_Json:
+ break;
+ case MODE_Explain: case MODE_Semi: case MODE_EQP: case MODE_Pretty: default:
+ /* Modes used internally, not settable by .mode command. */
+ return;
+ }
+ if( zRowSep!=0 ){
+ sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, zRowSep);
+ }
+ if( zColSep!=0 ){
+ sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator, zColSep);
+ }
+ psi->mode = modeNominal;
+}
- if( c=='c' && n>=3 && cli_strncmp(azArg[0], "changes", n)==0 ){
- if( nArg==2 ){
- setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
+/*****************
+ * The .mode command
+ */
+COLLECT_HELP_TEXT[
+ ".mode MODE ?OPTIONS? Set output mode",
+ " MODE is one of:",
+ " ascii Columns/rows delimited by 0x1F and 0x1E",
+ " box Tables using unicode box-drawing characters",
+ " csv Comma-separated values",
+ " column Output in columns. (See .width)",
+ " html HTML <table> code",
+ " insert SQL insert statements for TABLE",
+ " json Results in a JSON array",
+ " line One value per line",
+ " list Values delimited by \"|\"",
+ " markdown Markdown table format",
+ " qbox Shorthand for \"box --wrap 60 --quote\"",
+ " quote Escape answers as for SQL",
+ " table ASCII-art table",
+ " tabs Tab-separated values",
+ " tcl TCL list elements",
+ " OPTIONS: (for columnar modes or insert mode):",
+ " --wrap N Wrap output lines to no longer than N characters",
+ " --wordwrap B Wrap or not at word boundaries per B (on/off)",
+ " --ww Shorthand for \"--wordwrap 1\"",
+ " --quote Quote output text as SQL literals",
+ " --noquote Do not quote output text",
+ " TABLE The name of SQL table used for \"insert\" mode",
+];
+DISPATCHABLE_COMMAND( mode ? 1 0 ){
+ ShellInState *psi = ISS(p);
+ const char *zTabname = 0;
+ const char *zArg;
+ int i, aix;
+ u8 foundMode = MODE_COUNT_OF, setMode = MODE_COUNT_OF;
+ ColModeOpts cmOpts = ColModeOpts_default;
+ for(aix=1; aix<nArg; aix++){
+ zArg = azArg[aix];
+ if( optionMatch(zArg,"wrap") && aix+1<nArg ){
+ cmOpts.iWrap = integerValue(azArg[++aix]);
+ }else if( optionMatch(zArg,"ww") ){
+ cmOpts.bWordWrap = 1;
+ }else if( optionMatch(zArg,"wordwrap") && aix+1<nArg ){
+ cmOpts.bWordWrap = (u8)booleanValue(azArg[++aix]);
+ }else if( optionMatch(zArg,"quote") ){
+ cmOpts.bQuote = 1;
+ }else if( optionMatch(zArg,"noquote") ){
+ cmOpts.bQuote = 0;
}else{
- raw_printf(stderr, "Usage: .changes on|off\n");
- rc = 1;
+ /* Not a known option. Check for known mode, or possibly a table name. */
+ if( foundMode==MODE_Insert && zTabname==0 ){
+ zTabname = zArg;
+ }else if( *zArg=='-' ){
+ goto flag_unknown;
+ }else{
+ int im, nza = strlen30(zArg);
+ if( foundMode!=MODE_COUNT_OF ) goto mode_badarg;
+ for( im=0; im<MODE_COUNT_OF; ++im ){
+ if( modeDescr[im].bUserBlocked ) continue;
+ if( cli_strncmp(zArg,modeDescr[im].zModeName,nza)==0 ){
+ foundMode = (u8)im;
+ setMode = modeDescr[im].iAliasFor;
+ break;
+ }
+ }
+ if( cli_strcmp(zArg, "qbox")==0 ){
+ ColModeOpts cmo = ColModeOpts_default_qbox;
+ foundMode = setMode = MODE_Box;
+ cmOpts = cmo;
+ }else if( im==MODE_COUNT_OF ) goto mode_unknown;
+ }
+ }
+ } /* Arg loop */
+ if( foundMode==MODE_COUNT_OF ){
+ FILE *out = psi->out;
+ const char *zMode;
+ int nms;
+ i = psi->mode;
+ assert(i>=0 && i<MODE_COUNT_OF);
+ zMode = modeDescr[i].zModeName;
+ nms = strlen30(zMode)-modeDescr[i].bDepluralize;
+ /* Mode not specified. Show present mode (and toss any options set.) */
+ if( MODE_IS_COLUMNAR(psi->mode) ){
+ raw_printf
+ (out, "current output mode: %.*s --wrap %d --wordwrap %s --%squote\n",
+ nms, zMode, psi->cmOpts.iWrap,
+ psi->cmOpts.bWordWrap ? "on" : "off",
+ psi->cmOpts.bQuote ? "" : "no");
+ }else{
+ raw_printf(out, "current output mode: %.*s\n", nms, zMode);
}
- }else
+ }else{
+ effectMode(psi, foundMode, setMode);
+ if( MODE_IS_COLUMNAR(setMode) ){
+ psi->cmOpts = cmOpts;
+#if SHELL_DATAIO_EXT
+ psi->pActiveExporter = psi->pColumnarExporter;
+#endif
+ }else{
+#if SHELL_DATAIO_EXT
+ psi->pActiveExporter = psi->pFreeformExporter;
+#endif
+ if( setMode==MODE_Insert ){
+ set_table_name(p, zTabname ? zTabname : "table");
+ }
+ }
+ }
+ psi->cMode = psi->mode;
+ return DCR_Ok;
+ flag_unknown:
+ *pzErr = smprintf("Unknown .mode option: %s\nValid options:\n%s",
+ zArg,
+ " --noquote\n"
+ " --quote\n"
+ " --wordwrap on/off\n"
+ " --wrap N\n"
+ " --ww\n");
+ return DCR_Unknown|aix;
+ mode_unknown:
+ *pzErr = smprintf("Mode should be one of:\n"
+ " ascii box column csv html insert json line\n"
+ " list markdown qbox quote table tabs tcl\n");
+ return DCR_Unknown|aix;
+ mode_badarg:
+ *pzErr = smprintf("Invalid .mode argument: %s\n", zArg);
+ return DCR_ArgWrong|aix;
+}
+/* Note that .open is (partially) available in WASM builds but is
+** currently only intended to be used by the fiddle tool, not
+** end users, so is "undocumented." */
+#ifdef SQLITE_SHELL_FIDDLE
+# define HOPEN ",open"
+#else
+# define HOPEN ".open"
+#endif
+/* ToDo: Get defined help text collection into macro processor. */
+/*****************
+ * The .nonce, .nullvalue and .open commands
+ */
+CONDITION_COMMAND(nonce !defined(SQLITE_SHELL_FIDDLE));
+COLLECT_HELP_TEXT[
+ ".open ?OPTIONS? ?FILE? Close existing database and reopen FILE",
+ " Options:",
+ " --append Use appendvfs to append database to the end of FILE",
+#ifndef SQLITE_OMIT_DESERIALIZE
+ " --deserialize Load into memory using sqlite3_deserialize()",
+ " --hexdb Load the output of \"dbtotxt\" as an in-memory db",
+ " --maxsize N Maximum size for --hexdb or --deserialized database",
+#endif
+ " --new Initialize FILE to an empty database",
+ " --nofollow Do not follow symbolic links",
+ " --readonly Open FILE readonly",
+ " --zip FILE is a ZIP archive",
+ ".nonce STRING Suspend safe mode for one command if nonce matches",
+ ".nullvalue STRING Use STRING in place of NULL values",
+];
+DISPATCHABLE_COMMAND( open 3 1 0 ){
+ ShellInState *psi = ISS(p);
+ const char *zFN = 0; /* Pointer to constant filename */
+ char *zNewFilename = 0; /* Name of the database file to open */
+ int iName = 1; /* Index in azArg[] of the filename */
+ int newFlag = 0; /* True to delete file before opening */
+ u8 openMode = SHELL_OPEN_UNSPEC;
+ int openFlags = 0;
+ sqlite3_int64 szMax = 0;
+ int rc = 0;
+ /* Check for command-line arguments */
+ for(iName=1; iName<nArg; iName++){
+ const char *z = azArg[iName];
#ifndef SQLITE_SHELL_FIDDLE
- /* Cancel output redirection, if it is currently set (by .testcase)
- ** Then read the content of the testcase-out.txt file and compare against
- ** azArg[1]. If there are differences, report an error and exit.
- */
- if( c=='c' && n>=3 && cli_strncmp(azArg[0], "check", n)==0 ){
- char *zRes = 0;
- output_reset(p);
- if( nArg!=2 ){
- raw_printf(stderr, "Usage: .check GLOB-PATTERN\n");
- rc = 2;
- }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){
- rc = 2;
- }else if( testcase_glob(azArg[1],zRes)==0 ){
- utf8_printf(stderr,
- "testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n",
- p->zTestcase, azArg[1], zRes);
- rc = 1;
+ if( optionMatch(z,"new") ){
+ newFlag = 1;
+# ifdef SQLITE_HAVE_ZLIB
+ }else if( optionMatch(z, "zip") ){
+ openMode = SHELL_OPEN_ZIPFILE;
+# endif
+ }else if( optionMatch(z, "append") ){
+ openMode = SHELL_OPEN_APPENDVFS;
+ }else if( optionMatch(z, "readonly") ){
+ openMode = SHELL_OPEN_READONLY;
+ }else if( optionMatch(z, "nofollow") ){
+ openFlags |= SQLITE_OPEN_NOFOLLOW;
+# ifndef SQLITE_OMIT_DESERIALIZE
+ }else if( optionMatch(z, "deserialize") ){
+ openMode = SHELL_OPEN_DESERIALIZE;
+ }else if( optionMatch(z, "hexdb") ){
+ openMode = SHELL_OPEN_HEXDB;
+ }else if( optionMatch(z, "maxsize") && iName+1<nArg ){
+ szMax = integerValue(azArg[++iName]);
+# endif /* SQLITE_OMIT_DESERIALIZE */
+ }else
+#endif /* !defined(SQLITE_SHELL_FIDDLE) */
+ if( z[0]=='-' ){
+ return DCR_Unknown|iName;
+ }else if( zFN ){
+ *pzErr = smprintf("extra argument: \"%s\"\n", z);
+ return DCR_TooMany;
}else{
- utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase);
- p->nCheck++;
+ zFN = z;
}
- sqlite3_free(zRes);
- }else
-#endif /* !defined(SQLITE_SHELL_FIDDLE) */
+ }
+ /* Close the existing database */
+ session_close_all(psi, -1);
+#if SHELL_DYNAMIC_EXTENSION
+ if( DBX(p)!=0 ) notify_subscribers(psi, NK_DbAboutToClose, DBX(p));
+#endif
+ close_db(DBX(p));
+ DBX(p) = 0;
+ psi->pAuxDb->zDbFilename = 0;
+ sqlite3_free(psi->pAuxDb->zFreeOnClose);
+ psi->pAuxDb->zFreeOnClose = 0;
+ psi->openMode = openMode;
+ psi->openFlags = 0;
+ psi->szMax = 0;
+
+ /* If a filename is specified, try to open it first */
+ if( zFN || psi->openMode==SHELL_OPEN_HEXDB ){
+ if( newFlag && zFN && !psi->bSafeMode ) shellDeleteFile(zFN);
#ifndef SQLITE_SHELL_FIDDLE
- if( c=='c' && cli_strncmp(azArg[0], "clone", n)==0 ){
- failIfSafeMode(p, "cannot run .clone in safe mode");
- if( nArg==2 ){
- tryToClone(p, azArg[1]);
- }else{
- raw_printf(stderr, "Usage: .clone FILENAME\n");
- rc = 1;
+ if( psi->bSafeMode
+ && psi->openMode!=SHELL_OPEN_HEXDB
+ && zFN
+ && cli_strcmp(zFN,":memory:")!=0
+ ){
+ *pzErr = smprintf("cannot open database files in safe mode");
+ return DCR_AbortError;
}
- }else
-#endif /* !defined(SQLITE_SHELL_FIDDLE) */
-
- if( c=='c' && cli_strncmp(azArg[0], "connection", n)==0 ){
- if( nArg==1 ){
- /* List available connections */
- int i;
- for(i=0; i<ArraySize(p->aAuxDb); i++){
- const char *zFile = p->aAuxDb[i].zDbFilename;
- if( p->aAuxDb[i].db==0 && p->pAuxDb!=&p->aAuxDb[i] ){
- zFile = "(not open)";
- }else if( zFile==0 ){
- zFile = "(memory)";
- }else if( zFile[0]==0 ){
- zFile = "(temporary-file)";
- }
- if( p->pAuxDb == &p->aAuxDb[i] ){
- utf8_printf(stdout, "ACTIVE %d: %s\n", i, zFile);
- }else if( p->aAuxDb[i].db!=0 ){
- utf8_printf(stdout, " %d: %s\n", i, zFile);
- }
- }
- }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){
- int i = azArg[1][0] - '0';
- if( p->pAuxDb != &p->aAuxDb[i] && i>=0 && i<ArraySize(p->aAuxDb) ){
- p->pAuxDb->db = p->db;
- p->pAuxDb = &p->aAuxDb[i];
- globalDb = p->db = p->pAuxDb->db;
- p->pAuxDb->db = 0;
- }
- }else if( nArg==3 && cli_strcmp(azArg[1], "close")==0
- && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){
- int i = azArg[2][0] - '0';
- if( i<0 || i>=ArraySize(p->aAuxDb) ){
- /* No-op */
- }else if( p->pAuxDb == &p->aAuxDb[i] ){
- raw_printf(stderr, "cannot close the active database connection\n");
- rc = 1;
- }else if( p->aAuxDb[i].db ){
- session_close_all(p, i);
- close_db(p->aAuxDb[i].db);
- p->aAuxDb[i].db = 0;
- }
+#else
+ /* WASM mode has its own sandboxed pseudo-filesystem. */
+#endif
+ if( zFN ){
+ zNewFilename = smprintf("%s", zFN);
+ shell_check_oom(zNewFilename);
}else{
- raw_printf(stderr, "Usage: .connection [close] [CONNECTION-NUMBER]\n");
- rc = 1;
- }
- }else
-
- if( c=='d' && n>1 && cli_strncmp(azArg[0], "databases", n)==0 ){
- char **azName = 0;
- int nName = 0;
- sqlite3_stmt *pStmt;
- int i;
- open_db(p, 0);
- rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
- if( rc ){
- utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
+ zNewFilename = 0;
+ }
+ psi->pAuxDb->zDbFilename = zNewFilename;
+ psi->openFlags = openFlags;
+ psi->szMax = szMax;
+ open_db(p, OPEN_DB_KEEPALIVE);
+ if( DBX(p)==0 ){
+ *pzErr = smprintf("cannot open '%s'\n", zNewFilename);
+ sqlite3_free(zNewFilename);
rc = 1;
}else{
- while( sqlite3_step(pStmt)==SQLITE_ROW ){
- const char *zSchema = (const char *)sqlite3_column_text(pStmt,1);
- const char *zFile = (const char*)sqlite3_column_text(pStmt,2);
- if( zSchema==0 || zFile==0 ) continue;
- azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*));
- shell_check_oom(azName);
- azName[nName*2] = strdup(zSchema);
- azName[nName*2+1] = strdup(zFile);
- nName++;
- }
+ psi->pAuxDb->zFreeOnClose = zNewFilename;
}
- sqlite3_finalize(pStmt);
- for(i=0; i<nName; i++){
- int eTxn = sqlite3_txn_state(p->db, azName[i*2]);
- int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]);
- const char *z = azName[i*2+1];
- utf8_printf(p->out, "%s: %s %s%s\n",
- azName[i*2],
- z && z[0] ? z : "\"\"",
- bRdonly ? "r/o" : "r/w",
- eTxn==SQLITE_TXN_NONE ? "" :
- eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
- free(azName[i*2]);
- free(azName[i*2+1]);
- }
- sqlite3_free(azName);
- }else
-
- if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbconfig", n)==0 ){
- static const struct DbConfigChoices {
- const char *zName;
- int op;
- } aDbConfig[] = {
- { "defensive", SQLITE_DBCONFIG_DEFENSIVE },
- { "dqs_ddl", SQLITE_DBCONFIG_DQS_DDL },
- { "dqs_dml", SQLITE_DBCONFIG_DQS_DML },
- { "enable_fkey", SQLITE_DBCONFIG_ENABLE_FKEY },
- { "enable_qpsg", SQLITE_DBCONFIG_ENABLE_QPSG },
- { "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER },
- { "enable_view", SQLITE_DBCONFIG_ENABLE_VIEW },
- { "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER },
- { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE },
- { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT },
- { "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION },
- { "no_ckpt_on_close", SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE },
- { "reset_database", SQLITE_DBCONFIG_RESET_DATABASE },
- { "reverse_scanorder", SQLITE_DBCONFIG_REVERSE_SCANORDER },
- { "stmt_scanstatus", SQLITE_DBCONFIG_STMT_SCANSTATUS },
- { "trigger_eqp", SQLITE_DBCONFIG_TRIGGER_EQP },
- { "trusted_schema", SQLITE_DBCONFIG_TRUSTED_SCHEMA },
- { "writable_schema", SQLITE_DBCONFIG_WRITABLE_SCHEMA },
- };
- int ii, v;
+ }
+ if( DBX(p)==0 ){
+ /* As a fall-back open a TEMP database */
+ psi->pAuxDb->zDbFilename = 0;
open_db(p, 0);
- for(ii=0; ii<ArraySize(aDbConfig); ii++){
- if( nArg>1 && cli_strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue;
- if( nArg>=3 ){
- sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0);
- }
- sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v);
- utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off");
- if( nArg>1 ) break;
- }
- if( nArg>1 && ii==ArraySize(aDbConfig) ){
- utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]);
- utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n");
- }
- }else
+ }
+ return DCR_Ok|(rc!=0);
+}
-#if SQLITE_SHELL_HAVE_RECOVER
- if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbinfo", n)==0 ){
- rc = shell_dbinfo_command(p, nArg, azArg);
- }else
+DISPATCHABLE_COMMAND( nonce ? 2 2 ){
+ ShellInState *psi = ISS(p);
+ if( psi->zNonce==0 || cli_strcmp(azArg[1],psi->zNonce)!=0 ){
+ raw_printf(STD_ERR, "line %d: incorrect nonce: \"%s\"\n",
+ psi->pInSource->lineno, azArg[1]);
+ exit(1);
+ }
+ /* Suspend safe mode for 1 dot-command after this. */
+ psi->bSafeModeFuture = 2;
+ return DCR_Ok;
+}
- if( c=='r' && cli_strncmp(azArg[0], "recover", n)==0 ){
- open_db(p, 0);
- rc = recoverDatabaseCmd(p, nArg, azArg);
- }else
-#endif /* SQLITE_SHELL_HAVE_RECOVER */
+DISPATCHABLE_COMMAND( nullvalue ? 2 2 ){
+ sqlite3_snprintf(sizeof(ISS(p)->nullValue), ISS(p)->nullValue, "%.*s",
+ (int)ArraySize(ISS(p)->nullValue)-1, azArg[1]);
+ return DCR_Ok;
+}
- if( c=='d' && cli_strncmp(azArg[0], "dump", n)==0 ){
- char *zLike = 0;
- char *zSql;
- int i;
- int savedShowHeader = p->showHeader;
- int savedShellFlags = p->shellFlgs;
- ShellClearFlag(p,
- SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo
- |SHFLG_DumpDataOnly|SHFLG_DumpNoSys);
- for(i=1; i<nArg; i++){
- if( azArg[i][0]=='-' ){
- const char *z = azArg[i]+1;
- if( z[0]=='-' ) z++;
- if( cli_strcmp(z,"preserve-rowids")==0 ){
-#ifdef SQLITE_OMIT_VIRTUALTABLE
- raw_printf(stderr, "The --preserve-rowids option is not compatible"
- " with SQLITE_OMIT_VIRTUALTABLE\n");
- rc = 1;
- sqlite3_free(zLike);
- goto meta_command_exit;
-#else
- ShellSetFlag(p, SHFLG_PreserveRowid);
+/* Helper functions for .parameter and .vars commands
+ */
+
+struct keyval_row { char * value; int uses; int hits; };
+
+static int kv_find_callback(void *pData, int nc, char **pV, char **pC){
+ assert(nc>=1);
+ assert(cli_strcmp(pC[0],"value")==0);
+ struct keyval_row *pParam = (struct keyval_row *)pData;
+ assert(pParam->value==0); /* key values are supposedly unique. */
+ if( pParam->value!=0 ) sqlite3_free( pParam->value );
+ pParam->value = smprintf("%s", pV[0]); /* source owned by statement */
+ if( nc>1 ) pParam->uses = (int)integerValue(pV[1]);
+ ++pParam->hits;
+ return 0;
+}
+
+static void append_in_clause(sqlite3_str *pStr,
+ const char **azBeg, const char **azLim);
+static void append_glob_terms(sqlite3_str *pStr, const char *zColName,
+ const char **azBeg, const char **azLim);
+static char *find_home_dir(int clearFlag);
+
+/* Create a home-relative pathname from ~ prefixed path.
+ * Return it, or 0 for any error.
+ * Caller must sqlite3_free() it.
+ */
+static char *home_based_path( const char *zPath ){
+ char *zHome = find_home_dir(0);
+ char *zErr = 0;
+ assert( zPath[0]=='~' );
+ if( zHome==0 ){
+ zErr = "Cannot find home directory.";
+ }else if( zPath[0]==0 || (zPath[1]!='/'
+#if defined(_WIN32) || defined(WIN32)
+ && zPath[1]!='\\'
#endif
- }else
- if( cli_strcmp(z,"newlines")==0 ){
- ShellSetFlag(p, SHFLG_Newlines);
- }else
- if( cli_strcmp(z,"data-only")==0 ){
- ShellSetFlag(p, SHFLG_DumpDataOnly);
- }else
- if( cli_strcmp(z,"nosys")==0 ){
- ShellSetFlag(p, SHFLG_DumpNoSys);
- }else
- {
- raw_printf(stderr, "Unknown option \"%s\" on \".dump\"\n", azArg[i]);
- rc = 1;
- sqlite3_free(zLike);
- goto meta_command_exit;
- }
- }else{
- /* azArg[i] contains a LIKE pattern. This ".dump" request should
- ** only dump data for tables for which either the table name matches
- ** the LIKE pattern, or the table appears to be a shadow table of
- ** a virtual table for which the name matches the LIKE pattern.
- */
- char *zExpr = sqlite3_mprintf(
- "name LIKE %Q ESCAPE '\\' OR EXISTS ("
- " SELECT 1 FROM sqlite_schema WHERE "
- " name LIKE %Q ESCAPE '\\' AND"
- " sql LIKE 'CREATE VIRTUAL TABLE%%' AND"
- " substr(o.name, 1, length(name)+1) == (name||'_')"
- ")", azArg[i], azArg[i]
- );
+ ) ){
+ zErr = "Malformed pathname";
+ }else{
+ return smprintf("%s%s", zHome, zPath+1);
+ }
+ utf8_printf(STD_ERR, "Error: %s\n", zErr);
+ return 0;
+}
- if( zLike ){
- zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr);
- }else{
- zLike = zExpr;
- }
+/* Transfer selected parameters between two parameter tables, for save/load.
+ * Argument bSaveNotLoad determines transfer direction and other actions.
+ * If it is true, the store DB will be created if not existent, and its
+ * table for keeping parameters will be created. Or failure is returned.
+ * If it is false, the store DB will be opened for read and its presumed
+ * table for keeping parameters will be read. Or failure is returned.
+ *
+ * Arguments azNames and nNames reference the ?NAMES? save/load arguments.
+ * If it is an empty list, all parameters will be saved or loaded.
+ * Otherwise, only the named parameters are transferred, if they exist.
+ * It is not an error to specify a name that cannot be transferred
+ * because it does not exist in the source table.
+ *
+ * Returns are SQLITE_OK for success, or other codes for failure.
+ */
+static int kv_xfr_table(sqlite3 *db, const char *zStoreDbName,
+ int bSaveNotLoad, ParamTableUse ptu,
+ const char *azNames[], int nNames){
+ int rc = 0;
+ char *zSql = 0; /* to be sqlite3_free()'ed */
+ sqlite3_str *sbCopy = 0;
+ const char *zHere = 0;
+ const char *zThere = SH_KV_STORE_SNAME;
+ const char *zTo;
+ const char *zFrom;
+ sqlite3 *dbStore = 0;
+ int openFlags = (bSaveNotLoad)
+ ? SQLITE_OPEN_URI|SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE
+ : SQLITE_OPEN_READONLY;
+
+ switch( ptu ){
+ case PTU_Binding: zHere = PARAM_TABLE_SNAME; break;
+ case PTU_Script: zHere = SHVAR_TABLE_SNAME; break;
+ default: assert(0); return 1;
+ }
+ zTo = (bSaveNotLoad)? zThere : zHere;
+ zFrom = (bSaveNotLoad)? zHere : zThere;
+ /* Ensure store DB can be opened and/or created appropriately. */
+ rc = sqlite3_open_v2(zStoreDbName, &dbStore, openFlags, 0);
+ if( rc!=SQLITE_OK ){
+ utf8_printf(STD_ERR, "Error: Cannot %s key/value store DB %s\n",
+ bSaveNotLoad? "open/create" : "read", zStoreDbName);
+ return rc;
+ }
+ /* Ensure it has the kv store table, or handle its absence. */
+ assert(dbStore!=0);
+ if( sqlite3_table_column_metadata
+ (dbStore, "main", SH_KV_STORE_NAME, 0, 0, 0, 0, 0, 0)!=SQLITE_OK ){
+ if( !bSaveNotLoad ){
+ utf8_printf(STD_ERR, "Error: No key/value pairs ever stored in DB %s\n",
+ zStoreDbName);
+ rc = 1;
+ }else{
+ /* The saved parameters table is not there yet; create it. */
+ const char *zCT =
+ "CREATE TABLE IF NOT EXISTS "SH_KV_STORE_NAME"(\n"
+ " key TEXT PRIMARY KEY,\n"
+ " value,\n"
+ " uses INT\n"
+ ") WITHOUT ROWID;";
+ rc = sqlite3_exec(dbStore, zCT, 0, 0, 0);
+ if( rc!=SQLITE_OK ){
+ utf8_printf(STD_ERR, "Cannot create table %s. Nothing saved.", zThere);
}
}
+ }
+ sqlite3_close(dbStore);
+ if( rc!=0 ) return rc;
- open_db(p, 0);
+ zSql = smprintf("ATTACH %Q AS %s;", zStoreDbName, SH_KV_STORE_SCHEMA);
+ shell_check_oom(zSql);
+ rc = sqlite3_exec(db, zSql, 0, 0, 0);
+ sqlite3_free(zSql);
+ if( rc!=SQLITE_OK ) return rc;
- if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
- /* When playing back a "dump", the content might appear in an order
- ** which causes immediate foreign key constraints to be violated.
- ** So disable foreign-key constraint enforcement to prevent problems. */
- raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n");
- raw_printf(p->out, "BEGIN TRANSACTION;\n");
- }
- p->writableSchema = 0;
- p->showHeader = 0;
- /* Set writable_schema=ON since doing so forces SQLite to initialize
- ** as much of the schema as it can even if the sqlite_schema table is
- ** corrupt. */
- sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
- p->nErr = 0;
- if( zLike==0 ) zLike = sqlite3_mprintf("true");
- zSql = sqlite3_mprintf(
- "SELECT name, type, sql FROM sqlite_schema AS o "
- "WHERE (%s) AND type=='table'"
- " AND sql NOT NULL"
- " ORDER BY tbl_name='sqlite_sequence', rowid",
- zLike
- );
- run_schema_dump_query(p,zSql);
- sqlite3_free(zSql);
- if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
- zSql = sqlite3_mprintf(
- "SELECT sql FROM sqlite_schema AS o "
- "WHERE (%s) AND sql NOT NULL"
- " AND type IN ('index','trigger','view')",
- zLike
- );
- run_table_dump_query(p, zSql);
- sqlite3_free(zSql);
- }
- sqlite3_free(zLike);
- if( p->writableSchema ){
- raw_printf(p->out, "PRAGMA writable_schema=OFF;\n");
- p->writableSchema = 0;
- }
- sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0);
- sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0);
- if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
- raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n");
- }
- p->showHeader = savedShowHeader;
- p->shellFlgs = savedShellFlags;
- }else
+ sbCopy = sqlite3_str_new(db);
+ sqlite3_str_appendf
+ (sbCopy, "INSERT OR REPLACE INTO %s(key,value,uses)"
+ "SELECT key, value, uses FROM %s WHERE key ", zTo, zFrom);
+ append_in_clause(sbCopy, azNames, azNames+nNames);
+ zSql = sqlite3_str_finish(sbCopy);
+ shell_check_oom(zSql);
+ rc = sqlite3_exec(db, zSql, 0, 0, 0);
+ sqlite3_free(zSql);
- if( c=='e' && cli_strncmp(azArg[0], "echo", n)==0 ){
- if( nArg==2 ){
- setOrClearFlag(p, SHFLG_Echo, azArg[1]);
- }else{
- raw_printf(stderr, "Usage: .echo on|off\n");
- rc = 1;
- }
- }else
+ sqlite3_exec(db, "DETACH "SH_KV_STORE_SCHEMA";", 0, 0, 0);
+ return rc;
+}
- if( c=='e' && cli_strncmp(azArg[0], "eqp", n)==0 ){
- if( nArg==2 ){
- p->autoEQPtest = 0;
- if( p->autoEQPtrace ){
- if( p->db ) sqlite3_exec(p->db, "PRAGMA vdbe_trace=OFF;", 0, 0, 0);
- p->autoEQPtrace = 0;
- }
- if( cli_strcmp(azArg[1],"full")==0 ){
- p->autoEQP = AUTOEQP_full;
- }else if( cli_strcmp(azArg[1],"trigger")==0 ){
- p->autoEQP = AUTOEQP_trigger;
-#ifdef SQLITE_DEBUG
- }else if( cli_strcmp(azArg[1],"test")==0 ){
- p->autoEQP = AUTOEQP_on;
- p->autoEQPtest = 1;
- }else if( cli_strcmp(azArg[1],"trace")==0 ){
- p->autoEQP = AUTOEQP_full;
- p->autoEQPtrace = 1;
- open_db(p, 0);
- sqlite3_exec(p->db, "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0);
- sqlite3_exec(p->db, "PRAGMA vdbe_trace=ON;", 0, 0, 0);
-#endif
- }else{
- p->autoEQP = (u8)booleanValue(azArg[1]);
- }
- }else{
- raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n");
- rc = 1;
- }
- }else
+/* Default locations of kv store DBs for .parameters and .vars save/load. */
+static const char *zDefaultParamStore = "~/sqlite_params.sdb";
+static const char *zDefaultVarStore = "~/sqlite_vars.sdb";
-#ifndef SQLITE_SHELL_FIDDLE
- if( c=='e' && cli_strncmp(azArg[0], "exit", n)==0 ){
- if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc);
- rc = 2;
- }else
-#endif
+/* Possibly generate a derived path from input spec, with defaulting
+ * and conversion of leading (or only) tilde as home directory.
+ * The above-set default is used for zSpec NULL, "" or "~".
+ * When return is 0, there is an error; what needs doing cannnot be done.
+ * If the return is exactly the input, it must not be sqlite3_free()'ed.
+ * If the return differs from the input, it must be sqlite3_free()'ed.
+ */
+ static const char *kv_store_path(const char *zSpec, ParamTableUse ptu){
+ if( zSpec==0 || zSpec[0]==0 || cli_strcmp(zSpec,"~")==0 ){
+ const char *zDef;
+ switch( ptu ){
+ case PTU_Binding: zDef = zDefaultParamStore; break;
+ case PTU_Script: zDef = zDefaultVarStore; break;
+ default: return 0;
+ }
+ return home_based_path(zDef);
+ }else if ( zSpec[0]=='~' ){
+ return home_based_path(zSpec);
+ }
+ return zSpec;
+}
- /* The ".explain" command is automatic now. It is largely pointless. It
- ** retained purely for backwards compatibility */
- if( c=='e' && cli_strncmp(azArg[0], "explain", n)==0 ){
- int val = 1;
- if( nArg>=2 ){
- if( cli_strcmp(azArg[1],"auto")==0 ){
- val = 99;
- }else{
- val = booleanValue(azArg[1]);
- }
- }
- if( val==1 && p->mode!=MODE_Explain ){
- p->normalMode = p->mode;
- p->mode = MODE_Explain;
- p->autoExplain = 0;
- }else if( val==0 ){
- if( p->mode==MODE_Explain ) p->mode = p->normalMode;
- p->autoExplain = 0;
- }else if( val==99 ){
- if( p->mode==MODE_Explain ) p->mode = p->normalMode;
- p->autoExplain = 1;
- }
- }else
+/* Load some or all kv pairs. Arguments are "load FILE ?NAMES?". */
+static int kv_pairs_load(sqlite3 *db, ParamTableUse ptu,
+ const char *azArg[], int nArg){
+ const char *zStore = kv_store_path((nArg>1)? azArg[1] : 0, ptu);
+ if( zStore==0 ){
+ utf8_printf(STD_ERR, "Cannot form parameter load path. Nothing loaded.\n");
+ return DCR_Error;
+ }else{
+ const char **pzFirst = (nArg>2)? azArg+2 : 0;
+ int nNames = (nArg>2)? nArg-2 : 0;
+ int rc = kv_xfr_table(db, zStore, 0, ptu, pzFirst, nNames);
+ if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore);
+ return rc;
+ }
+}
-#ifndef SQLITE_OMIT_VIRTUALTABLE
- if( c=='e' && cli_strncmp(azArg[0], "expert", n)==0 ){
- if( p->bSafeMode ){
- raw_printf(stderr,
- "Cannot run experimental commands such as \"%s\" in safe mode\n",
- azArg[0]);
- rc = 1;
+/* Save some or all parameters. Arguments are "save FILE ?NAMES?". */
+static int kv_pairs_save(sqlite3 *db, ParamTableUse ptu,
+ const char *azArg[], int nArg){
+ const char *zStore = kv_store_path((nArg>1)? azArg[1] : 0, ptu);
+ if( zStore==0 ){
+ utf8_printf(STD_ERR, "Cannot form parameter save path. Nothing saved.\n");
+ return DCR_Error;
+ }else{
+ const char **pzFirst = (nArg>2)? azArg+2 : 0;
+ int nNames = (nArg>2)? nArg-2 : 0;
+ int rc = kv_xfr_table(db, zStore, 1, ptu, pzFirst, nNames);
+ if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore);
+ return rc;
+ }
+}
+
+#ifndef SQLITE_NOHAVE_SYSTEM
+/*
+ * Edit one named value in the parameters or shell variables table.
+ * If it does not yet exist, create it. If eval is true, the value
+ * is treated as a bare expression, otherwise it is a text value.
+ * The "uses" argument sets the 3rd column in the selected table,
+ * and serves to select which of the two tables is modified.
+ */
+static int edit_one_kvalue(sqlite3 *db, char *name, int eval,
+ ParamTableUse uses, const char * zEditor){
+ struct keyval_row kvRow = {0,0,0};
+ int rc;
+ char * zVal = 0;
+ const char *zTab = 0;
+ char * zSql = 0;
+
+ switch( uses ){
+ case PTU_Script: zTab = SHVAR_TABLE_SNAME; break;
+ case PTU_Binding: zTab = PARAM_TABLE_SNAME; break;
+ default: assert(0);
+ }
+ zSql = smprintf("SELECT value, uses FROM %s "
+ "WHERE key=%Q AND uses=%d", zTab, name, uses);
+ shell_check_oom(zSql);
+ sqlite3_exec(db, zSql, kv_find_callback, &kvRow, 0);
+ sqlite3_free(zSql);
+ assert(kvRow.hits<2);
+ if( kvRow.hits==1 && kvRow.uses==uses){
+ /* Editing an existing value of same kind. */
+ sqlite3_free(kvRow.value);
+ if( eval!=0 ){
+ zSql = smprintf("SELECT edit(value, %Q) FROM %s "
+ "WHERE key=%Q AND uses=%d", zEditor, zTab, name, uses);
+ shell_check_oom(zSql);
+ zVal = db_text(db, zSql, 1);
+ sqlite3_free(zSql);
+ zSql = smprintf("UPDATE %s SET value=(SELECT %s) "
+ "WHERE key=%Q AND uses=%d", zTab, zVal, name, uses);
}else{
- open_db(p, 0);
- expertDotCommand(p, azArg, nArg);
+ zSql = smprintf("UPDATE %s SET value=edit(value, %Q) WHERE"
+ " key=%Q AND uses=%d", zTab, zEditor, name, uses);
}
- }else
+ }else{
+ /* Editing a new value of same kind. */
+ assert(kvRow.value==0 || kvRow.uses!=uses);
+ if( eval!=0 ){
+ zSql = smprintf("SELECT edit('-- %q%s', %Q)", name, "\n", zEditor);
+ zVal = db_text(db, zSql, 1);
+ sqlite3_free(zSql);
+ zSql = smprintf("INSERT INTO %s(key,value,uses)"
+ " VALUES (%Q,(SELECT %s LIMIT 1),%d)",
+ zTab, name, zVal, uses);
+ }else{
+ zSql = smprintf("INSERT INTO %s(key,value,uses)"
+ " VALUES (%Q,edit('-- %q;%s', %Q),%d)",
+ zTab, name, name, "\n", zEditor, uses);
+ }
+ }
+ shell_check_oom(zSql);
+ rc = sqlite3_exec(db, zSql, 0, 0, 0);
+ sqlite3_free(zSql);
+ sqlite3_free(zVal);
+ return rc!=SQLITE_OK;
+}
#endif
- if( c=='f' && cli_strncmp(azArg[0], "filectrl", n)==0 ){
- static const struct {
- const char *zCtrlName; /* Name of a test-control option */
- int ctrlCode; /* Integer code for that option */
- const char *zUsage; /* Usage notes */
- } aCtrl[] = {
- { "chunk_size", SQLITE_FCNTL_CHUNK_SIZE, "SIZE" },
- { "data_version", SQLITE_FCNTL_DATA_VERSION, "" },
- { "has_moved", SQLITE_FCNTL_HAS_MOVED, "" },
- { "lock_timeout", SQLITE_FCNTL_LOCK_TIMEOUT, "MILLISEC" },
- { "persist_wal", SQLITE_FCNTL_PERSIST_WAL, "[BOOLEAN]" },
- /* { "pragma", SQLITE_FCNTL_PRAGMA, "NAME ARG" },*/
- { "psow", SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]" },
- { "reserve_bytes", SQLITE_FCNTL_RESERVE_BYTES, "[N]" },
- { "size_limit", SQLITE_FCNTL_SIZE_LIMIT, "[LIMIT]" },
- { "tempfilename", SQLITE_FCNTL_TEMPFILENAME, "" },
- /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY, "COUNT DELAY" },*/
- };
- int filectrl = -1;
- int iCtrl = -1;
- sqlite3_int64 iRes = 0; /* Integer result to display if rc2==1 */
- int isOk = 0; /* 0: usage 1: %lld 2: no-result */
- int n2, i;
- const char *zCmd = 0;
- const char *zSchema = 0;
+/* Space-join values in an argument list. *valLim is not included. */
+char *values_join( char **valBeg, char **valLim ){
+ char *z = 0;
+ const char *zSep = 0;
+ while( valBeg < valLim ){
+ z = smprintf("%z%s%s", z, zSep, *valBeg);
+ zSep = " ";
+ ++valBeg;
+ }
+ return z;
+}
- open_db(p, 0);
- zCmd = nArg>=2 ? azArg[1] : "help";
+static struct ParamSetOpts {
+ const char cCast;
+ const char *zTypename;
+ int evalKind;
+} param_set_opts[] = {
+ /* { 'q', 0, 2 }, */
+ /* { 'x', 0, 1 }, */
+ { 'i', "INT", 1 },
+ { 'r', "REAL", 1 },
+ { 'b', "BLOB", 1 },
+ { 't', "TEXT", 0 },
+ { 'n', "NUMERIC", 1 }
+};
- if( zCmd[0]=='-'
- && (cli_strcmp(zCmd,"--schema")==0 || cli_strcmp(zCmd,"-schema")==0)
- && nArg>=4
- ){
- zSchema = azArg[2];
- for(i=3; i<nArg; i++) azArg[i-2] = azArg[i];
- nArg -= 2;
- zCmd = azArg[1];
- }
+/* Return an option character if it is single and prefixed by - or --,
+ * else return 0.
+ */
+static char option_char(char *zArg){
+ if( zArg[0]=='-' ){
+ ++zArg;
+ if( zArg[0]=='-' ) ++zArg;
+ if( zArg[0]!=0 && zArg[1]==0 ) return zArg[0];
+ }
+ return 0;
+}
- /* The argument can optionally begin with "-" or "--" */
- if( zCmd[0]=='-' && zCmd[1] ){
- zCmd++;
- if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
- }
+/* Most of .vars set
+ * Return SQLITE_OK on success, else SQLITE_ERROR.
+ */
+static int shvar_set(sqlite3 *db, char *name, char **valBeg, char **valLim){
+ int rc = SQLITE_OK;
+ char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0;
+ sqlite3_stmt *pStmtSet = 0;
+ char *zValue = (zValGlom==0)? *valBeg : zValGlom;
+ char *zSql
+ = smprintf("REPLACE INTO "SHVAR_TABLE_SNAME"(key,value,uses)"
+ "VALUES(%Q,%Q,"SPTU_Script");", name, zValue);
+ shell_check_oom(zSql);
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0);
+ assert(rc==SQLITE_OK);
+ sqlite3_free(zSql);
+ rc = (SQLITE_DONE==sqlite3_step(pStmtSet))? SQLITE_OK : SQLITE_ERROR;
+ sqlite3_finalize(pStmtSet);
+ sqlite3_free(zValGlom);
+ return rc;
+}
- /* --help lists all file-controls */
- if( cli_strcmp(zCmd,"help")==0 ){
- utf8_printf(p->out, "Available file-controls:\n");
- for(i=0; i<ArraySize(aCtrl); i++){
- utf8_printf(p->out, " .filectrl %s %s\n",
- aCtrl[i].zCtrlName, aCtrl[i].zUsage);
- }
- rc = 1;
- goto meta_command_exit;
- }
- /* convert filectrl text option to value. allow any unique prefix
- ** of the option name, or a numerical value. */
- n2 = strlen30(zCmd);
- for(i=0; i<ArraySize(aCtrl); i++){
- if( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
- if( filectrl<0 ){
- filectrl = aCtrl[i].ctrlCode;
- iCtrl = i;
- }else{
- utf8_printf(stderr, "Error: ambiguous file-control: \"%s\"\n"
- "Use \".filectrl --help\" for help\n", zCmd);
- rc = 1;
- goto meta_command_exit;
- }
+/* Most of the .parameter set subcommand (per help text)
+ */
+static int param_set(sqlite3 *db, char cCast,
+ char *name, char **valBeg, char **valLim){
+ char *zSql = 0;
+ int rc = SQLITE_OK, retries = 0, needsEval = 1;
+ char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0;
+ sqlite3_stmt *pStmtSet = 0;
+ const char *zCastTo = 0;
+ char *zValue = (zValGlom==0)? *valBeg : zValGlom;
+ if( cCast ){
+ struct ParamSetOpts *pSO = param_set_opts;
+ for(; pSO-param_set_opts < ArraySize(param_set_opts); ++pSO ){
+ if( cCast==pSO->cCast ){
+ zCastTo = pSO->zTypename;
+ needsEval = pSO->evalKind > 0;
+ break;
}
}
- if( filectrl<0 ){
- utf8_printf(stderr,"Error: unknown file-control: %s\n"
- "Use \".filectrl --help\" for help\n", zCmd);
+ }
+ if( needsEval ){
+ if( zCastTo!=0 ){
+ zSql = smprintf
+ ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
+ " VALUES(%Q,CAST((%s) AS %s),"SPTU_Binding");",
+ name, zValue, zCastTo );
}else{
- switch(filectrl){
- case SQLITE_FCNTL_SIZE_LIMIT: {
- if( nArg!=2 && nArg!=3 ) break;
- iRes = nArg==3 ? integerValue(azArg[2]) : -1;
- sqlite3_file_control(p->db, zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes);
- isOk = 1;
- break;
- }
- case SQLITE_FCNTL_LOCK_TIMEOUT:
- case SQLITE_FCNTL_CHUNK_SIZE: {
- int x;
- if( nArg!=3 ) break;
- x = (int)integerValue(azArg[2]);
- sqlite3_file_control(p->db, zSchema, filectrl, &x);
- isOk = 2;
- break;
- }
- case SQLITE_FCNTL_PERSIST_WAL:
- case SQLITE_FCNTL_POWERSAFE_OVERWRITE: {
- int x;
- if( nArg!=2 && nArg!=3 ) break;
- x = nArg==3 ? booleanValue(azArg[2]) : -1;
- sqlite3_file_control(p->db, zSchema, filectrl, &x);
- iRes = x;
- isOk = 1;
- break;
- }
- case SQLITE_FCNTL_DATA_VERSION:
- case SQLITE_FCNTL_HAS_MOVED: {
- int x;
- if( nArg!=2 ) break;
- sqlite3_file_control(p->db, zSchema, filectrl, &x);
- iRes = x;
- isOk = 1;
- break;
- }
- case SQLITE_FCNTL_TEMPFILENAME: {
- char *z = 0;
- if( nArg!=2 ) break;
- sqlite3_file_control(p->db, zSchema, filectrl, &z);
- if( z ){
- utf8_printf(p->out, "%s\n", z);
- sqlite3_free(z);
+ zSql = smprintf
+ ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
+ "VALUES(%Q,(%s),"SPTU_Binding");", name, zValue );
+ }
+ shell_check_oom(zSql);
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0);
+ sqlite3_free(zSql);
+ }
+ if( !needsEval || rc!=SQLITE_OK ){
+ /* Reach here when value either requested to be cast to text, or must be. */
+ sqlite3_finalize(pStmtSet);
+ pStmtSet = 0;
+ zSql = smprintf
+ ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
+ "VALUES(%Q,%Q,"SPTU_Binding");", name, zValue );
+ shell_check_oom(zSql);
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0);
+ assert(rc==SQLITE_OK);
+ sqlite3_free(zSql);
+ }
+ sqlite3_step(pStmtSet);
+ sqlite3_finalize(pStmtSet);
+ sqlite3_free(zValGlom);
+ return rc;
+}
+
+/* list or ls subcommand for .parameter and .vars dot-commands */
+static void list_pov_entries(ShellExState *psx, ParamTableUse ptu, u8 bShort,
+ char **pzArgs, int nArg){
+ sqlite3_stmt *pStmt = 0;
+ sqlite3_str *sbList;
+ int len = 0, rc;
+ char *zFromWhere = 0;
+ char *zSql = 0;
+ sqlite3 *db;
+ const char *zTab;
+
+ switch( ptu ){
+ case PTU_Binding:
+ db = DBX(psx);
+ zTab = PARAM_TABLE_SNAME;
+ break;
+ case PTU_Script:
+ db = psx->dbShell;
+ zTab = SHVAR_TABLE_NAME;
+ break;
+ default: assert(0); return;
+ }
+ sbList = sqlite3_str_new(db);
+ sqlite3_str_appendf(sbList, "FROM ");
+ sqlite3_str_appendf(sbList, zTab);
+ sqlite3_str_appendf(sbList, " WHERE (uses=?1) AND ");
+ append_glob_terms(sbList, "key",
+ (const char **)pzArgs, (const char **)pzArgs+nArg);
+ zFromWhere = sqlite3_str_finish(sbList);
+ shell_check_oom(zFromWhere);
+ zSql = smprintf("SELECT max(length(key)) %s", zFromWhere);
+ shell_check_oom(zSql);
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_int(pStmt, 1, ptu);
+ if( sqlite3_step(pStmt)==SQLITE_ROW ){
+ len = sqlite3_column_int(pStmt, 0);
+ if( len>40 ) len = 40;
+ if( len<4 ) len = 4;
+ }
+ }
+ sqlite3_finalize(pStmt);
+ pStmt = 0;
+ if( len ){
+ FILE *out = ISS(psx)->out;
+ sqlite3_free(zSql);
+ if( !bShort ){
+ int nBindings = 0, nScripts = 0;
+ zSql = smprintf("SELECT key, uses,"
+ " iif(typeof(value)='text', quote(value), value) as v"
+ " %z ORDER BY uses, key", zFromWhere);
+ shell_check_oom(zSql);
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ sqlite3_bind_int(pStmt, 1, ptu);
+ while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
+ ParamTableUse ptux = sqlite3_column_int(pStmt,1);
+ switch( ptux ){
+ case PTU_Binding:
+ if( nBindings++ == 0 ){
+ utf8_printf(out, "Bindings:\n%-*s %s\n",
+ len, "name", "value");
}
- isOk = 2;
+ utf8_printf(out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
+ sqlite3_column_text(pStmt,2));
break;
- }
- case SQLITE_FCNTL_RESERVE_BYTES: {
- int x;
- if( nArg>=3 ){
- x = atoi(azArg[2]);
- sqlite3_file_control(p->db, zSchema, filectrl, &x);
+ case PTU_Script:
+ if( nScripts++ == 0 ){
+ utf8_printf(out, "Scripts:\n%-*s %s\n", len, "name", "value");
}
- x = -1;
- sqlite3_file_control(p->db, zSchema, filectrl, &x);
- utf8_printf(p->out,"%d\n", x);
- isOk = 2;
+ utf8_printf(out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
+ sqlite3_column_text(pStmt,2));
break;
+ default: break; /* Ignore */
+ }
+ }
+ }else{
+ int nc = 0, ncw = 78/(len+2);
+ zSql = smprintf("SELECT key %z ORDER BY key", zFromWhere);
+ shell_check_oom(zSql);
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ sqlite3_bind_int(pStmt, 1, ptu);
+ while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
+ utf8_printf(out, "%s %-*s", ((++nc%ncw==0)? "\n" : ""),
+ len, sqlite3_column_text(pStmt,0));
+ }
+ if( nc>0 ) utf8_printf(out, "\n");
+ }
+ sqlite3_finalize(pStmt);
+ }else{
+ sqlite3_free(zFromWhere);
+ }
+ sqlite3_free(zSql);
+}
+
+/* Append an OR'ed series of GLOB terms comparing a given column
+ * name to a series of patterns. Result is an appended expression.
+ * For an empty pattern series, expression is true for non-NULL.
+ */
+static void append_glob_terms(sqlite3_str *pStr, const char *zColName,
+ const char **azBeg, const char **azLim){
+ if( azBeg==azLim ) sqlite3_str_appendf(pStr, "%s NOT NULL", zColName);
+ else{
+ char *zSep = "(";
+ while( azBeg<azLim ){
+ sqlite3_str_appendf(pStr, "%sglob(%Q,%s)", zSep, *azBeg, zColName);
+ zSep = " OR ";
+ ++azBeg;
+ }
+ sqlite3_str_appendf(pStr, ")");
+ }
+}
+
+/* Append either an IN clause or an always true test to some SQL.
+ *
+ * An empty IN list is the same as always true (for non-NULL LHS)
+ * for this clause, which assumes a trailing LHS operand and space.
+ * If that is not the right result, guard the call against it.
+ * This is used for ".parameter dostuff ?NAMES?" options,
+ * where a missing list means all the qualifying entries.
+ *
+ * The empty list may be signified by azBeg and azLim both 0.
+ */
+static void append_in_clause(sqlite3_str *pStr,
+ const char **azBeg, const char **azLim){
+ if( azBeg==azLim ) sqlite3_str_appendf(pStr, "NOT NULL");
+ else{
+ char cSep = '(';
+ sqlite3_str_appendf(pStr, "IN");
+ while( azBeg<azLim ){
+ sqlite3_str_appendf(pStr, "%c%Q", cSep, *azBeg);
+ cSep = ',';
+ ++azBeg;
+ }
+ sqlite3_str_appendf(pStr, ")");
+ }
+}
+
+/*****************
+ * The .parameter command
+ */
+COLLECT_HELP_TEXT[
+ ".parameter CMD ... Manage per-DB SQL parameter bindings table",
+ " clear ?NAMES? Erase all or only given named parameters",
+#ifndef SQLITE_NOHAVE_SYSTEM
+ " edit ?OPT? NAME ... Use edit() to create or alter parameter NAME",
+ " OPT may be -t to use edited value as text or -e to evaluate it.",
+#endif
+ " init Initialize TEMP table for bindings and scripts",
+ " list ?PATTERNS? List current DB parameters table binding values",
+ " Alternatively, to list just some or all names: ls ?PATTERNS?",
+ " load ?FILE? ?NAMES? Load some or all named parameters from FILE",
+ " If FILE missing, empty or '~', it defaults to ~/sqlite_params.sdb",
+ " save ?FILE? ?NAMES? Save some or all named parameters into FILE",
+ " If FILE missing, empty or '~', it defaults to ~/sqlite_params.sdb",
+ " set ?TOPT? NAME VALUE Give SQL parameter NAME a value of VALUE",
+ " NAME must begin with one of $,:,@,? for bindings, VALUE is the space-",
+ " joined argument list. TOPT may be one of {-b -i -n -r -t} to cast the",
+ " effective value to BLOB, INT, NUMERIC, REAL or TEXT respectively.",
+ " unset ?NAMES? Remove named parameter(s) from parameters table",
+];
+DISPATCHABLE_COMMAND( parameter 2 2 0 ){
+ int rc = 0;
+ open_db(p,0);
+ sqlite3 *db = DBX(p);
+
+ /* .parameter clear and .parameter unset ?NAMES?
+ ** Delete some or all parameters from the TEMP table that holds them.
+ ** Without any arguments, clear deletes them all and unset does nothing.
+ */
+ if( cli_strcmp(azArg[1],"clear")==0 || cli_strcmp(azArg[1],"unset")==0 ){
+ if( param_table_exists(db) && (nArg>2 || azArg[1][0]=='c') ){
+ sqlite3_str *sbZap = sqlite3_str_new(db);
+ char *zSql;
+ sqlite3_str_appendf
+ (sbZap, "DELETE FROM "PARAM_TABLE_SNAME" WHERE key ");
+ append_in_clause(sbZap,
+ (const char **)&azArg[2], (const char **)&azArg[nArg]);
+ zSql = sqlite3_str_finish(sbZap);
+ shell_check_oom(zSql);
+ sqlite3_exec(db, zSql, 0, 0, 0);
+ sqlite3_free(zSql);
+ }
+ }else
+#ifndef SQLITE_NOHAVE_SYSTEM
+ /* .parameter edit ?NAMES?
+ ** Edit the named parameters. Any that do not exist are created.
+ */
+ if( cli_strcmp(azArg[1],"edit")==0 ){
+ ShellInState *psi = ISS(p);
+ int ia = 2;
+ int eval = 0;
+ int edSet;
+
+ if( !INSOURCE_IS_INTERACTIVE(psi->pInSource) ){
+ utf8_printf(STD_ERR, "Error: "
+ ".parameter edit can only be used interactively.\n");
+ return DCR_Error;
+ }
+ param_table_init(db);
+ edSet = attempt_editor_set(psi, azArg[0], (nArg>2)? azArg[2] : 0 );
+ if( edSet < 0 ) return DCR_Error;
+ else ia += edSet;
+ /* Future: Allow an option whereby new value can be evaluated
+ * the way that .parameter set ... does.
+ */
+ while( ia < nArg ){
+ ParamTableUse ptu;
+ char *zA = azArg[ia];
+ char cf = (zA[0]=='-')? zA[1] : 0;
+ if( cf!=0 && zA[2]==0 ){
+ ++ia;
+ switch( cf ){
+ case 'e': eval = 1; continue;
+ case 't': eval = 0; continue;
+ default:
+ utf8_printf(STD_ERR, "Error: bad .parameter name: %s\n", zA);
+ return DCR_Error;
}
}
- }
- if( isOk==0 && iCtrl>=0 ){
- utf8_printf(p->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
- rc = 1;
- }else if( isOk==1 ){
- char zBuf[100];
- sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes);
- raw_printf(p->out, "%s\n", zBuf);
+ ptu = classify_param_name(zA);
+ if( ptu!=PTU_Binding ){
+ utf8_printf(STD_ERR, "Error: "
+ "%s cannot be a binding parameter name.\n", zA);
+ return DCR_Error;
+ }
+ rc = edit_one_kvalue(db, zA, eval, ptu, psi->zEditor);
+ ++ia;
+ if( rc!=0 ) return DCR_Error;
}
}else
+#endif
- if( c=='f' && cli_strncmp(azArg[0], "fullschema", n)==0 ){
- ShellState data;
- int doStats = 0;
- memcpy(&data, p, sizeof(data));
- data.showHeader = 0;
- data.cMode = data.mode = MODE_Semi;
- if( nArg==2 && optionMatch(azArg[1], "indent") ){
- data.cMode = data.mode = MODE_Pretty;
- nArg = 1;
- }
- if( nArg!=1 ){
- raw_printf(stderr, "Usage: .fullschema ?--indent?\n");
- rc = 1;
- goto meta_command_exit;
- }
- open_db(p, 0);
- rc = sqlite3_exec(p->db,
- "SELECT sql FROM"
- " (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x"
- " FROM sqlite_schema UNION ALL"
- " SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) "
- "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' "
- "ORDER BY x",
- callback, &data, 0
- );
- if( rc==SQLITE_OK ){
- sqlite3_stmt *pStmt;
- rc = sqlite3_prepare_v2(p->db,
- "SELECT rowid FROM sqlite_schema"
- " WHERE name GLOB 'sqlite_stat[134]'",
- -1, &pStmt, 0);
- doStats = sqlite3_step(pStmt)==SQLITE_ROW;
- sqlite3_finalize(pStmt);
- }
- if( doStats==0 ){
- raw_printf(p->out, "/* No STAT tables available */\n");
- }else{
- raw_printf(p->out, "ANALYZE sqlite_schema;\n");
- data.cMode = data.mode = MODE_Insert;
- data.zDestTable = "sqlite_stat1";
- shell_exec(&data, "SELECT * FROM sqlite_stat1", 0);
- data.zDestTable = "sqlite_stat4";
- shell_exec(&data, "SELECT * FROM sqlite_stat4", 0);
- raw_printf(p->out, "ANALYZE sqlite_schema;\n");
- }
+ /* .parameter init
+ ** Make sure the TEMP table used to hold bind parameters exists.
+ ** Create it if necessary.
+ */
+ if( nArg==2 && cli_strcmp(azArg[1],"init")==0 ){
+ param_table_init(db);
}else
- if( c=='h' && cli_strncmp(azArg[0], "headers", n)==0 ){
- if( nArg==2 ){
- p->showHeader = booleanValue(azArg[1]);
- p->shellFlgs |= SHFLG_HeaderSet;
- }else{
- raw_printf(stderr, "Usage: .headers on|off\n");
- rc = 1;
- }
+ /* .parameter list|ls
+ ** List all or selected bind parameters.
+ ** list displays names, values and uses.
+ ** ls displays just the names.
+ */
+ if( nArg>=2 && ((cli_strcmp(azArg[1],"list")==0)
+ || (cli_strcmp(azArg[1],"ls")==0)) ){
+ list_pov_entries(p, PTU_Binding, azArg[1][1]=='s', azArg+2, nArg-2);
}else
- if( c=='h' && cli_strncmp(azArg[0], "help", n)==0 ){
- if( nArg>=2 ){
- n = showHelp(p->out, azArg[1]);
- if( n==0 ){
- utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]);
- }
- }else{
- showHelp(p->out, 0);
- }
+ /* .parameter load
+ ** Load all or named parameters from specified or default (DB) file.
+ */
+ if( cli_strcmp(azArg[1],"load")==0 ){
+ param_table_init(db);
+ rc = kv_pairs_load(db, PTU_Binding, (const char **)azArg+1, nArg-1);
}else
-#ifndef SQLITE_SHELL_FIDDLE
- if( c=='i' && cli_strncmp(azArg[0], "import", n)==0 ){
- char *zTable = 0; /* Insert data into this table */
- char *zSchema = 0; /* within this schema (may default to "main") */
- char *zFile = 0; /* Name of file to extra content from */
- sqlite3_stmt *pStmt = NULL; /* A statement */
- int nCol; /* Number of columns in the table */
- int nByte; /* Number of bytes in an SQL string */
- int i, j; /* Loop counters */
- int needCommit; /* True to COMMIT or ROLLBACK at end */
- int nSep; /* Number of bytes in p->colSeparator[] */
- char *zSql; /* An SQL statement */
- char *zFullTabName; /* Table name with schema if applicable */
- ImportCtx sCtx; /* Reader context */
- char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
- int eVerbose = 0; /* Larger for more console output */
- int nSkip = 0; /* Initial lines to skip */
- int useOutputMode = 1; /* Use output mode to determine separators */
- char *zCreate = 0; /* CREATE TABLE statement text */
-
- failIfSafeMode(p, "cannot run .import in safe mode");
- memset(&sCtx, 0, sizeof(sCtx));
- if( p->mode==MODE_Ascii ){
- xRead = ascii_read_one_field;
- }else{
- xRead = csv_read_one_field;
- }
- rc = 1;
- for(i=1; i<nArg; i++){
- char *z = azArg[i];
- if( z[0]=='-' && z[1]=='-' ) z++;
- if( z[0]!='-' ){
- if( zFile==0 ){
- zFile = z;
- }else if( zTable==0 ){
- zTable = z;
- }else{
- utf8_printf(p->out, "ERROR: extra argument: \"%s\". Usage:\n", z);
- showHelp(p->out, "import");
- goto meta_command_exit;
- }
- }else if( cli_strcmp(z,"-v")==0 ){
- eVerbose++;
- }else if( cli_strcmp(z,"-schema")==0 && i<nArg-1 ){
- zSchema = azArg[++i];
- }else if( cli_strcmp(z,"-skip")==0 && i<nArg-1 ){
- nSkip = integerValue(azArg[++i]);
- }else if( cli_strcmp(z,"-ascii")==0 ){
- sCtx.cColSep = SEP_Unit[0];
- sCtx.cRowSep = SEP_Record[0];
- xRead = ascii_read_one_field;
- useOutputMode = 0;
- }else if( cli_strcmp(z,"-csv")==0 ){
- sCtx.cColSep = ',';
- sCtx.cRowSep = '\n';
- xRead = csv_read_one_field;
- useOutputMode = 0;
- }else{
- utf8_printf(p->out, "ERROR: unknown option: \"%s\". Usage:\n", z);
- showHelp(p->out, "import");
- goto meta_command_exit;
- }
- }
- if( zTable==0 ){
- utf8_printf(p->out, "ERROR: missing %s argument. Usage:\n",
- zFile==0 ? "FILE" : "TABLE");
- showHelp(p->out, "import");
- goto meta_command_exit;
- }
- seenInterrupt = 0;
- open_db(p, 0);
- if( useOutputMode ){
- /* If neither the --csv or --ascii options are specified, then set
- ** the column and row separator characters from the output mode. */
- nSep = strlen30(p->colSeparator);
- if( nSep==0 ){
- raw_printf(stderr,
- "Error: non-null column separator required for import\n");
- goto meta_command_exit;
- }
- if( nSep>1 ){
- raw_printf(stderr,
- "Error: multi-character column separators not allowed"
- " for import\n");
- goto meta_command_exit;
- }
- nSep = strlen30(p->rowSeparator);
- if( nSep==0 ){
- raw_printf(stderr,
- "Error: non-null row separator required for import\n");
- goto meta_command_exit;
- }
- if( nSep==2 && p->mode==MODE_Csv
- && cli_strcmp(p->rowSeparator,SEP_CrLf)==0
- ){
- /* When importing CSV (only), if the row separator is set to the
- ** default output row separator, change it to the default input
- ** row separator. This avoids having to maintain different input
- ** and output row separators. */
- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
- nSep = strlen30(p->rowSeparator);
- }
- if( nSep>1 ){
- raw_printf(stderr, "Error: multi-character row separators not allowed"
- " for import\n");
- goto meta_command_exit;
- }
- sCtx.cColSep = (u8)p->colSeparator[0];
- sCtx.cRowSep = (u8)p->rowSeparator[0];
- }
- sCtx.zFile = zFile;
- sCtx.nLine = 1;
- if( sCtx.zFile[0]=='|' ){
-#ifdef SQLITE_OMIT_POPEN
- raw_printf(stderr, "Error: pipes are not supported in this OS\n");
- goto meta_command_exit;
-#else
- sCtx.in = popen(sCtx.zFile+1, "r");
- sCtx.zFile = "<pipe>";
- sCtx.xCloser = pclose;
-#endif
- }else{
- sCtx.in = fopen(sCtx.zFile, "rb");
- sCtx.xCloser = fclose;
- }
- if( sCtx.in==0 ){
- utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile);
- goto meta_command_exit;
- }
- if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
- char zSep[2];
- zSep[1] = 0;
- zSep[0] = sCtx.cColSep;
- utf8_printf(p->out, "Column separator ");
- output_c_string(p->out, zSep);
- utf8_printf(p->out, ", row separator ");
- zSep[0] = sCtx.cRowSep;
- output_c_string(p->out, zSep);
- utf8_printf(p->out, "\n");
- }
- sCtx.z = sqlite3_malloc64(120);
- if( sCtx.z==0 ){
- import_cleanup(&sCtx);
- shell_out_of_memory();
- }
- /* Below, resources must be freed before exit. */
- while( (nSkip--)>0 ){
- while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
- }
- if( zSchema!=0 ){
- zFullTabName = sqlite3_mprintf("\"%w\".\"%w\"", zSchema, zTable);
+ /* .parameter save
+ ** Save all or named parameters into specified or default (DB) file.
+ */
+ if( cli_strcmp(azArg[1],"save")==0 ){
+ rc = kv_pairs_save(db, PTU_Binding, (const char **)azArg+1, nArg-1);
+ }else
+
+ /* .parameter set NAME VALUE
+ ** Set or reset a bind parameter. NAME should be the full parameter
+ ** name exactly as it appears in the query. (ex: $abc, @def). The
+ ** VALUE can be in either SQL literal notation, or if not it will be
+ ** understood to be a text string.
+ */
+ if( nArg>=4 && cli_strcmp(azArg[1],"set")==0 ){
+ char cCast = option_char(azArg[2]);
+ int inv = 2 + (cCast != 0);
+ ParamTableUse ptu = classify_param_name(azArg[inv]);
+ if( ptu!=PTU_Binding ){
+ utf8_printf(STD_ERR,
+ "Error: %s is not a usable parameter name.\n", azArg[inv]);
+ rc = 1;
}else{
- zFullTabName = sqlite3_mprintf("\"%w\"", zTable);
- }
- zSql = sqlite3_mprintf("SELECT * FROM %s", zFullTabName);
- if( zSql==0 || zFullTabName==0 ){
- import_cleanup(&sCtx);
- shell_out_of_memory();
- }
- nByte = strlen30(zSql);
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */
- if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){
- sqlite3 *dbCols = 0;
- char *zRenames = 0;
- char *zColDefs;
- zCreate = sqlite3_mprintf("CREATE TABLE %s", zFullTabName);
- while( xRead(&sCtx) ){
- zAutoColumn(sCtx.z, &dbCols, 0);
- if( sCtx.cTerm!=sCtx.cColSep ) break;
- }
- zColDefs = zAutoColumn(0, &dbCols, &zRenames);
- if( zRenames!=0 ){
- utf8_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr,
- "Columns renamed during .import %s due to duplicates:\n"
- "%s\n", sCtx.zFile, zRenames);
- sqlite3_free(zRenames);
- }
- assert(dbCols==0);
- if( zColDefs==0 ){
- utf8_printf(stderr,"%s: empty file\n", sCtx.zFile);
- import_fail:
- sqlite3_free(zCreate);
- sqlite3_free(zSql);
- sqlite3_free(zFullTabName);
- import_cleanup(&sCtx);
+ param_table_init(db);
+ rc = param_set(db, cCast, azArg[inv], &azArg[inv+1], &azArg[nArg]);
+ if( rc!=SQLITE_OK ){
+ utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(db));
rc = 1;
- goto meta_command_exit;
}
- zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs);
- if( eVerbose>=1 ){
- utf8_printf(p->out, "%s\n", zCreate);
+ }
+ }else
+
+ { /* If no command name and arg count matches, show a syntax error */
+ showHelp(ISS(p)->out, "parameter", p);
+ return DCR_CmdErred;
+ }
+
+ return DCR_Ok | (rc!=0);
+}
+
+/*****************
+ * The .print, .progress and .prompt commands
+ */
+CONDITION_COMMAND( progress !defined(SQLITE_OMIT_PROGRESS_CALLBACK) );
+COLLECT_HELP_TEXT[
+ ".print STRING... Print literal STRING",
+ ".progress N Invoke progress handler after every N opcodes",
+ " --limit N Interrupt after N progress callbacks",
+ " --once Do no more than one progress interrupt",
+ " --quiet|-q No output except at interrupts",
+ " --reset Reset the count for each input and interrupt",
+ ".prompt MAIN CONTINUE Replace the standard prompts",
+];
+DISPATCHABLE_COMMAND( print 3 1 0 ){
+ int i;
+ for(i=1; i<nArg; i++){
+ utf8_printf(ISS(p)->out, "%s%s", azArg[i], (i==nArg-1)? "\n" : " ");
+ }
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( progress 3 2 0 ){
+ ShellInState *psi = ISS(p);
+ int i;
+ int nn = 0;
+ psi->flgProgress = 0;
+ psi->mxProgress = 0;
+ psi->nProgress = 0;
+ for(i=1; i<nArg; i++){
+ const char *z = azArg[i];
+ if( z[0]=='-' ){
+ z++;
+ if( z[0]=='-' ) z++;
+ if( cli_strcmp(z,"quiet")==0 || cli_strcmp(z,"q")==0 ){
+ psi->flgProgress |= SHELL_PROGRESS_QUIET;
+ continue;
}
- rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
- if( rc ){
- utf8_printf(stderr, "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
- goto import_fail;
+ if( cli_strcmp(z,"reset")==0 ){
+ psi->flgProgress |= SHELL_PROGRESS_RESET;
+ continue;
}
- sqlite3_free(zCreate);
- zCreate = 0;
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- }
- if( rc ){
- if (pStmt) sqlite3_finalize(pStmt);
- utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db));
- goto import_fail;
- }
- sqlite3_free(zSql);
- nCol = sqlite3_column_count(pStmt);
- sqlite3_finalize(pStmt);
- pStmt = 0;
- if( nCol==0 ) return 0; /* no columns, no error */
- zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 );
- if( zSql==0 ){
- import_cleanup(&sCtx);
- shell_out_of_memory();
- }
- sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName);
- j = strlen30(zSql);
- for(i=1; i<nCol; i++){
- zSql[j++] = ',';
- zSql[j++] = '?';
- }
- zSql[j++] = ')';
- zSql[j] = 0;
- if( eVerbose>=2 ){
- utf8_printf(p->out, "Insert using: %s\n", zSql);
- }
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- if( rc ){
- utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
- if (pStmt) sqlite3_finalize(pStmt);
- goto import_fail;
- }
- sqlite3_free(zSql);
- sqlite3_free(zFullTabName);
- needCommit = sqlite3_get_autocommit(p->db);
- if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
- do{
- int startLine = sCtx.nLine;
- for(i=0; i<nCol; i++){
- char *z = xRead(&sCtx);
- /*
- ** Did we reach end-of-file before finding any columns?
- ** If so, stop instead of NULL filling the remaining columns.
- */
- if( z==0 && i==0 ) break;
- /*
- ** Did we reach end-of-file OR end-of-line before finding any
- ** columns in ASCII mode? If so, stop instead of NULL filling
- ** the remaining columns.
- */
- if( p->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
- sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
- if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
- utf8_printf(stderr, "%s:%d: expected %d columns but found %d - "
- "filling the rest with NULL\n",
- sCtx.zFile, startLine, nCol, i+1);
- i += 2;
- while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; }
- }
+ if( cli_strcmp(z,"once")==0 ){
+ psi->flgProgress |= SHELL_PROGRESS_ONCE;
+ continue;
}
- if( sCtx.cTerm==sCtx.cColSep ){
- do{
- xRead(&sCtx);
- i++;
- }while( sCtx.cTerm==sCtx.cColSep );
- utf8_printf(stderr, "%s:%d: expected %d columns but found %d - "
- "extras ignored\n",
- sCtx.zFile, startLine, nCol, i);
- }
- if( i>=nCol ){
- sqlite3_step(pStmt);
- rc = sqlite3_reset(pStmt);
- if( rc!=SQLITE_OK ){
- utf8_printf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
- startLine, sqlite3_errmsg(p->db));
- sCtx.nErr++;
+ if( cli_strcmp(z,"limit")==0 ){
+ if( i+1>=nArg ){
+ *pzErr = smprintf("missing argument on --limit\n");
+ return DCR_Unpaired|i;
}else{
- sCtx.nRow++;
+ psi->mxProgress = (int)integerValue(azArg[++i]);
}
+ continue;
}
- }while( sCtx.cTerm!=EOF );
-
- import_cleanup(&sCtx);
- sqlite3_finalize(pStmt);
- if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0);
- if( eVerbose>0 ){
- utf8_printf(p->out,
- "Added %d rows with %d errors using %d lines of input\n",
- sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
+ return DCR_Unknown|i;
+ }else{
+ nn = (int)integerValue(z);
}
- }else
-#endif /* !defined(SQLITE_SHELL_FIDDLE) */
+ }
+ open_db(p, 0);
+ sqlite3_progress_handler(DBX(p), nn, progress_handler, psi);
+ return DCR_Ok;
+}
-#ifndef SQLITE_UNTESTABLE
- if( c=='i' && cli_strncmp(azArg[0], "imposter", n)==0 ){
- char *zSql;
- char *zCollist = 0;
- sqlite3_stmt *pStmt;
- int tnum = 0;
- int isWO = 0; /* True if making an imposter of a WITHOUT ROWID table */
- int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */
- int i;
- if( !ShellHasFlag(p,SHFLG_TestingMode) ){
- utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n",
- "imposter");
- rc = 1;
- goto meta_command_exit;
- }
- if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){
- utf8_printf(stderr, "Usage: .imposter INDEX IMPOSTER\n"
- " .imposter off\n");
- /* Also allowed, but not documented:
- **
- ** .imposter TABLE IMPOSTER
- **
- ** where TABLE is a WITHOUT ROWID table. In that case, the
- ** imposter is another WITHOUT ROWID table with the columns in
- ** storage order. */
- rc = 1;
- goto meta_command_exit;
- }
- open_db(p, 0);
- if( nArg==2 ){
- sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 1);
- goto meta_command_exit;
- }
- zSql = sqlite3_mprintf(
- "SELECT rootpage, 0 FROM sqlite_schema"
- " WHERE name='%q' AND type='index'"
- "UNION ALL "
- "SELECT rootpage, 1 FROM sqlite_schema"
- " WHERE name='%q' AND type='table'"
- " AND sql LIKE '%%without%%rowid%%'",
- azArg[1], azArg[1]
- );
- sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- sqlite3_free(zSql);
- if( sqlite3_step(pStmt)==SQLITE_ROW ){
- tnum = sqlite3_column_int(pStmt, 0);
- isWO = sqlite3_column_int(pStmt, 1);
- }
- sqlite3_finalize(pStmt);
- zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]);
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- sqlite3_free(zSql);
- i = 0;
- while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
- char zLabel[20];
- const char *zCol = (const char*)sqlite3_column_text(pStmt,2);
+/* Allow too few arguments by tradition, (a form of no-op.) */
+DISPATCHABLE_COMMAND( prompt ? 1 3 ){
+ if( nArg >= 2) {
+ SET_MAIN_PROMPT(azArg[1]);
+ }
+ if( nArg >= 3) {
+ SET_MORE_PROMPT(azArg[2]);
+ }
+ return DCR_Ok;
+}
+
+/*****************
+ * The .recover and .restore commands
+ */
+CONDITION_COMMAND( recover SQLITE_SHELL_HAVE_RECOVER );
+CONDITION_COMMAND( restore !defined(SQLITE_SHELL_FIDDLE) );
+COLLECT_HELP_TEXT[
+ ".recover Recover as much data as possible from corrupt db.",
+ " --ignore-freelist Ignore pages that appear to be on db freelist",
+ " --lost-and-found TABLE Alternative name for the lost-and-found table",
+ " --no-rowids Do not attempt to recover rowid values",
+ " that are not also INTEGER PRIMARY KEYs",
+ ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE",
+];
+
+/*
+** This command is invoked to recover data from the database. A script
+** to construct a new database containing all recovered data is output
+** on stream pState->out.
+*/
+DISPATCHABLE_COMMAND( recover ? 1 7 ){
+ int rc = SQLITE_OK;
+ const char *zRecoveryDb = ""; /* Name of "recovery" database. Debug only */
+ const char *zLAF = "lost_and_found";
+ int bFreelist = 1; /* 0 if --ignore-freelist is specified */
+ int bRowids = 1; /* 0 if --no-rowids */
+ sqlite3_recover *pr = 0;
+ int i = 0;
+ FILE *out = ISS(p)->out;
+ sqlite3 *db;
+
+ open_db(p, 0);
+ db = DBX(p);
+
+ for(i=1; i<nArg; i++){
+ char *z = azArg[i];
+ int n;
+ if( z[0]=='-' && z[1]=='-' ) z++;
+ n = strlen30(z);
+ if( n<=17 && memcmp("-ignore-freelist", z, n)==0 ){
+ bFreelist = 0;
+ }else
+ if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
+ /* This option determines the name of the ATTACH-ed database used
+ ** internally by the recovery extension. The default is "" which
+ ** means to use a temporary database that is automatically deleted
+ ** when closed. This option is undocumented and might disappear at
+ ** any moment. */
i++;
- if( zCol==0 ){
- if( sqlite3_column_int(pStmt,1)==-1 ){
- zCol = "_ROWID_";
- }else{
- sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i);
- zCol = zLabel;
- }
- }
- if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){
- lenPK = (int)strlen(zCollist);
- }
- if( zCollist==0 ){
- zCollist = sqlite3_mprintf("\"%w\"", zCol);
- }else{
- zCollist = sqlite3_mprintf("%z,\"%w\"", zCollist, zCol);
- }
+ zRecoveryDb = azArg[i];
+ }else
+ if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
+ i++;
+ zLAF = azArg[i];
+ }else
+ if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
+ bRowids = 0;
}
- sqlite3_finalize(pStmt);
- if( i==0 || tnum==0 ){
- utf8_printf(stderr, "no such index: \"%s\"\n", azArg[1]);
- rc = 1;
- sqlite3_free(zCollist);
- goto meta_command_exit;
+ else{
+ utf8_printf(stderr, "unexpected option: %s\n", azArg[i]);
+ showHelp(stderr, azArg[0], p);
+ return DCR_Error;
}
- if( lenPK==0 ) lenPK = 100000;
- zSql = sqlite3_mprintf(
- "CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))WITHOUT ROWID",
- azArg[2], zCollist, lenPK, zCollist);
- sqlite3_free(zCollist);
- rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 1, tnum);
- if( rc==SQLITE_OK ){
- rc = sqlite3_exec(p->db, zSql, 0, 0, 0);
- sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0);
- if( rc ){
- utf8_printf(stderr, "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db));
- }else{
- utf8_printf(stdout, "%s;\n", zSql);
- raw_printf(stdout,
- "WARNING: writing to an imposter table will corrupt the \"%s\" %s!\n",
- azArg[1], isWO ? "table" : "index"
- );
- }
- }else{
- raw_printf(stderr, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
- rc = 1;
+ }
+
+ pr = sqlite3_recover_init_sql(db, "main", recoverSqlCb, (void*)p);
+
+ sqlite3_recover_config(pr, 789, (void*)zRecoveryDb); /* Debug use only */
+ sqlite3_recover_config(pr, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF);
+ sqlite3_recover_config(pr, SQLITE_RECOVER_ROWIDS, (void*)&bRowids);
+ sqlite3_recover_config(pr, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist);
+
+ sqlite3_recover_run(pr);
+ if( sqlite3_recover_errcode(pr)!=SQLITE_OK ){
+ const char *zErr = sqlite3_recover_errmsg(pr);
+ int errCode = sqlite3_recover_errcode(pr);
+ raw_printf(stderr, "sql error: %s (%d)\n", zErr, errCode);
+ }
+ rc = sqlite3_recover_finish(pr);
+ return rc? DCR_Error : DCR_Ok;
+}
+
+DISPATCHABLE_COMMAND( restore ? 2 3 ){
+ int rc;
+ const char *zSrcFile;
+ const char *zDb;
+ sqlite3 *pSrc;
+ sqlite3_backup *pBackup;
+ int nTimeout = 0;
+
+ if( ISS(p)->bSafeMode ) return DCR_AbortError;
+ if( nArg==2 ){
+ zSrcFile = azArg[1];
+ zDb = "main";
+ }else if( nArg==3 ){
+ zSrcFile = azArg[2];
+ zDb = azArg[1];
+ }else{
+ return DCR_TooMany;
+ }
+ rc = sqlite3_open(zSrcFile, &pSrc);
+ if( rc!=SQLITE_OK ){
+ *pzErr = smprintf("cannot open \"%s\"\n", zSrcFile);
+ close_db(pSrc);
+ return DCR_Error;
+ }
+ open_db(p, 0);
+ pBackup = sqlite3_backup_init(DBX(p), zDb, pSrc, "main");
+ if( pBackup==0 ){
+ *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
+ close_db(pSrc);
+ return DCR_Error;
+ }
+ while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK
+ || rc==SQLITE_BUSY ){
+ if( rc==SQLITE_BUSY ){
+ if( nTimeout++ >= 3 ) break;
+ sqlite3_sleep(100);
}
- sqlite3_free(zSql);
- }else
-#endif /* !defined(SQLITE_OMIT_TEST_CONTROL) */
+ }
+ sqlite3_backup_finish(pBackup);
+ if( rc==SQLITE_DONE ){
+ rc = 0;
+ }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){
+ *pzErr = smprintf("source database is busy\n");
+ rc = 1;
+ }else{
+ *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
+ rc = 1;
+ }
+ close_db(pSrc);
+ return DCR_Ok|rc;
+}
-#ifdef SQLITE_ENABLE_IOTRACE
- if( c=='i' && cli_strncmp(azArg[0], "iotrace", n)==0 ){
- SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...);
- if( iotrace && iotrace!=stdout ) fclose(iotrace);
- iotrace = 0;
- if( nArg<2 ){
- sqlite3IoTrace = 0;
- }else if( cli_strcmp(azArg[1], "-")==0 ){
- sqlite3IoTrace = iotracePrintf;
- iotrace = stdout;
+/*****************
+ * The .scanstats and .schema commands
+ */
+COLLECT_HELP_TEXT[
+ ".scanstats on|off|est Turn sqlite3_stmt_scanstatus() metrics on or off",
+ ".schema ?PATTERN? Show the CREATE statements matching PATTERN",
+ " Options:",
+ " --indent Try to pretty-print the schema",
+ " --nosys Omit objects whose names start with \"sqlite_\"",
+];
+DISPATCHABLE_COMMAND( scanstats ? 2 2 ){
+ if( cli_strcmp(azArg[1], "est")==0 ){
+ ISS(p)->scanstatsOn = 2;
+ }else{
+ ISS(p)->scanstatsOn = (u8)booleanValue(azArg[1]);
+ }
+ open_db(p, 0);
+ sqlite3_db_config(DBX(p), SQLITE_DBCONFIG_STMT_SCANSTATUS,
+ ISS(p)->scanstatsOn, (int*)0);
+#ifndef SQLITE_ENABLE_STMT_SCANSTATUS
+ raw_printf(STD_ERR, "Warning: .scanstats not available in this build.\n");
+#endif
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( schema ? 1 2 ){
+ int rc = 0;
+ ShellText sSelect;
+ ShellInState data;
+ ShellExState datax;
+ char *zErrMsg = 0;
+ const char *zDiv = "(";
+ const char *zName = 0;
+ int iSchema = 0;
+ int bDebug = 0;
+ int bNoSystemTabs = 0;
+ int ii;
+
+ open_db(p, 0);
+ /* Consider some refactoring to avoid duplicative wholesale copying. */
+ memcpy(&data, ISS(p), sizeof(data));
+ memcpy(&datax, p, sizeof(datax));
+ data.pSXS = &datax;
+ datax.pSIS = &data;
+
+ data.showHeader = 0;
+ data.cMode = data.mode = MODE_Semi;
+ initText(&sSelect);
+ for(ii=1; ii<nArg; ii++){
+ if( optionMatch(azArg[ii],"indent") ){
+ data.cMode = data.mode = MODE_Pretty;
+ }else if( optionMatch(azArg[ii],"debug") ){
+ bDebug = 1;
+ }else if( optionMatch(azArg[ii],"nosys") ){
+ bNoSystemTabs = 1;
+ }else if( azArg[ii][0]=='-' ){
+ return DCR_Unknown|ii;
+ }else if( zName==0 ){
+ zName = azArg[ii];
}else{
- iotrace = fopen(azArg[1], "w");
- if( iotrace==0 ){
- utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]);
- sqlite3IoTrace = 0;
- rc = 1;
+ return DCR_TooMany;
+ }
+ }
+ if( zName!=0 ){
+ int isSchema = sqlite3_strlike(zName, "sqlite_master", '\\')==0
+ || sqlite3_strlike(zName, "sqlite_schema", '\\')==0
+ || sqlite3_strlike(zName,"sqlite_temp_master", '\\')==0
+ || sqlite3_strlike(zName,"sqlite_temp_schema", '\\')==0;
+ if( isSchema ){
+ char *new_argv[2], *new_colv[2];
+ new_argv[0] = smprintf(
+ "CREATE TABLE %s (\n"
+ " type text,\n"
+ " name text,\n"
+ " tbl_name text,\n"
+ " rootpage integer,\n"
+ " sql text\n"
+ ")", zName);
+ shell_check_oom(new_argv[0]);
+ new_argv[1] = 0;
+ new_colv[0] = "sql";
+ new_colv[1] = 0;
+ callback(&datax, 1, new_argv, new_colv);
+ sqlite3_free(new_argv[0]);
+ }
+ }
+ if( zDiv ){
+ sqlite3_stmt *pStmt = 0;
+ rc = sqlite3_prepare_v2(datax.dbUser,
+ "SELECT name FROM pragma_database_list",
+ -1, &pStmt, 0);
+ if( rc ){
+ *pzErr = smprintf("%s\n", sqlite3_errmsg(datax.dbUser));
+ sqlite3_finalize(pStmt);
+ return DCR_Error;
+ }
+ appendText(&sSelect, "SELECT sql FROM", 0);
+ iSchema = 0;
+ while( sqlite3_step(pStmt)==SQLITE_ROW ){
+ const char *zDb = (const char*)sqlite3_column_text(pStmt, 0);
+ char zScNum[30];
+ sqlite3_snprintf(sizeof(zScNum), zScNum, "%d", ++iSchema);
+ appendText(&sSelect, zDiv, 0);
+ zDiv = " UNION ALL ";
+ appendText(&sSelect, "SELECT shell_add_schema(sql,", 0);
+ if( sqlite3_stricmp(zDb, "main")!=0 ){
+ appendText(&sSelect, zDb, '\'');
}else{
- sqlite3IoTrace = iotracePrintf;
+ appendText(&sSelect, "NULL", 0);
}
+ appendText(&sSelect, ",name) AS sql, type, tbl_name, name, rowid,", 0);
+ appendText(&sSelect, zScNum, 0);
+ appendText(&sSelect, " AS snum, ", 0);
+ appendText(&sSelect, zDb, '\'');
+ appendText(&sSelect, " AS sname FROM ", 0);
+ appendText(&sSelect, zDb, quoteChar(zDb));
+ appendText(&sSelect, ".sqlite_schema", 0);
+ }
+ sqlite3_finalize(pStmt);
+#ifndef SQLITE_OMIT_INTROSPECTION_PRAGMAS
+ if( zName ){
+ appendText(&sSelect,
+ " UNION ALL SELECT shell_module_schema(name),"
+ " 'table', name, name, name, 9e+99, 'main'"
+ " FROM pragma_module_list",
+ 0);
}
- }else
#endif
-
- if( c=='l' && n>=5 && cli_strncmp(azArg[0], "limits", n)==0 ){
- static const struct {
- const char *zLimitName; /* Name of a limit */
- int limitCode; /* Integer code for that limit */
- } aLimit[] = {
- { "length", SQLITE_LIMIT_LENGTH },
- { "sql_length", SQLITE_LIMIT_SQL_LENGTH },
- { "column", SQLITE_LIMIT_COLUMN },
- { "expr_depth", SQLITE_LIMIT_EXPR_DEPTH },
- { "compound_select", SQLITE_LIMIT_COMPOUND_SELECT },
- { "vdbe_op", SQLITE_LIMIT_VDBE_OP },
- { "function_arg", SQLITE_LIMIT_FUNCTION_ARG },
- { "attached", SQLITE_LIMIT_ATTACHED },
- { "like_pattern_length", SQLITE_LIMIT_LIKE_PATTERN_LENGTH },
- { "variable_number", SQLITE_LIMIT_VARIABLE_NUMBER },
- { "trigger_depth", SQLITE_LIMIT_TRIGGER_DEPTH },
- { "worker_threads", SQLITE_LIMIT_WORKER_THREADS },
- };
- int i, n2;
- open_db(p, 0);
- if( nArg==1 ){
- for(i=0; i<ArraySize(aLimit); i++){
- printf("%20s %d\n", aLimit[i].zLimitName,
- sqlite3_limit(p->db, aLimit[i].limitCode, -1));
- }
- }else if( nArg>3 ){
- raw_printf(stderr, "Usage: .limit NAME ?NEW-VALUE?\n");
- rc = 1;
- goto meta_command_exit;
- }else{
- int iLimit = -1;
- n2 = strlen30(azArg[1]);
- for(i=0; i<ArraySize(aLimit); i++){
- if( sqlite3_strnicmp(aLimit[i].zLimitName, azArg[1], n2)==0 ){
- if( iLimit<0 ){
- iLimit = i;
- }else{
- utf8_printf(stderr, "ambiguous limit: \"%s\"\n", azArg[1]);
- rc = 1;
- goto meta_command_exit;
- }
- }
- }
- if( iLimit<0 ){
- utf8_printf(stderr, "unknown limit: \"%s\"\n"
- "enter \".limits\" with no arguments for a list.\n",
- azArg[1]);
- rc = 1;
- goto meta_command_exit;
+ appendText(&sSelect, ") WHERE ", 0);
+ if( zName ){
+ char *zQarg = smprintf("%Q", zName);
+ int bGlob;
+ shell_check_oom(zQarg);
+ bGlob = strchr(zName, '*') != 0 || strchr(zName, '?') != 0
+ || strchr(zName, '[') != 0;
+ if( strchr(zName, '.') ){
+ appendText(&sSelect, "lower(printf('%s.%s',sname,tbl_name))", 0);
+ }else{
+ appendText(&sSelect, "lower(tbl_name)", 0);
}
- if( nArg==3 ){
- sqlite3_limit(p->db, aLimit[iLimit].limitCode,
- (int)integerValue(azArg[2]));
+ appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0);
+ appendText(&sSelect, zQarg, 0);
+ if( !bGlob ){
+ appendText(&sSelect, " ESCAPE '\\' ", 0);
}
- printf("%20s %d\n", aLimit[iLimit].zLimitName,
- sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1));
+ appendText(&sSelect, " AND ", 0);
+ sqlite3_free(zQarg);
}
- }else
-
- if( c=='l' && n>2 && cli_strncmp(azArg[0], "lint", n)==0 ){
- open_db(p, 0);
- lintDotCommand(p, azArg, nArg);
- }else
-
-#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE)
- if( c=='l' && cli_strncmp(azArg[0], "load", n)==0 ){
- const char *zFile, *zProc;
- char *zErrMsg = 0;
- failIfSafeMode(p, "cannot run .load in safe mode");
- if( nArg<2 || azArg[1][0]==0 ){
- /* Must have a non-empty FILE. (Will not load self.) */
- raw_printf(stderr, "Usage: .load FILE ?ENTRYPOINT?\n");
- rc = 1;
- goto meta_command_exit;
+ if( bNoSystemTabs ){
+ appendText(&sSelect, "name NOT LIKE 'sqlite_%%' AND ", 0);
}
- zFile = azArg[1];
- zProc = nArg>=3 ? azArg[2] : 0;
- open_db(p, 0);
- rc = sqlite3_load_extension(p->db, zFile, zProc, &zErrMsg);
- if( rc!=SQLITE_OK ){
- utf8_printf(stderr, "Error: %s\n", zErrMsg);
- sqlite3_free(zErrMsg);
- rc = 1;
+ appendText(&sSelect, "sql IS NOT NULL"
+ " ORDER BY snum, rowid", 0);
+ if( bDebug ){
+ utf8_printf(data.out, "SQL: %s;\n", sSelect.z);
+ }else{
+ rc = sqlite3_exec(datax.dbUser, sSelect.z, callback, &datax, &zErrMsg);
}
- }else
-#endif
+ freeText(&sSelect);
+ }
+ if( zErrMsg ){
+ *pzErr = zErrMsg;
+ return DCR_Error;
+ }else if( rc != SQLITE_OK ){
+ *pzErr = smprintf("Error: querying schema information\n");
+ return DCR_Error;
+ }else{
+ return DCR_Ok;
+ }
+}
- if( c=='l' && cli_strncmp(azArg[0], "log", n)==0 ){
- if( nArg!=2 ){
- raw_printf(stderr, "Usage: .log FILENAME\n");
- rc = 1;
+/*****************
+ * The .selecttrace, .separator, .session and .sha3sum commands
+ */
+CONDITION_COMMAND( session defined(SQLITE_ENABLE_SESSION) );
+COLLECT_HELP_TEXT[
+ ".separator COL ?ROW? Change the column and row separators",
+ ".session ?NAME? CMD ... Create or control sessions",
+ " Subcommands:",
+ " attach TABLE Attach TABLE",
+ " changeset FILE Write a changeset into FILE",
+ " close Close one session",
+ " enable ?BOOLEAN? Set or query the enable bit",
+ " filter GLOB... Reject tables matching GLOBs",
+ " indirect ?BOOLEAN? Mark or query the indirect status",
+ " isempty Query whether the session is empty",
+ " list List currently open session names",
+ " open DB NAME Open a new session on DB",
+ " patchset FILE Write a patchset into FILE",
+ " If ?NAME? is omitted, the first defined session is used.",
+ ".sha3sum ... Compute a SHA3 hash of database content",
+ " Options:",
+ " --schema Also hash the sqlite_schema table",
+ " --sha3-224 Use the sha3-224 algorithm",
+ " --sha3-256 Use the sha3-256 algorithm (default)",
+ " --sha3-384 Use the sha3-384 algorithm",
+ " --sha3-512 Use the sha3-512 algorithm",
+ " Any other argument is a LIKE pattern for tables to hash",
+];
+DISPATCHABLE_COMMAND( separator ? 2 3 ){
+ if( nArg>=2 ){
+ sqlite3_snprintf(sizeof(ISS(p)->colSeparator), ISS(p)->colSeparator,
+ "%.*s", (int)ArraySize(ISS(p)->colSeparator)-1, azArg[1]);
+ }
+ if( nArg>=3 ){
+ sqlite3_snprintf(sizeof(ISS(p)->rowSeparator), ISS(p)->rowSeparator,
+ "%.*s", (int)ArraySize(ISS(p)->rowSeparator)-1, azArg[2]);
+ }
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( session 3 2 0 ){
+ int rc = 0;
+ struct AuxDb *pAuxDb = ISS(p)->pAuxDb;
+ OpenSession *pSession = &pAuxDb->aSession[0];
+ FILE *out = ISS(p)->out;
+ char **azCmd = &azArg[1];
+ int iSes = 0;
+ int nCmd = nArg - 1;
+ int i;
+ open_db(p, 0);
+ if( nArg>=3 ){
+ for(iSes=0; iSes<pAuxDb->nSession; iSes++){
+ if( cli_strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break;
+ }
+ if( iSes<pAuxDb->nSession ){
+ pSession = &pAuxDb->aSession[iSes];
+ azCmd++;
+ nCmd--;
}else{
- const char *zFile = azArg[1];
- if( p->bSafeMode
- && cli_strcmp(zFile,"on")!=0
- && cli_strcmp(zFile,"off")!=0
- ){
- raw_printf(stdout, "cannot set .log to anything other "
- "than \"on\" or \"off\"\n");
- zFile = "off";
+ pSession = &pAuxDb->aSession[0];
+ iSes = 0;
+ }
+ }
+
+ /* .session attach TABLE
+ ** Invoke the sqlite3session_attach() interface to attach a particular
+ ** table so that it is never filtered.
+ */
+ if( cli_strcmp(azCmd[0],"attach")==0 ){
+ if( nCmd!=2 ) goto session_syntax_error;
+ if( pSession->p==0 ){
+ session_not_open:
+ raw_printf(STD_ERR, "ERROR: No sessions are open\n");
+ }else{
+ rc = sqlite3session_attach(pSession->p, azCmd[1]);
+ if( rc ){
+ raw_printf(STD_ERR, "ERROR: sqlite3session_attach() returns %d\n", rc);
+ rc = 0;
}
- output_file_close(p->pLog);
- if( cli_strcmp(zFile,"on")==0 ) zFile = "stdout";
- p->pLog = output_file_open(zFile, 0);
}
}else