From: larrybr Date: Fri, 25 Mar 2022 20:39:09 +0000 (+0000) Subject: Sync w/trunk X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c351035606f02ed9f87258e2b249d7c437cb82cf;p=thirdparty%2Fsqlite.git Sync w/trunk FossilOrigin-Name: 8402e5e78a83af3ba015bb6ab173350c1b73e04ce37ef933f9a847a7cf5e6157 --- c351035606f02ed9f87258e2b249d7c437cb82cf diff --cc manifest index 03860984ab,9769465203..bb0787a778 --- a/manifest +++ b/manifest @@@ -1,5 -1,5 +1,5 @@@ - C CLI\spreparation\sfor\sshell's\sdb\sto\sbe\sused\sfrom\sTCL - D 2022-03-25T18:27:47.085 -C Fix\sthe\sBloom-filter\soptimization\sso\sthat\sit\sdoes\snot\suse\sIS\sNULL\sor\nIS\sNOT\sNULL\sconstraints\sfrom\sthe\sWHERE\sclause\swhen\soperating\son\sa\sLEFT\sJOIN.\n[forum:/forumpost/031e262a89b6a9d2|Forum\sthread\s031e262a89b6a9d2]. -D 2022-03-25T01:23:37.631 ++C Sync\sw/trunk ++D 2022-03-25T20:39:09.123 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@@ -555,10 -552,9 +555,10 @@@ F src/printf.c 05d8dfd2018bc4fc3ddb8b37 F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c F src/resolve.c ea935b87d6fb36c78b70cdc7b28561dc8f33f2ef37048389549c7b5ef9b0ba5e F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 - F src/select.c 4890a3cfee0bc60ff231c3a44db37968859ab0be156983dbcc0c096109832cdd - F src/shell.c.in dd69e8a81fd8fc8a0fc1c470fa5c028ec01b3d106afc26cfa84399e534405e73 + F src/select.c c366c05e48e836ea04f8ecefb9c1225745dc250c3f01bdb39e9cbb0dc25e3610 -F src/shell.c.in 24d1082d275db252379629c2b3694dbfa0f8886530b0f6f9896d52e456afe510 ++F src/shell.c.in 0c3dd6f397b01770c8c5366574b5fbccb59bfac88a09ecfc4f92a2ce5658d9ab x +F src/shext_linkage.h 113e2e22378002999e297747b5651d3550cdd5b573a1977d274be5a7390d00c0 - F src/sqlite.h.in 5845213799feca09cd69d18ff841a85fe0df31021f46aaa1797e703e80dc1d70 + F src/sqlite.h.in 2a35f62185eb5e7ecc64a2f68442b538ce9be74f80f28a00abc24837edcf1c17 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h f49e28c25bd941e79794db5415fdf7b202deb3bc072ed6f1ed273d578703684e F src/sqliteInt.h 2ce7d868630ccd70ffd4b15d46b59ccf7daf89198993b62ed6e4a165d3511280 @@@ -1950,8 -1945,8 +1951,8 @@@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a9 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 - P 29fd246fb2605a2731dd6327a354daedd8182225a97234939c79fc66e1e9e83e - R 20ee2f610aa91298bb1bd5f8f19a382e -P a7abb725c7c783331e73837597917e25e5ff692aac8ec97a0a8c0e30db62d68b -R 7df72da8b94385b7c430ac7b857fb766 -U drh -Z f44c9fa4c137748740188f91b08447a0 ++P 57501e7fbda84a22cd8ab15beff8af4766a3851aa3c6c705484fe1eab808caa9 1a6328f2a5b4973094e5f85787145d652119822c86ec01a61f3f985c9d2903f2 ++R c21a00b1e4b1c40d66b41b4b18ac61fa +U larrybr - Z 795c799e5971cf2292cc8435f8c2bc8b ++Z a8882d9824db5961956d1a3e112d82e7 # Remove this line to create a well-formed Fossil manifest. diff --cc manifest.uuid index c588a02a76,2c894bd6bf..c06e0b1470 --- a/manifest.uuid +++ b/manifest.uuid @@@ -1,1 -1,1 +1,1 @@@ - 57501e7fbda84a22cd8ab15beff8af4766a3851aa3c6c705484fe1eab808caa9 -1a6328f2a5b4973094e5f85787145d652119822c86ec01a61f3f985c9d2903f2 ++8402e5e78a83af3ba015bb6ab173350c1b73e04ce37ef933f9a847a7cf5e6157 diff --cc src/shell.c.in index 97f80dac85,e5c546bd74..71d1fdcb2d mode 100644,100644..100755 --- a/src/shell.c.in +++ b/src/shell.c.in @@@ -5061,48 -4983,50 +5061,50 @@@ static void open_db(ShellExState *psx, exit(1); } #ifndef SQLITE_OMIT_LOAD_EXTENSION - sqlite3_enable_load_extension(p->db, 1); -#endif - sqlite3_fileio_init(p->db, 0, 0); - sqlite3_shathree_init(p->db, 0, 0); - sqlite3_completion_init(p->db, 0, 0); - sqlite3_uint_init(p->db, 0, 0); - sqlite3_decimal_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_fileio_init(globalDb, 0, 0); + sqlite3_shathree_init(globalDb, 0, 0); + sqlite3_completion_init(globalDb, 0, 0); + sqlite3_uint_init(globalDb, 0, 0); + sqlite3_decimal_init(globalDb, 0, 0); + sqlite3_regexp_init(globalDb, 0, 0); + sqlite3_ieee_init(globalDb, 0, 0); + sqlite3_series_init(globalDb, 0, 0); #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) - sqlite3_dbdata_init(p->db, 0, 0); + sqlite3_dbdata_init(globalDb, 0, 0); #endif #ifdef SQLITE_HAVE_ZLIB - sqlite3_zipfile_init(globalDb, 0, 0); - sqlite3_sqlar_init(globalDb, 0, 0); - 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 - sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0, + sqlite3_create_function(globalDb, "shell_add_schema", 3, SQLITE_UTF8, 0, shellAddSchemaName, 0, 0); - sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0, + sqlite3_create_function(globalDb, "shell_module_schema", 1, SQLITE_UTF8, 0, shellModuleSchema, 0, 0); - sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p, + sqlite3_create_function(globalDb, "shell_putsnl", 1, SQLITE_UTF8, psx, shellPutsFunc, 0, 0); - sqlite3_create_function(p->db, "shell_escape_crnl", 1, SQLITE_UTF8, 0, + sqlite3_create_function(globalDb, "shell_escape_crnl", 1, SQLITE_UTF8, 0, shellEscapeCrnl, 0, 0); - sqlite3_create_function(p->db, "shell_int32", 2, SQLITE_UTF8, 0, + sqlite3_create_function(globalDb, "shell_int32", 2, SQLITE_UTF8, 0, shellInt32, 0, 0); - sqlite3_create_function(p->db, "shell_idquote", 1, SQLITE_UTF8, 0, + sqlite3_create_function(globalDb, "shell_idquote", 1, SQLITE_UTF8, 0, shellIdQuote, 0, 0); - sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0, + sqlite3_create_function(globalDb, "usleep",1,SQLITE_UTF8, 0, shellUSleepFunc, 0, 0); #ifndef SQLITE_NOHAVE_SYSTEM - sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0, + sqlite3_create_function(globalDb, "edit", 1, SQLITE_UTF8, 0, editFunc, 0, 0); - sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0, + sqlite3_create_function(globalDb, "edit", 2, SQLITE_UTF8, 0, editFunc, 0, 0); #endif - if( p->openMode==SHELL_OPEN_ZIPFILE ){ - char *zSql = sqlite3_mprintf( - "CREATE VIRTUAL TABLE zip USING zipfile(%Q);", zDbFilename); + if( psi->openMode==SHELL_OPEN_ZIPFILE ){ + char *zSql = smprintf("CREATE VIRTUAL TABLE zip USING zipfile(%Q);", + zDbFilename); shell_check_oom(zSql); - sqlite3_exec(p->db, zSql, 0, 0, 0); + sqlite3_exec(DBX(psx), zSql, 0, 0, 0); sqlite3_free(zSql); } #ifndef SQLITE_OMIT_DESERIALIZE @@@ -7722,3944 -8043,1457 +7724,3946 @@@ FROM ( } } -/* -** 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]; - -#ifndef SQLITE_OMIT_VIRTUALTABLE - if( p->expert.pExpert ){ - expertFinish(p, 1, 0); - } -#endif - - /* Parse the input line into tokens. - */ - while( zLine[h] && nArgixExtPending 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 meta-command, to be called during extension load/init. */ +static int register_meta_command(ShellExState *p, + ExtensionId eid, MetaCommand *pMC){ + ShellInState *psi = ISS(p); + ShExtInfo *psei = pending_ext_info(psi); + const char *zSql + = "INSERT INTO ShellCommands (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->numMetaCommands; + 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->ppMetaCommands + = sqlite3_realloc(psei->ppMetaCommands, (nc+1)*sizeof(MetaCommand *)); + shell_check_oom(psei->ppMetaCommands); + 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->ppMetaCommands[nc++] = pMC; + psei->numMetaCommands = nc; + return SQLITE_OK; }else{ - azArg[nArg++] = &zLine[h]; - while( zLine[h] && !IsSpace(zLine[h]) ){ h++; } - if( zLine[h] ) zLine[h++] = 0; - resolve_backslashes(azArg[nArg-1]); + psei->ppMetaCommands[nc] = 0; } } - azArg[nArg] = 0; + return SQLITE_ERROR; +} - /* Process the input line. - */ - if( nArg==0 ) return 0; /* no tokens, no error */ - n = strlen30(azArg[0]); - c = azArg[0][0]; - clearTempFile(p); +/* Register an output data display (or other disposition) mode */ +static int register_out_mode(ShellExState *p, + ExtensionId eid, OutModeHandler *pOMH){ + return SQLITE_ERROR; +} -#ifndef SQLITE_OMIT_AUTHORIZATION - if( c=='a' && 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 +/* Register an import variation from (various sources) for .import */ +static int register_importer(ShellExState *p, + ExtensionId eid, ImportHandler *pIH){ + return SQLITE_ERROR; +} -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) - if( c=='a' && 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 +static int hook_scripting(ShellExState *p, ExtensionId eid, ScriptHooks *pSH){ + ShellInState *psi = ISS(p); + if( psi->scriptXid!=0 ){ + /* Scripting support already provided. Only one provider is allowed. */ + return SQLITE_BUSY; + } + if( eid==0 || pSH==0 || psi->ixExtPending==0 + || pSH->scriptIsComplete==0 || pSH->runScript==0 ){ + return SQLITE_MISUSE; + } + psi->scripting = *pSH; + psi->scriptXid = eid; + return SQLITE_OK; +} - if( (c=='b' && n>=3 && strncmp(azArg[0], "backup", n)==0) - || (c=='s' && n>=3 && 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; jpSubscriptions; + 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 || eid==eid) + && (eid!=0 ||eventHandler!=0 ||/* for shell use */ pvUserData==p ) ){ + int nLeft = pesLim - pesLim; + assert(pes->eventHandler!=0); + pes->eventHandler(pes->pvUserData, NK_Unsubscribe, p); + if( nLeft>1 ) memmove(pes, pes+1, (nLeft-1)*sizeof(*pes)); + --pesLim; + --psi->numSubscriptions; }else{ - raw_printf(stderr, "Usage: .backup ?DB? ?OPTIONS? FILENAME\n"); - return 1; + ++pes; } } - 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); - if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", zDestFile); - close_db(pDest); - return 1; - } - 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 - - if( c=='b' && n>=3 && 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; + if( psi->numSubscriptions==0 ){ + sqlite3_free(psi->pSubscriptions); + psi->pSubscriptions = 0; } - }else - - if( c=='b' && n>=3 && strncmp(azArg[0], "binary", n)==0 ){ - if( nArg==2 ){ - if( booleanValue(azArg[1]) ){ - setBinaryMode(p->out, 1); - }else{ - setTextMode(p->out, 1); + 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; } - }else{ - raw_printf(stderr, "Usage: .binary on|off\n"); - rc = 1; } - }else - - /* The undocumented ".breakpoint" command causes a call to the no-op - ** routine named test_breakpoint(). - */ - if( c=='b' && n>=3 && strncmp(azArg[0], "breakpoint", n)==0 ){ - test_breakpoint(); - }else - - if( c=='c' && 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); -#else - rc = chdir(azArg[1]); + assert(pes==pesLim); + pes = sqlite3_realloc(pes, (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; + } +} #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; - } - }else - if( c=='c' && n>=3 && strncmp(azArg[0], "changes", n)==0 ){ - if( nArg==2 ){ - setOrClearFlag(p, SHFLG_CountChanges, azArg[1]); - }else{ - raw_printf(stderr, "Usage: .changes on|off\n"); - rc = 1; - } - }else +static FILE *currentOutputFile(ShellExState *p){ + return ISS(p)->out; +} - /* 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 && 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 ){ - raw_printf(stderr, "Error: cannot read 'testcase-out.txt'\n"); - 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; - }else{ - utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase); - p->nCheck++; - } - sqlite3_free(zRes); - }else +static struct InSource *currentInputSource(ShellExState *p){ + return ISS(p)->pInSource; +} - if( c=='c' && 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; - } - }else +static int nowInteractive(ShellExState *p){ + return INSOURCE_IS_INTERACTIVE(ISS(p)->pInSource); +} - if( c=='c' && strncmp(azArg[0], "connection", n)==0 ){ - if( nArg==1 ){ - /* List available connections */ - int i; - for(i=0; iaAuxDb); 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 && iaAuxDb) ){ - p->pAuxDb->db = p->db; - p->pAuxDb = &p->aAuxDb[i]; - globalDb = p->db = p->pAuxDb->db; - p->pAuxDb->db = 0; - } - }else if( nArg==3 && 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{ - raw_printf(stderr, "Usage: .connection [close] [CONNECTION-NUMBER]\n"); - rc = 1; - } - }else +static const char *shellInvokedAs(void){ + return Argv0; +} - if( c=='d' && n>1 && 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)); - 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++; - } - } - sqlite3_finalize(pStmt); - for(i=0; idb, 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 +static const char *shellStartupDir(void){ + return startupDir; +} - if( c=='d' && n>=3 && 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 }, - { "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; ii1 && 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 +static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths); +static MetaCommand * findMetaCommand(const char *, ShellExState *, int *); +static DotCmdRC runMetaCommand(MetaCommand*, char *[], int na, ShellExState*); - if( c=='d' && n>=3 && strncmp(azArg[0], "dbinfo", n)==0 ){ - rc = shell_dbinfo_command(p, nArg, azArg); - }else +static ExtensionHelpers extHelpers = { + 11, + { + failIfSafeMode, + currentOutputFile, + currentInputSource, + strLineGet, + findMetaCommand, + runMetaCommand, + setColumnWidths, + nowInteractive, + shellInvokedAs, + shellStartupDir, + sqlite3_enable_load_extension, + 0 + } +}; -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) - if( c=='r' && strncmp(azArg[0], "recover", n)==0 ){ - open_db(p, 0); - rc = recoverDatabaseCmd(p, nArg, azArg); - }else -#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */ +static ShellExtensionAPI shellExtAPI = { + &extHelpers, 5, { + register_meta_command, + register_out_mode, + register_importer, + hook_scripting, + subscribe_events, + 0 + } +}; - if( c=='d' && 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; i0 ){ + 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); +} + +#ifndef SHELL_DB_FILE +# define SHELL_DB_STORE ":memory:" #else - ShellSetFlag(p, SHFLG_PreserveRowid); +# define SHELL_DB_STORE SHELL_STRINGIFY(SHELL_DB_FILE) #endif - }else - if( strcmp(z,"newlines")==0 ){ - ShellSetFlag(p, SHFLG_Newlines); - }else - if( strcmp(z,"data-only")==0 ){ - ShellSetFlag(p, SHFLG_DumpDataOnly); - }else - if( 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] - ); - - if( zLike ){ - zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr); - }else{ - zLike = zExpr; - } - } - } - open_db(p, 0); +/* 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, rc, rc1, rc2; + char *zErr = 0; + const char *zSql; + ShExtInfo sei = {0}; + /* Consider: Store these dynamic arrays in the DB as indexed-into blobs. */ + assert(psx->dbShell==0 && psi->numExtLoaded==0 && psi->pShxLoaded==0); + psi->pShxLoaded = (ShExtInfo *)sqlite3_malloc(2*sizeof(ShExtInfo)); + sei.ppMetaCommands + = (MetaCommand **)sqlite3_malloc((numCommands+2)*sizeof(MetaCommand *)); + sei.ppOutModeHandlers + = (OutModeHandler **)sqlite3_malloc(2*sizeof(OutModeHandler *)); + sei.ppImportHandlers + = (ImportHandler **)sqlite3_malloc(2*sizeof(ImportHandler *)); + if( sei.ppMetaCommands==0||sei.ppOutModeHandlers==0||sei.ppImportHandlers==0 + || psi->pShxLoaded==0 ){ + shell_out_of_memory(); + } + sei.numOutModeHandlers = 0; + sei.numImportHandlers = 0; + for( ic=0; icpShxLoaded[psi->numExtLoaded++] = sei; + rc = sqlite3_open(SHELL_DB_STORE, &psx->dbShell); + if( rc!=SQLITE_OK ) return 1; +#ifdef SHELL_DB_FILE + sqlite3_exec(psx->dbShell, "DROP TABLE IF EXISTS ShellCommands", 0,0,0); + sqlite3_exec(psx->dbShell, "DROP VIEW IF EXISTS ActiveCommands", 0,0,0); +#endif + rc1 = sqlite3_exec(psx->dbShell, "CREATE TABLE ShellCommands(" + "name TEXT, extIx INT, cmdIx INT," + "PRIMARY KEY(extIx,cmdIx)) WITHOUT ROWID", + 0, 0, &zErr); + rc2 = sqlite3_exec(psx->dbShell, "CREATE VIEW ActiveCommands AS SELECT " + "s.name AS name, max(s.extIx) AS extIx, s.cmdIx AS cmdIx " + "FROM ShellCommands s GROUP BY name,extIx", + 0, 0, &zErr); + if( rc1!=SQLITE_OK || rc2!=SQLITE_OK || zErr!=0 ){ + utf8_printf(STD_ERR, "Shell DB init failure, %s\n", zErr? zErr : ""); + return 1; + } + zSql = "INSERT INTO ShellCommands (name, extIx, cmdIx) VALUES(?, 0, ?)"; + rc1 = sqlite3_prepare_v2(psx->dbShell, zSql, -1, &pStmt, 0); + rc2 = sqlite3_exec(psx->dbShell, "BEGIN TRANSACTION", 0, 0, &zErr); + if( rc1!=SQLITE_OK || rc2!=SQLITE_OK ) return 1; + assert(sei.numMetaCommands>0); + for( ic=0; icpMethods->name(pmc); + sqlite3_reset(pStmt); + sqlite3_bind_text(pStmt, 1, zName, -1, 0); + sqlite3_bind_int(pStmt, 2, ic); + rc = sqlite3_step(pStmt); + if( rc!=SQLITE_DONE ){ + sqlite3_exec(psx->dbShell, "ABORT", 0, 0, 0); + break; + } + } + 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); + return SQLITE_OK; +} - 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); +/* Call one loaded extension's destructors, in reverse order + * of their objects' creation, then free the tracking dyna-arrays. + */ +static void free_one_shext_tracking(ShExtInfo *psei){ + int j; + if( psei->ppMetaCommands!=0 ){ + for( j=psei->numMetaCommands; j>0; --j ){ + MetaCommand *pmc = psei->ppMetaCommands[j-1]; + if( pmc->pMethods->destruct!=0 ) pmc->pMethods->destruct(pmc); } - sqlite3_free(zLike); - if( p->writableSchema ){ - raw_printf(p->out, "PRAGMA writable_schema=OFF;\n"); - p->writableSchema = 0; + sqlite3_free(psei->ppMetaCommands); + } + if( psei->ppOutModeHandlers!=0 ){ + for( j=psei->numOutModeHandlers; j>0; --j ){ + OutModeHandler *pomh = psei->ppOutModeHandlers[j-1]; + if( pomh->pMethods->destruct!=0 ) pomh->pMethods->destruct(pomh); } - 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"); + sqlite3_free(psei->ppOutModeHandlers); + } + 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); } - p->showHeader = savedShowHeader; - p->shellFlgs = savedShellFlags; - }else + sqlite3_free(psei->ppImportHandlers); + } + if( psei->extDtor!=0 ){ + psei->extDtor(psei->pvExtObj); + } +} - if( c=='e' && 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; +/* Call all existent loaded extension destructors, in reverse order + * of their objects' creation, 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); + if( psi->scriptXid!=0 && psi->scriptXid==psei->extId ){ + memset(&psi->scripting, 0, sizeof(ScriptHooks)); + psi->scriptXid = 0; + } } - }else + sqlite3_free(psi->pShxLoaded); + psi->pShxLoaded = 0; + psi->numExtLoaded = 0; + } +} - if( c=='e' && 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( strcmp(azArg[1],"full")==0 ){ - p->autoEQP = AUTOEQP_full; - }else if( strcmp(azArg[1],"trigger")==0 ){ - p->autoEQP = AUTOEQP_trigger; -#ifdef SQLITE_DEBUG - }else if( strcmp(azArg[1],"test")==0 ){ - p->autoEQP = AUTOEQP_on; - p->autoEQPtest = 1; - }else if( 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; +static MetaCommand *command_by_index(ShellInState *psi, int extIx, int cmdIx){ + if( extIx>=0 && extIxnumExtLoaded ){ + ShExtInfo *psei = & psi->pShxLoaded[extIx]; + if( cmdIx>=0 && cmdIxnumMetaCommands ){ + return psei->ppMetaCommands[cmdIx]; } - }else + } + return 0; +} - if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){ - if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc); - rc = 2; - }else +static int load_shell_extension(ShellExState *psx, const char *zFile, + const char *zProc, char **pzErr){ + ShellExtensionLink shxLink = { + sizeof(ShellExtensionLink), + &shellExtAPI, + psx, /* pSXS */ + 0, /* zErrMsg */ + 0, /* ExtensionId */ + 0, /* Extension destructor */ + 0 /* Extension data ref */ + }; //extDtor(pvExtObj) + ShellInState *psi = ISS(psx); + /* save script hooking state for possible fallback if load fails */ + ScriptHooks shSave = psi->scripting; + ExtensionId siSave = psi->scriptXid; + int rc; - /* The ".explain" command is automatic now. It is largely pointless. It - ** retained purely for backwards compatibility */ - if( c=='e' && strncmp(azArg[0], "explain", n)==0 ){ - int val = 1; - if( nArg>=2 ){ - if( 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; + if( pzErr ) *pzErr = 0; + if( psx->dbShell==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); + --psi->numExtLoaded; } - }else - -#ifndef SQLITE_OMIT_VIRTUALTABLE - if( c=='e' && 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; - }else{ - open_db(p, 0); - expertDotCommand(p, azArg, nArg); + /* And unhook any scripting linkage it might have setup. */ + psi->scripting = shSave; + psi->scriptXid = siSave; + } + psi->ixExtPending = 0; + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_MISUSE && pzErr!=0 ){ + *pzErr = sqlite3_mprintf("extension id mismatch %z\n", *pzErr); } - }else + rc = SQLITE_ERROR; + } + return rc; +} #endif - if( c=='f' && 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; - - open_db(p, 0); - zCmd = nArg>=2 ? azArg[1] : "help"; +/* Meta-command implementation functions are defined in this section. +COMMENT Define meta-commands and provide for their dispatch and .help text. +COMMENT These should be kept in command name order for coding convenience +COMMENT except where meta-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_METACMD_INIT further on.) +** All dispatchable meta-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 }, + METACMD_INIT=META_CMD_INFO(${cmd}, $arg1,$arg2,$arg3, , ), + 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; iaout, "%s%s", azArg[ia], (ia==nArg-1)? "|\n" : "|"); + return DCR_Ok; +} + +CONDITION_COMMAND(archive !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)); +/***************** + * 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); +} - if( zCmd[0]=='-' - && (strcmp(zCmd,"--schema")==0 || strcmp(zCmd,"-schema")==0) - && nArg>=4 - ){ - zSchema = azArg[2]; - for(i=3; ibSafeModeFuture ){ + sqlite3_set_authorizer(DBX(p), safeModeAuth, p); + }else{ + sqlite3_set_authorizer(DBX(p), 0, 0); + } + return DCR_Ok; +} - /* The argument can optionally begin with "-" or "--" */ - if( zCmd[0]=='-' && zCmd[1] ){ - zCmd++; - if( zCmd[0]=='-' && zCmd[1] ) zCmd++; +/***************** + * The .backup and .save commands (aliases for each other) + * These defer to writeDb in the dispatch table, so are not here. + */ +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; +} + +/***************** + * 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 undocumented ".breakpoint" command causes a call +** to the no-op routine named test_breakpoint(). +*/ +DISPATCHABLE_COMMAND( breakpoint 3 1 1 ){ + test_breakpoint(); + return DCR_Ok; +} + +/***************** + * 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; + int rc = 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 = shellMPrintf(&rc, "Error: cannot read 'testcase-out.txt'"); + rv = DCR_Return; + }else if( testcase_glob(azArg[1],zRes)==0 ){ + *pzErr = + shellMPrintf(&rc, + "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 (rc==SQLITE_NOMEM)? 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; iaAuxDb); 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 && iaAuxDb) ){ + psi->pAuxDb->db = DBI(psi); + psi->pAuxDb = &psi->aAuxDb[i]; +#if SHELL_DYNAMIC_EXTENSION + if( DBI(psi)!=0 ) notify_subscribers(psi, NK_DbUserVanishing); +#endif + globalDb = DBI(psi) = psi->pAuxDb->db; +#if SHELL_DYNAMIC_EXTENSION + if( DBI(psi)!=0 ) notify_subscribers(psi, NK_DbUserAppeared); +#endif + psi->pAuxDb->db = 0; + } + }else if( nArg==3 && 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; +} - /* --help lists all file-controls */ - if( strcmp(zCmd,"help")==0 ){ - utf8_printf(p->out, "Available file-controls:\n"); - for(i=0; iout, " .filectrl %s %s\n", - aCtrl[i].zCtrlName, aCtrl[i].zUsage); - } - rc = 1; - goto meta_command_exit; +/***************** + * 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; iout, "%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 }, + { "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; ii1 && 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); +} - /* convert filectrl text option to value. allow any unique prefix - ** of the option name, or a numerical value. */ - n2 = strlen30(zCmd); - for(i=0; ishowHeader; + int savedShellFlags = psi->shellFlgs; + ShellClearFlag(p, + SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo + |SHFLG_DumpDataOnly|SHFLG_DumpNoSys); + for(i=1; idb, 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); - } - isOk = 2; - break; - } - case SQLITE_FCNTL_RESERVE_BYTES: { - int x; - if( nArg>=3 ){ - x = atoi(azArg[2]); - sqlite3_file_control(p->db, zSchema, filectrl, &x); - } - x = -1; - sqlite3_file_control(p->db, zSchema, filectrl, &x); - utf8_printf(p->out,"%d\n", x); - isOk = 2; - break; - } + /* 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; } } - 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); - } - }else + } - if( c=='f' && 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"); - } - }else + open_db(p, 0); - if( c=='h' && strncmp(azArg[0], "headers", n)==0 ){ - if( nArg==2 ){ - p->showHeader = booleanValue(azArg[1]); - p->shellFlgs |= SHFLG_HeaderSet; + 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( strcmp(azArg[1],"full")==0 ){ + psi->autoEQP = AUTOEQP_full; + }else if( strcmp(azArg[1],"trigger")==0 ){ + psi->autoEQP = AUTOEQP_trigger; +#ifdef SQLITE_DEBUG + }else if( strcmp(azArg[1],"test")==0 ){ + psi->autoEQP = AUTOEQP_on; + psi->autoEQPtest = 1; + }else if( 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{ - raw_printf(stderr, "Usage: .headers on|off\n"); - rc = 1; + psi->autoEQP = (u8)booleanValue(azArg[1]); } - }else + }else{ + return DCR_ArgWrong; + } + return DCR_Ok; +} - if( c=='h' && 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]); +/***************** + * 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 Quit processing this input or script", +]; +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=2 && 0==strncmp(z, "-verbose", n) ){ + psi->expert.bVerbose = 1; + } + else if( n>=2 && 0==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{ - showHelp(p->out, 0); } - }else - - if( c=='i' && 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)); - sCtx.z = sqlite3_malloc64(120); - if( sCtx.z==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); + else{ + return DCR_Unknown|i; } - if( p->mode==MODE_Ascii ){ - xRead = ascii_read_one_field; + } + + 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( strcmp(azArg[1],"auto")==0 ){ + val = 99; }else{ - xRead = csv_read_one_field; + val = booleanValue(azArg[1]); } - for(i=1; iout, "ERROR: extra argument: \"%s\". Usage:\n", z); - showHelp(p->out, "import"); - rc = 1; - goto meta_command_exit; - } - }else if( strcmp(z,"-v")==0 ){ - eVerbose++; - }else if( strcmp(z,"-schema")==0 && imode!=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. + */ +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\")", +]; +/* 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; - int bTxtMode = 0; ++ u8 bTxtMode = 0; ++ u8 bPutBOM = 0; + int i; - int bBOM = 0; ++ static unsigned const char zBOM[4] = {0xef,0xbb,0xbf,0}; + if( psi->bSafeMode ) return DCR_AbortError; + for(i=1; iout, "ERROR: unknown option: \"%s\". Usage:\n", z); - showHelp(p->out, "import"); - rc = 1; - goto meta_command_exit; + 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+1out, "ERROR: missing %s argument. Usage:\n", - zFile==0 ? "FILE" : "TABLE"); - showHelp(p->out, "import"); - rc = 1; - 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"); - rc = 1; - goto meta_command_exit; - } - if( nSep>1 ){ - raw_printf(stderr, - "Error: multi-character column separators not allowed" - " for import\n"); - rc = 1; - goto meta_command_exit; - } - nSep = strlen30(p->rowSeparator); - if( nSep==0 ){ - raw_printf(stderr, - "Error: non-null row separator required for import\n"); - rc = 1; - goto meta_command_exit; - } - if( nSep==2 && p->mode==MODE_Csv && 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"); - rc = 1; - goto meta_command_exit; - } - sCtx.cColSep = p->colSeparator[0]; - sCtx.cRowSep = p->rowSeparator[0]; + } + 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; } - sCtx.zFile = zFile; - sCtx.nLine = 1; - if( sCtx.zFile[0]=='|' ){ + sqlite3_free(zFile); + zFile = smprintf("%s", psi->zTempFile); + } +#endif /* SQLITE_NOHAVE_SYSTEM */ + shell_check_oom(zFile); + if( zFile[0]=='|' ){ #ifdef SQLITE_OMIT_POPEN - raw_printf(stderr, "Error: pipes are not supported in this OS\n"); - rc = 1; - goto meta_command_exit; + *pzErr = smprintf("pipes are not supported in this OS\n"); + rc = 1; + psi->out = STD_OUT; #else - sCtx.in = popen(sCtx.zFile+1, "r"); - sCtx.zFile = ""; - 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); + 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; - import_cleanup(&sCtx); - 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"); - } - /* 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); }else{ - if( bBOM ) fprintf(psi->out,"\357\273\277"); - zFullTabName = sqlite3_mprintf("\"%w\"", zTable); - } - zSql = sqlite3_mprintf("SELECT * FROM %s", zFullTabName); - if( zSql==0 || zFullTabName==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); ++ if( bPutBOM ) fwrite(zBOM, 1, 3, psi->out); + sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile); } - 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); - rc = 1; - goto meta_command_exit; - } - zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs); - if( eVerbose>=1 ){ - utf8_printf(p->out, "%s\n", zCreate); +#endif + }else{ + psi->out = output_file_open(zFile, bTxtMode); + if( psi->out==0 ){ + if( strcmp(zFile,"off")!=0 ){ + *pzErr = smprintf("cannot write to \"%s\"\n", zFile); } - 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; - } - sqlite3_free(zCreate); - zCreate = 0; - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + psi->out = STD_OUT; + rc = 1; + } else { - if( bBOM ) fprintf(psi->out,"\357\273\277"); ++ if( bPutBOM ) fwrite(zBOM, 1, 3, psi->out); + sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile); } - if( rc ){ - if (pStmt) sqlite3_finalize(pStmt); - utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db)); - goto import_fail; + } + sqlite3_free(zFile); + return DCR_Ok|rc; +} +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]=='-' + && (strcmp(zCmd,"--schema")==0 || strcmp(zCmd,"-schema")==0) + && nArg>=4 + ){ + zSchema = azArg[2]; + for(i=3; iout, "Available file-controls:\n"); + for(i=0; iout, " .filectrl %s %s\n", + aCtrl[i].zCtrlName, aCtrl[i].zUsage); } - 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(); + 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=2 ){ - utf8_printf(p->out, "Insert using: %s\n", zSql); + 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; } - 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; + 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; } - 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; imode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; - sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); - if( iout, "%s\n", z); + sqlite3_free(z); } - 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++; - }else{ - sCtx.nRow++; - } + 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); } - }while( sCtx.cTerm!=EOF ); + 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; + } +} - import_cleanup(&sCtx); +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( 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); + } + 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 && strcmp(z, zHelpAll)==0 && strcmp(azArg[2], zHelpAll)==0 ){ + /* Show the undocumented command help */ + zPat = zHelpAll; + }else if( strcmp(z,"-a")==0 || optionMatch(z, "all") ){ + zPat = ""; + }else{ + zPat = z; } - }else + } + if( showHelp(out, zPat, p)==0 && nArg>1 ){ + utf8_printf(out, "Nothing matches '%s'\n", azArg[1]); + } + /* Help pleas never fail! */ + return DCR_Ok; +} -#ifndef SQLITE_UNTESTABLE - if( c=='i' && 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( !(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; +/***************** + * 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( 0==(sCtx.z = sqlite3_malloc64(120)) ){ + shell_out_of_memory(); + } + + if( psi->mode==MODE_Ascii ){ + xRead = ascii_read_one_field; + }else{ + xRead = csv_read_one_field; + } + for(i=1; idb, "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); + } + 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 + && 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 = psi->colSeparator[0]; + sCtx.cRowSep = 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 = ""; + 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); + import_cleanup(&sCtx); + return DCR_Error; + } + /* 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 ){ - char *zCreate = smprintf("CREATE TABLE %s", zFullTabName); ++ 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; } - 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); - 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); - } + zCreate = smprintf("%z%z\n", zCreate, zColDefs); + if( eVerbose>=1 ){ + utf8_printf(out, "%s\n", zCreate); } - 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; + 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; } - 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" - ); + 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=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; imode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; + sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); + 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{ - sqlite3IoTrace = iotracePrintf; + sCtx.nRow++; } } - }else -#endif + }while( sCtx.cTerm!=EOF ); - if( c=='l' && n>=5 && 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; idb, 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; i0 ){ + 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( i75){ + zSep = "\n"; + nCol = 0; } + memcpy(kwBuf, zKW, szKW); + kwBuf[szKW] = 0; + utf8_printf(out, "%s%s", kwBuf, zSep); } } - 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; - } - if( nArg==3 ){ - sqlite3_limit(p->db, aLimit[iLimit].limitCode, - (int)integerValue(azArg[2])); - } - printf("%20s %d\n", aLimit[iLimit].zLimitName, - sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1)); } - }else - - if( c=='l' && n>2 && strncmp(azArg[0], "lint", n)==0 ){ - open_db(p, 0); - lintDotCommand(p, azArg, nArg); - }else + 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; +} -#ifndef SQLITE_OMIT_LOAD_EXTENSION - if( c=='l' && strncmp(azArg[0], "load", n)==0 ){ - const char *zFile, *zProc; - char *zErrMsg = 0; - failIfSafeMode(p, "cannot run .load in safe mode"); - if( nArg<2 ){ - raw_printf(stderr, "Usage: .load FILE ?ENTRYPOINT?\n"); - rc = 1; - goto meta_command_exit; +/***************** + * The .imposter, .iotrace, limit, lint, .load and .log commands + */ +CONDITION_COMMAND( imposter !defined(SQLITE_OMIT_TEST_CONTROL) ); +CONDITION_COMMAND( iotrace defined(SQLITE_ENABLE_IOTRACE) ); +CONDITION_COMMAND( load !defined(SQLITE_OMIT_LOAD_EXTENSION) ); +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", + ".load FILE ?ENTRY? Load an extension library", +#if SHELL_DYNAMIC_EXTENSION + " Option -shellext will load the library as a shell extension.", +#endif + ".log FILE|off Turn logging on or off. FILE can be stderr/stdout", +]; +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( !(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; + } } - 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; + if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){ + lenPK = (int)strlen(zCollist); } - }else -#endif - - if( c=='l' && strncmp(azArg[0], "log", n)==0 ){ - failIfSafeMode(p, "cannot run .log in safe mode"); - if( nArg!=2 ){ - raw_printf(stderr, "Usage: .log FILENAME\n"); - rc = 1; + if( zCollist==0 ){ + zCollist = smprintf("\"%w\"", zCol); }else{ - const char *zFile = azArg[1]; - output_file_close(p->pLog); - p->pLog = output_file_open(zFile, 0); + zCollist = smprintf("%z,\"%w\"", zCollist, zCol); } - }else - - if( c=='m' && strncmp(azArg[0], "mode", n)==0 ){ - const char *zMode = 0; - const char *zTabname = 0; - int i, n2; - ColModeOpts cmOpts = ColModeOpts_default; - for(i=1; i3 ){ + return DCR_TooMany; + }else{ + int iLimit = -1; + n2 = strlen30(azArg[1]); + for(i=0; iout; /* 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; i1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){ + bVerbose = 1; + } + else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){ + bGroupByParent = 1; + zIndent = " "; + } + else{ + raw_printf(STD_ERR, "Usage: %s %s ?-verbose? ?-groupbyparent?\n", + azArg[0], azArg[1] + ); + return DCR_Unknown|i; + } + } + + /* Register the fkey_collate_clause() SQL function */ + rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8, + 0, shellFkeyCollateClause, 0, 0 + ); + + if( rc==SQLITE_OK ){ + rc = sqlite3_prepare_v2(db, zSql, -1, &pSql, 0); + } + if( rc==SQLITE_OK ){ + sqlite3_bind_int(pSql, 1, bGroupByParent); + } + + 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); + + 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( 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); + } + + 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); + } + } + } + sqlite3_free(zPrev); + + if( rc!=SQLITE_OK ){ + *pzErr = smprintf("%s\n", sqlite3_errmsg(db)); + } + + rc2 = sqlite3_finalize(pSql); + if( rc==SQLITE_OK && rc2!=SQLITE_OK ){ + rc = rc2; + *pzErr = smprintf("%s\n", sqlite3_errmsg(db)); + } + }else{ + *pzErr = smprintf("%s\n", sqlite3_errmsg(db)); + } + + return DCR_Ok|(rc!=0); +} + +DISPATCHABLE_COMMAND( load ? 2 4 ){ + const char *zFile = 0, *zProc = 0; + char *zErrMsg = 0; + int ai = 1, rc; +#if SHELL_DYNAMIC_EXTENSION + u8 bLoadShellExt = 0; +#endif + if( ISS(p)->bSafeMode ) return DCR_AbortError; + while( aibSafeMode ) return DCR_AbortError; + output_file_close(ISS(p)->pLog); + 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; + } + 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; +} + +/***************** + * 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 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 --width 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; aixout; + const char *zMode; + int nms; + i = psi->mode; + assert(i>=0 && imode) ){ + 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{ + effectMode(psi, foundMode, setMode); + if( MODE_IS_COLUMNAR(setMode) ) psi->cmOpts = cmOpts; + else 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; +} + +/***************** + * The .open, .nonce and .nullvalue commands + */ +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; iNamepAuxDb->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); + if( psi->bSafeMode + && psi->openMode!=SHELL_OPEN_HEXDB + && zFN + && strcmp(zFN,":memory:")!=0 + ){ + *pzErr = smprintf("cannot open database files in safe mode"); + return DCR_AbortError; + } + if( zFN ){ + zNewFilename = smprintf("%s", zFN); + shell_check_oom(zNewFilename); + }else{ + 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{ + psi->pAuxDb->zFreeOnClose = zNewFilename; + } + } + if( DBX(p)==0 ){ + /* As a fall-back open a TEMP database */ + psi->pAuxDb->zDbFilename = 0; + open_db(p, 0); + } + return DCR_Ok|(rc!=0); +} + +DISPATCHABLE_COMMAND( nonce ? 2 2 ){ + ShellInState *psi = ISS(p); + if( psi->zNonce==0 || 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 meta-command after this. */ + psi->bSafeModeFuture = 2; + return DCR_Ok; +} + +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; +} + +/* Helper functions for .parameter command + */ + +struct param_row { char * value; int uses; int hits; }; + +static int param_find_callback(void *pData, int nc, char **pV, char **pC){ + assert(nc>=1); + assert(strcmp(pC[0],"value")==0); + struct param_row *pParam = (struct param_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 + ) ){ + zErr = "Malformed pathname"; + }else{ + return smprintf("%s%s", zHome, zPath+1); + } + utf8_printf(STD_ERR, "Error: %s\n", zErr); + return 0; +} + +/* 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 param_xfr_table(sqlite3 *db, const char *zStoreDbName, + int bSaveNotLoad, const char *azNames[], int nNames){ + int rc = 0; + char *zSql = 0; /* to be sqlite3_free()'ed */ + sqlite3_str *sbCopy = 0; + const char *zHere = PARAM_TABLE_SNAME; + const char *zThere = PARAM_STORE_SNAME; + const char *zTo = (bSaveNotLoad)? zThere : zHere; + const char *zFrom = (bSaveNotLoad)? zHere : zThere; + sqlite3 *dbStore = 0; + int openFlags = (bSaveNotLoad) + ? SQLITE_OPEN_URI|SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE + : SQLITE_OPEN_READONLY; + + /* 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 parameter store DB %s\n", + bSaveNotLoad? "open/create" : "read", zStoreDbName); + return rc; + } + /* Ensure it has the parameter store table, or handle its absence. */ + assert(dbStore!=0); + if( sqlite3_table_column_metadata + (dbStore, "main", PARAM_STORE_NAME, 0, 0, 0, 0, 0, 0)!=SQLITE_OK ){ + if( !bSaveNotLoad ){ + utf8_printf(STD_ERR, "Error: No parameters 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 "PARAM_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; + + zSql = smprintf("ATTACH %Q AS %s;", zStoreDbName, PARAM_STORE_SCHEMA); + shell_check_oom(zSql); + rc = sqlite3_exec(db, zSql, 0, 0, 0); + sqlite3_free(zSql); + if( rc!=SQLITE_OK ) return rc; + + 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); + + sqlite3_exec(db, "DETACH "PARAM_STORE_SCHEMA";", 0, 0, 0); + return rc; +} + +/* Default location of parameters store DB for .parameters save/load. */ +static const char *zDefaultParamStore = "~/sqlite_params.sdb"; + +/* 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 *params_store_path(const char *zSpec){ + if( zSpec==0 || zSpec[0]==0 || strcmp(zSpec,"~")==0 ){ + return home_based_path(zDefaultParamStore); + }else if ( zSpec[0]=='~' ){ + return home_based_path(zSpec); + } + return zSpec; +} + +/* Load some or all parameters. Arguments are "load FILE ?NAMES?". */ +static int parameters_load(sqlite3 *db, const char *azArg[], int nArg){ + const char *zStore = params_store_path((nArg>1)? azArg[1] : 0); + 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 = param_xfr_table(db, zStore, 0, pzFirst, nNames); + if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore); + return rc; + } +} + +/* Save some or all parameters. Arguments are "save FILE ?NAMES?". */ +static int parameters_save(sqlite3 *db, const char *azArg[], int nArg){ + const char *zStore = params_store_path((nArg>1)? azArg[1] : 0); + 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 = param_xfr_table(db, zStore, 1, pzFirst, nNames); + if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore); + return rc; + } +} + +#ifndef SQLITE_NOHAVE_SYSTEM +/* + * Edit one named parameter in the parameters 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 parameters table, and may also serve + * to partition the key namespace. (This is not done now.) + */ +static int edit_one_param(sqlite3 *db, char *name, int eval, + ParamTableUse uses, const char * zEditor){ + struct param_row paramVU = {0,0,0}; + int rc; + char * zVal = 0; + char * zSql = smprintf + ("SELECT value, uses FROM " PARAM_TABLE_SNAME " WHERE key=%Q", name); + shell_check_oom(zSql); + sqlite3_exec(db, zSql, param_find_callback, ¶mVU, 0); + sqlite3_free(zSql); + assert(paramVU.hits<2); + if( paramVU.hits==1 && paramVU.uses==uses){ + /* Editing an existing value of same kind. */ + sqlite3_free(paramVU.value); + if( eval!=0 ){ + zSql = smprintf + ("SELECT edit(value, %Q) FROM " PARAM_TABLE_SNAME + " WHERE key=%Q AND uses=%d", zEditor, name, uses); + zVal = db_text(db, zSql, 1); + sqlite3_free(zSql); + zSql = smprintf + ("UPDATE "PARAM_TABLE_SNAME" SET value=(SELECT %s) WHERE" + " key=%Q AND uses=%d", zVal, name, uses); + }else{ + zSql = smprintf + ("UPDATE "PARAM_TABLE_SNAME" SET value=edit(value, %Q) WHERE" + " key=%Q AND uses=%d", zEditor, name, uses); + } + }else{ + /* Editing a new value of same kind. */ + assert(paramVU.value==0 || paramVU.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 "PARAM_TABLE_SNAME"(key,value,uses)" + " VALUES (%Q,(SELECT %s LIMIT 1),%d)", + name, zVal, uses); + }else{ + zSql = smprintf + ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)" + " VALUES (%Q,edit('-- %q%s', %Q),%d)", + name, name, "\n", zEditor, uses); + } + } + shell_check_oom(zSql); + if( eval!=0 ){ + } + rc = sqlite3_exec(db, zSql, 0, 0, 0); + sqlite3_free(zSql); + sqlite3_free(zVal); + return rc!=SQLITE_OK; +} +#endif + +/* 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; +} + +/* Get a named parameter value in form of stepped prepared statement, + * ready to have its value taken from the 0th column. If the name + * cannot be found for the given ParamTableUse, 0 is returned. + * The caller is responsible for calling sqlite3_finalize(pStmt), + * where pStmt is the return from this function. + */ +static sqlite3_stmt *get_param_value(sqlite3 *db, char *name, + ParamTableUse ptu){ + sqlite3_stmt *rv = 0; + int rc; + char *zSql = smprintf + ( "SELECT value FROM "PARAM_TABLE_SNAME + " WHERE key=%Q AND uses=%d", name, ptu ); + shell_check_oom(zSql); + rc = sqlite3_prepare_v2(db, zSql, -1, &rv, 0); + sqlite3_free(zSql); + if( SQLITE_OK==rc ){ + if( SQLITE_ROW==sqlite3_step(rv) ) return rv; + sqlite3_finalize(rv); + } + return 0; +} + +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 } +}; + +/* 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 set subcommand (per help text) + */ +static int param_set(sqlite3 *db, char cCast, + char *name, char **valBeg, char **valLim, + ParamTableUse ptu){ + 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( needsEval ){ + if( zCastTo!=0 ){ + zSql = smprintf + ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" + " VALUES(%Q,CAST((%s) AS %s),%d);", name, zValue, zCastTo, ptu ); + }else{ + zSql = smprintf + ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" + "VALUES(%Q,(%s),%d);", name, zValue, ptu ); + } + 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,%d);", name, zValue, ptu ); + 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 dot-command */ +static void list_params(ShellExState *psx, ParamTableUse ptu, u8 bShort, + char **pzArgs, int nArg){ + sqlite3_stmt *pStmt = 0; + sqlite3 *db = DBX(psx); + sqlite3_str *sbList = sqlite3_str_new(db); + int len = 0, rc; + char *zFromWhere = 0; + char *zSql = 0; + sqlite3_str_appendf(sbList, "FROM "PARAM_TABLE_SNAME + " WHERE (?1=3 OR 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(uses, value, quote(value))" + " %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, "Binding Values:\n%-*s %s\n", + len, "name", "value"); + } + utf8_printf(out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0), + sqlite3_column_text(pStmt,2)); + break; + case PTU_Script: + if( nScripts++ == 0 ){ + utf8_printf(out, "Scripts\n%-*s %s\n", len, "name", "value"); + } + 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( azBeg2 || 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. + ** New ones get a uses tag auto-selected by their leading char. + */ + if( strcmp(azArg[1],"edit")==0 ){ + ShellInState *psi = ISS(p); + int ia = 2; + int eval = 0; + 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); + if( psi->zEditor==0 ){ + const char *zE = getenv("VISUAL"); + if( zE!=0 ) psi->zEditor = smprintf("%s", zE); + } + if( nArg>=3 && azArg[2][0]=='-' ){ + char *zArg = (azArg[2][1]=='-')? azArg[2]+2 : azArg[2]+1; + if( strncmp(zArg,"editor=",7)==0 ){ + sqlite3_free(psi->zEditor); + /* Accept an initial -editor=? option. */ + psi->zEditor = smprintf("%s", zArg+7); + ++ia; + } + } + if( psi->zEditor==0 ){ + utf8_printf(STD_ERR, + "Either set env-var VISUAL to name an" + " editor and restart, or rerun\n " + ".parameter edit with an initial " + "edit option, --editor=EDITOR_COMMAND .\n"); + return DCR_Error; + } + /* Future: Allow an option whereby new value can be evaluated + * the way that .parameter set ... does. + */ + while( ia < nArg ){ + ParamTableUse ptu; + char cf = (azArg[ia][0]=='-')? azArg[ia][1] : 0; + if( cf!=0 && azArg[ia][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", + azArg[--ia]); + return DCR_Error; + } + } + ptu = classify_param_name(azArg[ia]); + if( ptu==PTU_Nil ){ + utf8_printf(STD_ERR, "Error: %s cannot be a binding or executable" + " parameter name.\n", azArg[ia]); + return DCR_Error; + } + rc = edit_one_param(db, azArg[ia], eval, ptu, psi->zEditor); + ++ia; + if( rc!=0 ) return rc; + } + }else +#endif + + /* .parameter init + ** Make sure the TEMP table used to hold bind parameters exists. + ** Create it if necessary. + */ + if( nArg==2 && strcmp(azArg[1],"init")==0 ){ + param_table_init(db); + }else + + /* .parameter list|ls + ** List all or selected bind parameters. + ** list displays names, values and uses. + ** ls displays just the names. + */ + if( nArg>=2 && ((strcmp(azArg[1],"list")==0) + || (strcmp(azArg[1],"ls")==0)) ){ + list_params(p, PTU_Nil, azArg[1][1]=='s', azArg+2, nArg-2); + }else + + /* .parameter load + ** Load all or named parameters from specified or default (DB) file. + */ + if( strcmp(azArg[1],"load")==0 ){ + param_table_init(db); + rc = parameters_load(db, (const char **)azArg+1, nArg-1); + }else + + /* .parameter save + ** Save all or named parameters into specified or default (DB) file. + */ + if( strcmp(azArg[1],"save")==0 ){ + rc = parameters_save(db, (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 && 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_Nil ){ + utf8_printf(STD_ERR, + "Error: %s is not a usable parameter name.\n", azArg[inv]); + rc = 1; + }else{ + param_table_init(db); + rc = param_set(db, cCast, azArg[inv], + &azArg[inv+1], &azArg[nArg], ptu); + if( rc!=SQLITE_OK ){ + utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(db)); rc = 1; - goto meta_command_exit; + } + } + }else + + { /* If no command name and arg count matches, show a syntax error */ + showHelp(ISS(p)->out, "parameter", p); + return DCR_CmdErred; + } + + return rc; +} + +/***************** + * 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; iout, "%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; iflgProgress |= SHELL_PROGRESS_QUIET; + continue; + } + if( strcmp(z,"reset")==0 ){ + psi->flgProgress |= SHELL_PROGRESS_RESET; + continue; + } + if( strcmp(z,"once")==0 ){ + psi->flgProgress |= SHELL_PROGRESS_ONCE; + continue; + } + if( strcmp(z,"limit")==0 ){ + if( i+1>=nArg ){ + *pzErr = smprintf("missing argument on --limit\n"); + return DCR_Unpaired|i; + }else{ + psi->mxProgress = (int)integerValue(azArg[++i]); + } + continue; + } + return DCR_Unknown|i; + }else{ + nn = (int)integerValue(z); + } + } + open_db(p, 0); + sqlite3_progress_handler(DBX(p), nn, progress_handler, psi); + return DCR_Ok; +} +/* Allow too few arguments by tradition, (a form of no-op.) */ +DISPATCHABLE_COMMAND( prompt ? 1 3 ){ + if( nArg >= 2) { + strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1); + } + if( nArg >= 3) { + strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1); + } + return DCR_Ok; +} + +/***************** + * The .read, .recover and .restore commands + */ +CONDITION_COMMAND( recover !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) ); +COLLECT_HELP_TEXT[ + ".read FILE Read input from FILE", + " If FILE begins with \"|\", it is a command that generates the input.", + ".recover Recover as much data as possible from corrupt db.", + " --freelist-corrupt Assume the freelist is corrupt", + " --recovery-db NAME Store recovery metadata in database file NAME", + " --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", +]; +DISPATCHABLE_COMMAND( read 3 2 2 ){ + DotCmdRC rc = DCR_Ok; + FILE *inUse = 0; + int (*fCloser)(FILE *) = 0; + if( ISS(p)->bSafeMode ) return DCR_AbortError; + if( azArg[1][0]=='|' ){ +#ifdef SQLITE_OMIT_POPEN + *pzErr = smprintf("pipes are not supported in this OS\n"); + rc = DCR_Error; + /* p->out = STD_OUT; This was likely not needed. To be investigated. */ +#else + inUse = popen(azArg[1]+1, "r"); + if( inUse==0 ){ + *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]); + rc = DCR_Error; + }else{ + fCloser = pclose; + } +#endif + }else if( (inUse = openChrSource(azArg[1]))==0 ){ + *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]); + rc = DCR_Error; + }else{ + fCloser = fclose; + } + if( inUse!=0 ){ + InSource inSourceRedir + = INSOURCE_FILE_REDIR(inUse, azArg[1], ISS(p)->pInSource); + ISS(p)->pInSource = &inSourceRedir; + rc = process_input(ISS(p)); + /* If error(s) occured during process, leave complaining to them. */ + if( rc==DCR_Error ) rc = DCR_CmdErred; + assert(fCloser!=0); + fCloser(inUse); + ISS(p)->pInSource = inSourceRedir.pFrom; + } + return rc; +} + +/* +** 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 ){ + FILE *out = ISS(p)->out; + sqlite3 *db; + int rc = SQLITE_OK; + sqlite3_stmt *pLoop = 0; /* Loop through all root pages */ + sqlite3_stmt *pPages = 0; /* Loop through all pages in a group */ + sqlite3_stmt *pCells = 0; /* Loop through all cells in a page */ + const char *zRecoveryDb = ""; /* Name of "recovery" database */ + const char *zLostAndFound = "lost_and_found"; + int i; + int nOrphan = -1; + RecoverTable *pOrphan = 0; + + open_db(p, 0); + db = DBX(p); + + int bFreelist = 1; /* 0 if --freelist-corrupt is specified */ + int bRowids = 1; /* 0 if --no-rowids */ + for(i=1; i0" + " UNION" + " SELECT shell_int32(" + " (SELECT data FROM sqlite_dbpage WHERE pgno=trunk.pgno), 0) AS x " + " FROM trunk WHERE x>0" + ")," + "freelist(data, n, freepgno) AS (" + " SELECT data, min(16384, shell_int32(data, 1)-1), t.pgno " + " FROM trunk t, sqlite_dbpage s WHERE s.pgno=t.pgno" + " UNION ALL" + " SELECT data, n-1, shell_int32(data, 2+n) " + " FROM freelist WHERE n>=0" + ")" + "REPLACE INTO recovery.freelist SELECT freepgno FROM freelist;" + ); + } + + /* If this is an auto-vacuum database, add all pointer-map pages to + ** the freelist table. Do this regardless of whether or not + ** --freelist-corrupt was specified. */ + shellExec(db, &rc, + "WITH ptrmap(pgno) AS (" + " SELECT 2 WHERE shell_int32(" + " (SELECT data FROM sqlite_dbpage WHERE pgno=1), 13" + " )" + " UNION ALL " + " SELECT pgno+1+(SELECT page_size FROM pragma_page_size)/5 AS pp " + " FROM ptrmap WHERE pp<=(SELECT page_count FROM pragma_page_count)" + ")" + "REPLACE INTO recovery.freelist SELECT pgno FROM ptrmap" + ); + + shellExec(db, &rc, + "CREATE TABLE recovery.dbptr(" + " pgno, child, PRIMARY KEY(child, pgno)" + ") WITHOUT ROWID;" + "INSERT OR IGNORE INTO recovery.dbptr(pgno, child) " + " SELECT * FROM sqlite_dbptr" + " WHERE pgno NOT IN freelist AND child NOT IN freelist;" + + /* Delete any pointer to page 1. This ensures that page 1 is considered + ** a root page, regardless of how corrupt the db is. */ + "DELETE FROM recovery.dbptr WHERE child = 1;" + + /* Delete all pointers to any pages that have more than one pointer + ** to them. Such pages will be treated as root pages when recovering + ** data. */ + "DELETE FROM recovery.dbptr WHERE child IN (" + " SELECT child FROM recovery.dbptr GROUP BY child HAVING count(*)>1" + ");" + + /* Create the "map" table that will (eventually) contain instructions + ** for dealing with each page in the db that contains one or more + ** records. */ + "CREATE TABLE recovery.map(" + "pgno INTEGER PRIMARY KEY, maxlen INT, intkey, root INT" + ");" + + /* Populate table [map]. If there are circular loops of pages in the + ** database, the following adds all pages in such a loop to the map + ** as individual root pages. This could be handled better. */ + "WITH pages(i, maxlen) AS (" + " SELECT page_count, (" + " SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=page_count" + " ) FROM pragma_page_count WHERE page_count>0" + " UNION ALL" + " SELECT i-1, (" + " SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=i-1" + " ) FROM pages WHERE i>=2" + ")" + "INSERT INTO recovery.map(pgno, maxlen, intkey, root) " + " SELECT i, maxlen, NULL, (" + " WITH p(orig, pgno, parent) AS (" + " SELECT 0, i, (SELECT pgno FROM recovery.dbptr WHERE child=i)" + " UNION " + " SELECT i, p.parent, " + " (SELECT pgno FROM recovery.dbptr WHERE child=p.parent) FROM p" + " )" + " SELECT pgno FROM p WHERE (parent IS NULL OR pgno = orig)" + ") " + "FROM pages WHERE maxlen IS NOT NULL AND i NOT IN freelist;" + "UPDATE recovery.map AS o SET intkey = (" + " SELECT substr(data, 1, 1)==X'0D' FROM sqlite_dbpage WHERE pgno=o.pgno" + ");" + + /* Extract data from page 1 and any linked pages into table + ** recovery.schema. With the same schema as an sqlite_schema table. */ + "CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);" + "INSERT INTO recovery.schema SELECT " + " max(CASE WHEN field=0 THEN value ELSE NULL END)," + " max(CASE WHEN field=1 THEN value ELSE NULL END)," + " max(CASE WHEN field=2 THEN value ELSE NULL END)," + " max(CASE WHEN field=3 THEN value ELSE NULL END)," + " max(CASE WHEN field=4 THEN value ELSE NULL END)" + "FROM sqlite_dbdata WHERE pgno IN (" + " SELECT pgno FROM recovery.map WHERE root=1" + ")" + "GROUP BY pgno, cell;" + "CREATE INDEX recovery.schema_rootpage ON schema(rootpage);" + ); + + /* Open a transaction, then print out all non-virtual, non-"sqlite_%" + ** CREATE TABLE statements that extracted from the existing schema. */ + if( rc==SQLITE_OK ){ + sqlite3_stmt *pStmt = 0; + /* ".recover" might output content in an order which causes immediate + ** foreign key constraints to be violated. So disable foreign-key + ** constraint enforcement to prevent problems when running the output + ** script. */ + raw_printf(out, "PRAGMA foreign_keys=OFF;\n"); + raw_printf(out, "BEGIN;\n"); + raw_printf(out, "PRAGMA writable_schema = on;\n"); + shellPrepare(db, &rc, + "SELECT sql FROM recovery.schema " + "WHERE type='table' AND sql LIKE 'create table%'", &pStmt + ); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + const char *zCreateTable = (const char*)sqlite3_column_text(pStmt, 0); + raw_printf(out, "CREATE TABLE IF NOT EXISTS %s;\n", &zCreateTable[12]); + } + shellFinalize(&rc, pStmt); + } + + /* Figure out if an orphan table will be required. And if so, how many + ** user columns it should contain */ + shellPrepare(db, &rc, + "SELECT coalesce(max(maxlen), -2) FROM recovery.map WHERE root>1" + , &pLoop + ); + if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){ + nOrphan = sqlite3_column_int(pLoop, 0); + } + shellFinalize(&rc, pLoop); + pLoop = 0; + + shellPrepare(db, &rc, + "SELECT pgno FROM recovery.map WHERE root=?", &pPages + ); + + shellPrepare(db, &rc, + "SELECT max(field), group_concat(shell_escape_crnl(quote" + "(case when (? AND field<0) then NULL else value end)" + "), ', ')" + ", min(field) " + "FROM sqlite_dbdata WHERE pgno = ? AND field != ?" + "GROUP BY cell", &pCells + ); + + /* Loop through each root page. */ + shellPrepare(db, &rc, + "SELECT root, intkey, max(maxlen) FROM recovery.map" + " WHERE root>1 GROUP BY root, intkey ORDER BY root=(" + " SELECT rootpage FROM recovery.schema WHERE name='sqlite_sequence'" + ")", &pLoop + ); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){ + int iRoot = sqlite3_column_int(pLoop, 0); + int bIntkey = sqlite3_column_int(pLoop, 1); + int nCol = sqlite3_column_int(pLoop, 2); + int bNoop = 0; + RecoverTable *pTab; + + assert( bIntkey==0 || bIntkey==1 ); + pTab = recoverFindTable(db, &rc, iRoot, bIntkey, nCol, &bNoop); + if( bNoop || rc ) continue; + if( pTab==0 ){ + if( pOrphan==0 ){ + pOrphan = recoverOrphanTable(db, out, &rc, zLostAndFound, nOrphan); + } + pTab = pOrphan; + if( pTab==0 ) break; + } + + if( 0==sqlite3_stricmp(pTab->zQuoted, "\"sqlite_sequence\"") ){ + raw_printf(out, "DELETE FROM sqlite_sequence;\n"); + } + sqlite3_bind_int(pPages, 1, iRoot); + if( bRowids==0 && pTab->iPk<0 ){ + sqlite3_bind_int(pCells, 1, 1); + }else{ + sqlite3_bind_int(pCells, 1, 0); + } + sqlite3_bind_int(pCells, 3, pTab->iPk); + + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPages) ){ + int iPgno = sqlite3_column_int(pPages, 0); + sqlite3_bind_int(pCells, 2, iPgno); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pCells) ){ + int nField = sqlite3_column_int(pCells, 0); + int iMin = sqlite3_column_int(pCells, 2); + const char *zVal = (const char*)sqlite3_column_text(pCells, 1); + + RecoverTable *pTab2 = pTab; + if( pTab!=pOrphan && (iMin<0)!=bIntkey ){ + if( pOrphan==0 ){ + pOrphan = recoverOrphanTable(db, out, &rc, zLostAndFound, nOrphan); + } + pTab2 = pOrphan; + if( pTab2==0 ) break; + } + + nField = nField+1; + if( pTab2==pOrphan ){ + raw_printf(out, + "INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n", + pTab2->zQuoted, iRoot, iPgno, nField, + iMin<0 ? "" : "NULL, ", zVal, pTab2->azlCol[nField] + ); + }else{ + raw_printf(out, "INSERT INTO %s(%s) VALUES( %s );\n", + pTab2->zQuoted, pTab2->azlCol[nField], zVal + ); + } + } + shellReset(&rc, pCells); + } + shellReset(&rc, pPages); + if( pTab!=pOrphan ) recoverFreeTable(pTab); + } + shellFinalize(&rc, pLoop); + shellFinalize(&rc, pPages); + shellFinalize(&rc, pCells); + recoverFreeTable(pOrphan); + + /* The rest of the schema */ + if( rc==SQLITE_OK ){ + sqlite3_stmt *pStmt = 0; + shellPrepare(db, &rc, + "SELECT sql, name FROM recovery.schema " + "WHERE sql NOT LIKE 'create table%'", &pStmt + ); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + const char *zSql = (const char*)sqlite3_column_text(pStmt, 0); + if( sqlite3_strnicmp(zSql, "create virt", 11)==0 ){ + const char *zName = (const char*)sqlite3_column_text(pStmt, 1); + char *zPrint = shellMPrintf(&rc, + "INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)", + zName, zName, zSql + ); + raw_printf(out, "%s;\n", zPrint); + sqlite3_free(zPrint); }else{ - utf8_printf(stderr, "extra argument: \"%s\"\n", z); - rc = 1; - goto meta_command_exit; + raw_printf(out, "%s;\n", zSql); } } - if( zMode==0 ){ - if( p->mode==MODE_Column - || (p->mode>=MODE_Markdown && p->mode<=MODE_Box) - ){ - raw_printf - (p->out, - "current output mode: %s --wrap %d --wordwrap %s --%squote\n", - modeDescr[p->mode], p->cmOpts.iWrap, - p->cmOpts.bWordWrap ? "on" : "off", - p->cmOpts.bQuote ? "" : "no"); + shellFinalize(&rc, pStmt); + } + + if( rc==SQLITE_OK ){ + raw_printf(out, "PRAGMA writable_schema = off;\n"); + raw_printf(out, "COMMIT;\n"); + } + sqlite3_exec(db, "DETACH recovery", 0, 0, 0); + return rc; +} + +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_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; +} + +/***************** + * The .scanstats and .schema commands + */ +COLLECT_HELP_TEXT[ + ".scanstats on|off 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 ){ + ISS(p)->scanstatsOn = (u8)booleanValue(azArg[1]); +#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; + 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; iiout, "current output mode: %s\n", modeDescr[p->mode]); - } - zMode = modeDescr[p->mode]; - } - n2 = strlen30(zMode); - if( strncmp(zMode,"lines",n2)==0 ){ - p->mode = MODE_Line; - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - }else if( strncmp(zMode,"columns",n2)==0 ){ - p->mode = MODE_Column; - if( (p->shellFlgs & SHFLG_HeaderSet)==0 ){ - p->showHeader = 1; - } - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - p->cmOpts = cmOpts; - }else if( strncmp(zMode,"list",n2)==0 ){ - p->mode = MODE_List; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Column); - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - }else if( strncmp(zMode,"html",n2)==0 ){ - p->mode = MODE_Html; - }else if( strncmp(zMode,"tcl",n2)==0 ){ - p->mode = MODE_Tcl; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Space); - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - }else if( strncmp(zMode,"csv",n2)==0 ){ - p->mode = MODE_Csv; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf); - }else if( strncmp(zMode,"tabs",n2)==0 ){ - p->mode = MODE_List; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Tab); - }else if( strncmp(zMode,"insert",n2)==0 ){ - p->mode = MODE_Insert; - set_table_name(p, zTabname ? zTabname : "table"); - }else if( strncmp(zMode,"quote",n2)==0 ){ - p->mode = MODE_Quote; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - }else if( strncmp(zMode,"ascii",n2)==0 ){ - p->mode = MODE_Ascii; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Unit); - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Record); - }else if( strncmp(zMode,"markdown",n2)==0 ){ - p->mode = MODE_Markdown; - p->cmOpts = cmOpts; - }else if( strncmp(zMode,"table",n2)==0 ){ - p->mode = MODE_Table; - p->cmOpts = cmOpts; - }else if( strncmp(zMode,"box",n2)==0 ){ - p->mode = MODE_Box; - p->cmOpts = cmOpts; - }else if( strncmp(zMode,"count",n2)==0 ){ - p->mode = MODE_Count; - }else if( strncmp(zMode,"off",n2)==0 ){ - p->mode = MODE_Off; - }else if( strncmp(zMode,"json",n2)==0 ){ - p->mode = MODE_Json; + 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); + } +#endif + 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); + } + appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0); + appendText(&sSelect, zQarg, 0); + if( !bGlob ){ + appendText(&sSelect, " ESCAPE '\\' ", 0); + } + appendText(&sSelect, " AND ", 0); + sqlite3_free(zQarg); + } + if( bNoSystemTabs ){ + appendText(&sSelect, "name NOT LIKE 'sqlite_%%' AND ", 0); + } + appendText(&sSelect, "sql IS NOT NULL" + " ORDER BY snum, rowid", 0); + if( bDebug ){ + utf8_printf(data.out, "SQL: %s;\n", sSelect.z); }else{ - raw_printf(stderr, "Error: mode should be one of: " - "ascii box column csv html insert json line list markdown " - "qbox quote table tabs tcl\n"); - rc = 1; + rc = sqlite3_exec(datax.dbUser, sSelect.z, callback, &datax, &zErrMsg); } - p->cMode = p->mode; - }else + 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=='n' && strcmp(azArg[0], "nonce")==0 ){ - if( nArg!=2 ){ - raw_printf(stderr, "Usage: .nonce NONCE\n"); - rc = 1; - }else if( p->zNonce==0 || strcmp(azArg[1],p->zNonce)!=0 ){ - raw_printf(stderr, "line %d: incorrect nonce: \"%s\"\n", - p->lineno, azArg[1]); - exit(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( selecttrace ? 1 0 ){ + unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff; + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x); + return DCR_Ok; +} +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; iSesnSession; iSes++){ + if( strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break; + } + if( iSesnSession ){ + pSession = &pAuxDb->aSession[iSes]; + azCmd++; + nCmd--; }else{ - p->bSafeMode = 0; - return 0; /* Return immediately to bypass the safe mode reset - ** at the end of this procedure */ + pSession = &pAuxDb->aSession[0]; + iSes = 0; } - }else + } - if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 ){ - if( nArg==2 ){ - sqlite3_snprintf(sizeof(p->nullValue), p->nullValue, - "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]); + /* .session attach TABLE + ** Invoke the sqlite3session_attach() interface to attach a particular + ** table so that it is never filtered. + */ + if( 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{ - raw_printf(stderr, "Usage: .nullvalue STRING\n"); - rc = 1; + rc = sqlite3session_attach(pSession->p, azCmd[1]); + if( rc ){ + raw_printf(STD_ERR, "ERROR: sqlite3session_attach() returns %d\n", rc); + rc = 0; + } } }else