*/
#define UNUSED_PARAMETER(x) (void)(x)
+++#ifdef SQLITE_DEBUG
+++# define CHECK_RETURN_EQUAL(val, func) assert((val)==(func))
+++#else
+++# define CHECK_RETURN_EQUAL(val, func) func
+++#endif
+++
/*
** Number of elements in an array
*/
** by the SIGINT handler to interrupt database processing.
*/
static sqlite3 *globalDb = 0;
+++/*
+++** Mutex used to access *globalDb from main thread or ^C handler.
+++*/
++static sqlite3_mutex *pGlobalDbLock = 0;
/*
--** True if an interrupt (Control-C) has been received.
++** Greater than 0 if an interrupt (Control-C) has been received.
*/
static volatile int seenInterrupt = 0;
** SQL function: shell_putsnl(X)
**
** Write the text X to the screen (or whatever output is being directed)
---** adding a newline at the end, and then return X.
+++** adding a newline at the end, and then return X. If X is the NULL
+++** equivalent (due to OOM or otherwise), it acts as an empty string.
*/
static void shellPutsFunc(
sqlite3_context *pCtx,
int nVal,
sqlite3_value **apVal
){
-- ShellState *p = (ShellState*)sqlite3_user_data(pCtx);
++ ShellExState *psx = (ShellExState*)sqlite3_user_data(pCtx);
+++ const unsigned char *s = sqlite3_value_text(apVal[0]);
(void)nVal;
- utf8_printf(ISS(psx)->out, "%s\n", sqlite3_value_text(apVal[0]));
-- utf8_printf(p->out, "%s\n", sqlite3_value_text(apVal[0]));
+++ utf8_printf(ISS(psx)->out, "%s\n", (s!=0)?(char*)s:"");
sqlite3_result_value(pCtx, apVal[0]);
}
va_list ap;
char *zMsg;
va_start(ap, zErrMsg);
--- zMsg = sqlite3_vmprintf(zErrMsg, ap);
+++ shell_check_oom(zMsg = sqlite3_vmprintf(zErrMsg, ap));
va_end(ap);
-- raw_printf(stderr, "line %d: ", p->lineno);
-- utf8_printf(stderr, "%s\n", zMsg);
-- exit(1);
++ raw_printf(STD_ERR, "line %d: ", ISS(psx)->pInSource->lineno);
++ utf8_printf(STD_ERR, "%s\n", zMsg);
++ sqlite3_free(zMsg);
++ psx->shellAbruptExit = 0x202;
++ return 1;
}
++ return 0;
++}
++
++/*
++** Emit formatted output to shell's current output, possibly translated
++** for the legacy console on the Windows platform. This is exposed as
++** a helper for extensions so that they may share a common buffering
++** for FILE* output or share output capture when/if that is implemented.
++*/
++static void utf8_out_printf(ShellExState *p, const char *zFormat, ...){
++ va_list ap;
++ va_start(ap, zFormat);
++#if defined(_WIN32) || defined(WIN32)
++ vf_utf8_printf(ISS(p)->out, zFormat, ap);
++#else
++ vfprintf(ISS(p)->out, zFormat, ap);
++#endif
++ va_end(ap);
}
/*
if( argc==2 ){
zEditor = (const char*)sqlite3_value_text(argv[1]);
+++ /* If that failed for OOM, just pretend it is not there. */
}else{
zEditor = getenv("VISUAL");
+++ /* Note that this code NOT threadsafe due to use of the getenv()
+++ ** result. Because the shell is single-threaded, this is fine.
+++ ** But if the CLI is called as a subroutine in a multi-threaded
+++ ** program, adjustments may have to be made. */
}
if( zEditor==0 ){
sqlite3_result_error(context, "no editor for edit()", -1);
return;
}
db = sqlite3_context_db_handle(context);
--- zTempFile = 0;
sqlite3_file_control(db, 0, SQLITE_FCNTL_TEMPFILENAME, &zTempFile);
if( zTempFile==0 ){
sqlite3_uint64 r = 0;
sqlite3_result_blob64(context, p, sz, sqlite3_free);
}else{
sqlite3_int64 i, j;
-- if( hasCRNL ){
-- /* If the original contains \r\n then do no conversions back to \n */
-- }else{
++ if( !hasCRNL ){
/* If the file did not originally contain \r\n then convert any new
** \r\n back into \n */
+ + p[sz] = 0;
for(i=j=0; i<sz; i++){
if( p[i]=='\r' && p[i+1]=='\n' ) i++;
p[j++] = p[i];
#endif /* SQLITE_NOHAVE_SYSTEM */
/*
--** Save or restore the current output mode
++** Save or restore the current output mode, partially per spec. (OM_STATE)
++*/
++typedef enum {
++ SWM_showHeader = 1, SWM_shellFlags = 2, SWM_mode = 4, SWM_cmOpts = 8,
++ SWM_colSeparator = 0x10, SWM_rowSeparator = 0x20, SWM_everything = 0x3F,
++ SWM_CountOf = 6
++} SaveWhatMode;
++
++/* This is available in most C89+ C compilers as offsetof(...), but since we
++ * cater to the most arcane C89-like compilers around, define it for sure:
++ */
++#define MEMBER_OFFSET(stype, member) ((size_t)&(((stype*)0)->member))
++#define MEMBER_SIZEOF(stype, member) (sizeof(((stype*)0)->member))
++static struct {
++ size_t offset;
++ size_t size;
++} outputModeCopy[] = {
++#define SS_MEMBER_COPY(mn) \
++ { MEMBER_OFFSET(ShellInState,mn), MEMBER_SIZEOF(ShellInState,mn) }
++ SS_MEMBER_COPY(showHeader), SS_MEMBER_COPY(shellFlgs),
++ SS_MEMBER_COPY(mode), SS_MEMBER_COPY(cmOpts),
++ SS_MEMBER_COPY(colSeparator), SS_MEMBER_COPY(rowSeparator)
++#undef SS_MEMBER_COPY
++};
++
- /* Allocate a buffer, copy requested output mode data to it, and return it. */
+++/* Allocate a buffer, copy requested output mode data to it, and return it.
+++ * This never fails under OOM conditions. Instead, it returns 0.
+++ */
++static OutputModeSave *outputModeSave(ShellInState *psi, SaveWhatMode w){
++ u16 what = (u16)w;
++ int i, nAlloc = sizeof(what)+1, mask = 1;
++ char *pSaved = 0, *pFill;
++ for( i=0; i<SWM_CountOf; mask<<=1, ++i ){
++ if( (what & mask)!=0 ) nAlloc += (int)outputModeCopy[i].size;
++ }
++ assert(i==ArraySize(outputModeCopy));
++ pSaved = sqlite3_malloc(nAlloc);
++ if( pSaved==0 ) return 0;
++ *(u16 *)pSaved = what;
++ pFill = pSaved + sizeof(what);
++ for( mask=1, i=0; i<SWM_CountOf; mask<<=1, ++i ){
++ if( (what & mask)!=0 ){
++ size_t nb = outputModeCopy[i].size;
++ memcpy(pFill, ((char*)psi)+outputModeCopy[i].offset, nb);
++ pFill += nb;
++ }
++ }
++ *pFill = 0xA5;
++ return (OutputModeSave *)pSaved;
++}
++
++/* From a buffer returned by outputModeSave, restore output mode data.
++ * The buffer is freed and its pointer is invalidated.
++ * If called with some other buffer, results are undefined, likely bad.
++ */
++static void outputModeRestore(ShellInState *psi, OutputModeSave *pSaved){
++ sqlite3_uint64 nA = sqlite3_msize(pSaved);
++ u16 what = (nA>sizeof(what))? *((u16 *)pSaved) : 0;
++ int i, nAlloc = sizeof(what)+1, mask = 1;
++ char *pTake = (char *)pSaved + sizeof(what);
++ for( i=0; i<SWM_CountOf && nAlloc<nA; mask<<=1, ++i ){
++ if( (what & mask)!=0 ){
++ size_t nb = outputModeCopy[i].size;
++ memcpy(((char*)psi)+outputModeCopy[i].offset, pTake, nb);
++ pTake += nb;
++ }
++ }
++ assert(*pTake==0xA5);
++ sqlite3_free(pSaved);
++}
++
++/*
++** Save or restore the current output mode, in whole or in part.
+++** A save may abort on OOM.
*/
--static void outputModePush(ShellState *p){
-- p->modePrior = p->mode;
-- p->priorShFlgs = p->shellFlgs;
-- memcpy(p->colSepPrior, p->colSeparator, sizeof(p->colSeparator));
-- memcpy(p->rowSepPrior, p->rowSeparator, sizeof(p->rowSeparator));
++static void outputModePushSome(ShellInState *psi, SaveWhatMode w){
++ OutputModeSave *pOMS;
++ assert(psi->nSavedModes<MODE_STACK_MAX); /* Fail hard for this logic error. */
++ if( psi->nSavedModes>=MODE_STACK_MAX ) return;
++ pOMS = outputModeSave(psi, w);
++ shell_check_oom(pOMS);
++ psi->pModeStack[psi->nSavedModes++] = pOMS;
+}
- static void outputModePop(ShellState *p){
- p->mode = p->modePrior;
- p->shellFlgs = p->priorShFlgs;
- memcpy(p->colSeparator, p->colSepPrior, sizeof(p->colSeparator));
- memcpy(p->rowSeparator, p->rowSepPrior, sizeof(p->rowSeparator));
++static void outputModePush(ShellInState *psi){
++ outputModePushSome(psi, SWM_everything);
+ }
-static void outputModePop(ShellState *p){
- p->mode = p->modePrior;
- p->shellFlgs = p->priorShFlgs;
- memcpy(p->colSeparator, p->colSepPrior, sizeof(p->colSeparator));
- memcpy(p->rowSeparator, p->rowSepPrior, sizeof(p->rowSeparator));
++static void outputModePop(ShellInState *p){
++ OutputModeSave *pOMS;
++ assert(p->nSavedModes>0); /* Should not be here unless something pushed. */
++ if( p->nSavedModes==0 ) return;
++ pOMS = p->pModeStack[--p->nSavedModes];
++ assert(pOMS!=0);
++ p->pModeStack[p->nSavedModes] = 0;
++ outputModeRestore(p, pOMS);
}
/*
**
** Try to use zA and zB first. If both of those are already found in z[]
** then make up some string and store it in the buffer zBuf.
+++** The length of this buffer must be no less than SHELL_QESC_GENLEN .
*/
+++#define SHELL_QESC_GENLEN 20
static const char *unused_string(
const char *z, /* Result must not appear anywhere in z */
const char *zA, const char *zB, /* Try these first */
if( strstr(z, zA)==0 ) return zA;
if( strstr(z, zB)==0 ) return zB;
do{
--- sqlite3_snprintf(20,zBuf,"(%s%u)", zA, i++);
+++ sqlite3_snprintf(SHELL_QESC_GENLEN, zBuf,"(%s%u)", zA, i++);
}while( strstr(z,zBuf)!=0 );
return zBuf;
}
const char *zCR = 0;
int nNL = 0;
int nCR = 0;
--- char zBuf1[20], zBuf2[20];
+++ char zBuf1[SHELL_QESC_GENLEN], zBuf2[SHELL_QESC_GENLEN];
for(i=0; z[i]; i++){
if( z[i]=='\n' ) nNL++;
if( z[i]=='\r' ) nCR++;
** the separator, which may or may not be a comma. p->nullValue is
** the null value. Strings are quoted if necessary. The separator
** is only issued if bSep is true.
+++** This routine may abort under OOM conditions.
*/
--static void output_csv(ShellState *p, const char *z, int bSep){
-- FILE *out = p->out;
++static void output_csv(ShellExState *psx, const char *z, int bSep){
++ FILE *out = ISS(psx)->out;
++ char *zColSep = psx->zFieldSeparator;
if( z==0 ){
-- utf8_printf(out,"%s",p->nullValue);
++ utf8_printf(out,"%s",psx->zNullValue);
}else{
unsigned i;
for(i=0; z[i]; i++){
*/
static void interrupt_handler(int NotUsed){
UNUSED_PARAMETER(NotUsed);
-- if( ++seenInterrupt>1 ) exit(1);
-- if( globalDb ) sqlite3_interrupt(globalDb);
++ if( ++seenInterrupt>1 ){
- sqlite3_mutex_free(pGlobalDbLock);
++ exit(1);
++ }
++ if( globalDb ){
- sqlite3_mutex_enter(pGlobalDbLock);
- sqlite3_interrupt(globalDb);
- sqlite3_mutex_leave(pGlobalDbLock);
+++ int itry = 0;
+++ while( itry < 10 ){
+++ if( sqlite3_mutex_try(pGlobalDbLock)==SQLITE_OK ){
+++ sqlite3_interrupt(globalDb);
+++ sqlite3_mutex_leave(pGlobalDbLock);
+++ return;
+++ }else{
+++ sqlite3_sleep(50);
+++ ++itry;
+++ }
+++ }
+++ /* Apparently, no polite interruption is working. (Something is
+++ ** very busy under mutex protection.) So quit altother. */
+++ exit(1);
++ }
}
#if (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE)
if( zTail[0]==';' && (strstr(z, "/*")!=0 || strstr(z,"--")!=0) ){
const char *zOrig = z;
static const char *azTerm[] = { "", "*/", "\n" };
--- int i;
+++ int i, rc;
for(i=0; i<ArraySize(azTerm); i++){
--- char *zNew = sqlite3_mprintf("%s%s;", zOrig, azTerm[i]);
+++ char *zNew = smprintf("%s%s;", zOrig, azTerm[i]);
shell_check_oom(zNew);
--- if( sqlite3_complete(zNew) ){
+++ if( 1==(rc = sqlite3_complete(zNew)) ){
size_t n = strlen(zNew);
zNew[n-1] = 0;
zToFree = zNew;
break;
}
sqlite3_free(zNew);
+++ if( rc==SQLITE_NOMEM ) shell_out_of_memory();
}
}
if( sqlite3_strglob("CREATE TABLE ['\"]*", z)==0 ){
}
#endif /* SQLITE_OMIT_PROGRESS_CALLBACK */
-/*
-** Print N dashes
-*/
++/* For debugging or experimentation, the shell DB can be made file-based. */
++#ifndef SHELL_DB_FILE
++# define SHELL_DB_STORE ":memory:"
++#else
++# define SHELL_DB_STORE SHELL_STRINGIFY(SHELL_DB_FILE)
++#endif
++
++#define SHELL_DISP_SCHEMA "main"
++#define SHELL_DISP_TAB "ShellCommands"
++#define SHELL_AHELP_TAB "ShellAdHocHelp"
++#define SHELL_DISP_VIEW "ShellActiveCmds"
++#define SHELL_HELP_VIEW "ShellHelpedCmds"
++
++/*
++** Ensure dbShell exists and return SQLITE_OK,
++** or complain and return SQLITE_ERROR.
++*/
++static int ensure_shell_db(ShellExState *psx){
++ if( psx->dbShell!=0 ) return SQLITE_OK;
++ else{
++ int rc = sqlite3_open(SHELL_DB_STORE, &psx->dbShell);
++ if( rc!=SQLITE_OK ){
++ utf8_printf(STD_ERR, "Shell DB open failure: %s\n", sqlite3_errstr(rc));
++ return SQLITE_ERROR;
++ }
++#ifndef SQLITE_NOHAVE_SYSTEM
++ sqlite3_create_function(psx->dbShell, "edit", 1,
++ SQLITE_UTF8, 0, editFunc, 0, 0);
++ sqlite3_create_function(psx->dbShell, "edit", 2,
++ SQLITE_UTF8, 0, editFunc, 0, 0);
++#endif
++ return rc;
++ }
++}
++
++/* Tell whether the above-created table exists, return true iff exists. */
++static int dispatch_table_exists(sqlite3 *dbs){
++ return sqlite3_table_column_metadata
++ (dbs, SHELL_DISP_SCHEMA, SHELL_DISP_TAB, 0, 0, 0, 0, 0, 0)==SQLITE_OK;
++}
++
++static int ensure_dispatch_table(ShellExState *psx){
++ int rc = ensure_shell_db(psx);
++ if( rc==SQLITE_OK ){
++ char *zErr = 0;
++ int rc1, rc2;
++ if( dispatch_table_exists(psx->dbShell) ) return rc;
++ /* Create the dispatch table and view on it. */
++#ifdef SHELL_DB_FILE
++ sqlite3_exec(psx->dbShell, "DROP TABLE IF EXISTS "SHELL_DISP_TAB, 0,0,0);
++ sqlite3_exec(psx->dbShell, "DROP VIEW IF EXISTS "SHELL_DISP_VIEW, 0,0,0);
++ sqlite3_exec(psx->dbShell, "DROP TABLE IF EXISTS "SHELL_AHELP_TAB, 0,0,0);
++ sqlite3_exec(psx->dbShell, "DROP VIEW IF EXISTS "SHELL_HELP_VIEW, 0,0,0);
++#endif
++ rc1 = sqlite3_exec(psx->dbShell, "CREATE TABLE "SHELL_AHELP_TAB"("
++ "name TEXT, extIx INT, helpText TEXT,"
++ "PRIMARY KEY(name,extIx)) WITHOUT ROWID", 0, 0, &zErr);
++ rc1 = sqlite3_exec(psx->dbShell, "CREATE TABLE "SHELL_DISP_TAB"("
++ "name TEXT, extIx INT, cmdIx INT,"
++ "PRIMARY KEY(extIx,cmdIx)) WITHOUT ROWID", 0, 0, &zErr);
++ rc2 = sqlite3_exec(psx->dbShell,
++ /* name, extIx, cmdIx */
++ "CREATE VIEW "SHELL_DISP_VIEW" AS"
++ " SELECT s.name AS name,"
++ " max(s.extIx) AS extIx, s.cmdIx AS cmdIx"
++ " FROM "SHELL_DISP_TAB" s GROUP BY name"
++ " ORDER BY name",
++ 0, 0, &zErr);
++ rc2 = sqlite3_exec(psx->dbShell,
++ /* name, extIx, cmdIx, help */
++ "CREATE VIEW "SHELL_HELP_VIEW" AS"
++ " SELECT s.name AS name, max(s.extIx) AS extIx,"
++ " s.cmdIx AS cmdIx, NULL as help"
++ " FROM "SHELL_DISP_TAB" s GROUP BY name"
++ " UNION"
++ " SELECT s.name AS name, max(s.extIx) AS extIx,"
++ " -1 AS cmdIx, s.helpText AS help"
++ " FROM "SHELL_AHELP_TAB" s GROUP BY name"
++ " ORDER BY name",
++ 0, 0, &zErr);
++ if( rc1!=SQLITE_OK || rc2!=SQLITE_OK || zErr!=0 ){
++ utf8_printf(STD_ERR, "Shell DB init failure, %s\n", zErr? zErr : "?");
++ rc = SQLITE_ERROR;
++ }else rc = SQLITE_OK;
++ sqlite3_free(zErr);
++ }
++ return rc;
++}
++
++/*
++** Skip over whitespace, returning remainder.
++*/
++static const char *skipWhite( const char *z ){
++ while( IsSpace(*z) ) ++z;
++ return z;
++}
++
+/*
+** Print N dashes
+*/
static void print_dashes(FILE *out, int N){
const char zDash[] = "--------------------------------------------------";
const int nDash = sizeof(zDash) - 1;
/*
** Allocate space and save off string indicating current error.
+++** The return must be passed to sqlite3_free() sometime.
*/
static char *save_err_msg(
sqlite3 *db, /* Database to query */
sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &savedWhereTrace);
}
++/* DB schema protection for use by next two utility functions */
++typedef struct DbProtectState {
++ int wrSchema;
++ int defensiveMode;
++} DbProtectState;
++
++/* Allow system (sqlite_*) schema changes, return prior protection state. */
++static DbProtectState allow_sys_schema_change(sqlite3 *db){
++ DbProtectState rv;
++ sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, -1, &rv.defensiveMode);
++ sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0);
++ sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, -1, &rv.wrSchema);
++ sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, 0);
++ return rv;
++}
++
++/* Restore protection state using allow_sys_schema_change() return. */
++static void restore_sys_schema_protection( sqlite3 *db, DbProtectState *pPS ){
++ sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, pPS->wrSchema, 0);
++ sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, pPS->defensiveMode, 0);
++}
++
++/* Partition the temp.sqlite_parameters and main.sqlite_variables
++ * key namespace according to use.
++ */
++typedef enum {
++ PTU_Binding = 0, /* Binding parameter, value to be bound */
++#define SPTU_Binding SHELL_STRINGIFY(0)
++ PTU_Script = 1, /* A value containing shell SQL and dot-commands */
++#define SPTU_Script SHELL_STRINGIFY(1)
++ PTU_Entry = 2, /* Value as entered for non-text binding parameters */
++#define SPTU_Entry SHELL_STRINGIFY(2)
++ PTU_Nil = 3 /* Unspecified */
++} ParamTableUse;
++
++static ParamTableUse classify_param_name( const char *zName ){
++ char c = *zName;
++ switch( c ){
++ case '$': case ':': case '@': case '?': return PTU_Binding;
++ default: return isalpha(c)? PTU_Script : PTU_Nil;
++ }
++}
++
++#ifndef SQLITE_NOHAVE_SYSTEM
++/* Possibly using a -editor=X argument and env-var VISUAL, attempt
++ * to get the zEditor shell state member set iff not already set.
++ * If there is no such argument, the env-var is retrieved if set.
++ * If the argument is -editor=X or --editor=X, use that and leave
++ * the zEditor member set accordingly. Returns are:
++ * 0 => editor set, zEd was not the -editor option
++ * 1 => editor set, zEd consumed as -editor option
++ * -1 => editor not set, and error/advice message issued.
++ *
++ * This implements an undocumented fall-back for the .vars and
++ * .parameters edit subcommands, so that users need not restart
++ * a shell session to get an editor specified upon need for it. */
++int attempt_editor_set(ShellInState *psi, char *zDot, const char *zEd){
++ if( psi->zEditor==0 ){
++ const char *zE = getenv("VISUAL");
++ if( zE!=0 ) psi->zEditor = smprintf("%s", zE);
++ }
++ if( zEd && zEd[0]=='-' ){
++ zEd += 1 + (zEd[1]=='-');
++ if( cli_strncmp(zEd,"editor=",7)==0 ){
++ sqlite3_free(psi->zEditor);
++ /* Accept an initial -editor=? option. */
++ psi->zEditor = smprintf("%s", zEd+7);
++ return 1;
++ }
++ }
++ if( psi->zEditor==0 ){
++ utf8_printf(STD_ERR,
++ "Either set env-var VISUAL to name an"
++ " editor and restart, or rerun\n "
++ ".%s edit with an initial edit option,"
++ " --editor=EDITOR_COMMAND .\n", zDot);
++ return -1;
++ }
++ return 0;
++}
++#endif
++
++/* The table kept for user DBs if .parameter command is used usefully. */
++#define PARAM_TABLE_NAME "sqlite_parameters"
++#define PARAM_TABLE_SCHEMA "temp"
++#define PARAM_TABLE_SNAME PARAM_TABLE_SCHEMA"."PARAM_TABLE_NAME
++
++/* The table kept for the shell DB if .vars command is used usefully. */
++#define SHVAR_TABLE_NAME "sqlite_variables"
++#define SHVAR_TABLE_SCHEMA "main"
++#define SHVAR_TABLE_SNAME SHVAR_TABLE_SCHEMA"."SHVAR_TABLE_NAME
++
++#ifndef SH_KV_STORE_NAME
++/* Name for table keeping user's saved parameters */
++# define SH_KV_STORE_NAME "SQLiteShell_KeyValuePairs"
++#endif
++#ifndef SH_KV_STORE_SCHEMA
++/* Schema name used to attach saved parameters DB during load/save */
++# define SH_KV_STORE_SCHEMA "SQLiteShell"
++#endif
++#define SH_KV_STORE_SNAME SH_KV_STORE_SCHEMA"."SH_KV_STORE_NAME
++
/* Create the TEMP table used to store parameter bindings */
--static void bind_table_init(ShellState *p){
-- int wrSchema = 0;
-- int defensiveMode = 0;
-- sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, -1, &defensiveMode);
-- sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0);
-- sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, -1, &wrSchema);
-- sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, 0);
-- sqlite3_exec(p->db,
-- "CREATE TABLE IF NOT EXISTS temp.sqlite_parameters(\n"
++static void param_table_init(sqlite3 *db){
++ DbProtectState dps = allow_sys_schema_change(db);
++ sqlite3_exec(db,
++ "CREATE TABLE IF NOT EXISTS "PARAM_TABLE_SNAME"(\n"
+ " key TEXT PRIMARY KEY,\n"
- " value\n"
++ " value,\n"
++ " uses INT DEFAULT ("SPTU_Binding")"
+ ") WITHOUT ROWID;",
+ 0, 0, 0);
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, wrSchema, 0);
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, defensiveMode, 0);
++ restore_sys_schema_protection( db, &dps );
++}
++
++/* Tell whether the above-created table exists, return true iff exists. */
++static int param_table_exists( sqlite3 *db ){
++ return sqlite3_table_column_metadata
++ (db, PARAM_TABLE_SCHEMA, PARAM_TABLE_NAME, 0, 0, 0, 0, 0, 0)==SQLITE_OK;
++}
++
++/* Create the shell DB table used to store shell variables or scripts */
++static int shvars_table_init(sqlite3 *db){
++ DbProtectState dps = allow_sys_schema_change(db);
++ int rc = sqlite3_exec(db,
++ "CREATE TABLE IF NOT EXISTS "SHVAR_TABLE_SNAME"(\n"
+ " key TEXT PRIMARY KEY,\n"
- " value\n"
++ " value,\n"
++ " uses INT DEFAULT ("SPTU_Script")"
+ ") WITHOUT ROWID;",
+ 0, 0, 0);
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, wrSchema, 0);
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, defensiveMode, 0);
++ restore_sys_schema_protection( db, &dps );
++ return rc!=SQLITE_OK;
++}
++
++/* Tell whether the above-created table exists, return true iff exists. */
++static int shvars_table_exists( sqlite3 *db ){
++ return sqlite3_table_column_metadata
++ (db, SHVAR_TABLE_SCHEMA, SHVAR_TABLE_NAME, 0, 0, 0, 0, 0, 0)==SQLITE_OK;
++}
++
++/* Make shell vars table exist. */
++static int ensure_shvars_table(sqlite3 *dbs){
++ if( shvars_table_exists(dbs) ) return SQLITE_OK;
++ else return shvars_table_init(dbs);
}
/*
const char *zSql, /* SQL to be evaluated */
char **pzErrMsg /* Error msg written here */
){
++ ShellInState *psi = ISS(psx);
sqlite3_stmt *pStmt = NULL; /* Statement to execute. */
+++ sqlite3_stmt *pExplain = NULL; /* For explain generate */
+++ char *zEQP = NULL; /* For explain report */
int rc = SQLITE_OK; /* Return Code */
int rc2;
const char *zLeftover; /* Tail of unprocessed SQL */
-- sqlite3 *db = pArg->db;
--
++ sqlite3 *db = DBX(psx);
-
+++ ResourceMark mark = holder_mark();
if( pzErrMsg ){
*pzErrMsg = NULL;
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
-- if( pArg->expert.pExpert ){
-- rc = expertHandleSQL(pArg, zSql, pzErrMsg);
-- return expertFinish(pArg, (rc!=SQLITE_OK), pzErrMsg);
++ if( psi->expert.pExpert ){
++ rc = expertHandleSQL(psi, zSql, pzErrMsg);
++ return expertFinish(psi, (rc!=SQLITE_OK), pzErrMsg);
}
#endif
---
+++ stmt_holder(pStmt); /* offset 0 */
+++ stmt_holder(pExplain); /* offset 1 */
+++ sstr_holder(zEQP); /* offset 2 */
while( zSql[0] && (SQLITE_OK == rc) ){
static const char *zStmtSql;
rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover);
if( SQLITE_OK != rc ){
+++ if( rc==SQLITE_NOMEM ) shell_out_of_memory();
if( pzErrMsg ){
*pzErrMsg = save_err_msg(db, "in prepare", rc, zSql);
+++ shell_check_oom(*pzErrMsg);
}
}else{
if( !pStmt ){
/* this happens for a comment or white-space */
-- zSql = zLeftover;
-- while( IsSpace(zSql[0]) ) zSql++;
++ zSql = skipWhite(zLeftover);
continue;
}
+++ swap_held(mark, 0, pStmt);
zStmtSql = sqlite3_sql(pStmt);
if( zStmtSql==0 ) zStmtSql = "";
-- while( IsSpace(zStmtSql[0]) ) zStmtSql++;
++ else zStmtSql = skipWhite(zStmtSql);
/* save off the prepared statment handle and reset row count */
-- if( pArg ){
-- pArg->pStmt = pStmt;
-- pArg->cnt = 0;
++ if( psx ){
++ psi->pStmt = pStmt;
++ psx->resultCount = 0;
}
/* Show the EXPLAIN QUERY PLAN if .eqp is on */
-- if( pArg && pArg->autoEQP && sqlite3_stmt_isexplain(pStmt)==0 ){
-- sqlite3_stmt *pExplain;
-- char *zEQP;
++ if( psx && psi->autoEQP && sqlite3_stmt_isexplain(pStmt)==0 ){
- sqlite3_stmt *pExplain;
- char *zEQP;
int triggerEQP = 0;
disable_debug_trace_modes();
sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, -1, &triggerEQP);
-- if( pArg->autoEQP>=AUTOEQP_trigger ){
++ if( psi->autoEQP>=AUTOEQP_trigger ){
sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 1, 0);
}
-- zEQP = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", zStmtSql);
++ zEQP = smprintf("EXPLAIN QUERY PLAN %s", zStmtSql);
shell_check_oom(zEQP);
+++ swap_held(mark, 2, zEQP);
rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
if( rc==SQLITE_OK ){
+++ swap_held(mark, 1, pExplain);
while( sqlite3_step(pExplain)==SQLITE_ROW ){
const char *zEQPLine = (const char*)sqlite3_column_text(pExplain,3);
int iEqpId = sqlite3_column_int(pExplain, 0);
int iParentId = sqlite3_column_int(pExplain, 1);
if( zEQPLine==0 ) zEQPLine = "";
-- if( zEQPLine[0]=='-' ) eqp_render(pArg, 0);
-- eqp_append(pArg, iEqpId, iParentId, zEQPLine);
++ if( zEQPLine[0]=='-' ) eqp_render(psi, 0);
++ eqp_append(psi, iEqpId, iParentId, zEQPLine);
}
-- eqp_render(pArg, 0);
++ eqp_render(psi, 0);
}
sqlite3_finalize(pExplain);
+++ swap_held(mark, 1, 0);
sqlite3_free(zEQP);
-- if( pArg->autoEQP>=AUTOEQP_full ){
+++ swap_held(mark, 2, 0);
++ if( psi->autoEQP>=AUTOEQP_full ){
/* Also do an EXPLAIN for ".eqp full" mode */
-- zEQP = sqlite3_mprintf("EXPLAIN %s", zStmtSql);
++ zEQP = smprintf("EXPLAIN %s", zStmtSql);
shell_check_oom(zEQP);
+++ swap_held(mark, 2, zEQP);
rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
if( rc==SQLITE_OK ){
-- pArg->cMode = MODE_Explain;
-- explain_data_prepare(pArg, pExplain);
-- exec_prepared_stmt(pArg, pExplain);
-- explain_data_delete(pArg);
+++ swap_held(mark, 1, pExplain);
++ explain_data_prepare(psi, pExplain);
++ psi->cMode = MODE_Explain;
++#if SHELL_DATAIO_EXT
++ {
++ ExportHandler *pexSave = psi->pActiveExporter;
++ psi->pActiveExporter = psi->pFreeformExporter;
++ exec_prepared_stmt(psx, pExplain);
++ psi->pActiveExporter = pexSave;
++ }
++#else
++ exec_prepared_stmt(psx, pExplain);
++#endif
++ explain_data_delete(psi);
}
sqlite3_finalize(pExplain);
+++ swap_held(mark, 1, 0);
sqlite3_free(zEQP);
+++ swap_held(mark, 2, 0);
}
-- if( pArg->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){
++ if( psi->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){
sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 0, 0);
/* Reprepare pStmt before reactiving trace modes */
sqlite3_finalize(pStmt);
sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
-- if( pArg ) pArg->pStmt = pStmt;
+++ swap_held(mark, 0, pStmt);
++ if( psx ) psi->pStmt = pStmt;
}
restore_debug_trace_modes();
}
** copy of the error message. Otherwise, set zSql to point to the
** next statement to execute. */
rc2 = sqlite3_finalize(pStmt);
+++ swap_held(mark, 0, 0);
if( rc!=SQLITE_NOMEM ) rc = rc2;
if( rc==SQLITE_OK ){
-- zSql = zLeftover;
-- while( IsSpace(zSql[0]) ) zSql++;
++ zSql = skipWhite(zLeftover);
}else if( pzErrMsg ){
*pzErrMsg = save_err_msg(db, "stepping", rc, 0);
}
}
}
} /* end while */
+++ CHECK_RETURN_EQUAL(0, holder_free(mark));
return rc;
}
return rc;
}
++/* Configure help text generation to have coalesced secondary help lines
++ * with trailing newlines on all help lines. This allow help text to be
++ * representable as an array of two C-strings per dot-command.
++ */
++DISPATCH_CONFIG[
++ HELP_COALESCE=1
++];
++#define HELP_TEXT_FMTP ".%s"
++#define HELP_TEXT_FMTS "%s"
++/* Above HELP_COALESCE config and HELP_TEXT_FMT PP vars must track.
++ * Alternative is 0, ".%s\n" and "%s\n" .
++ */
++
++/* Forward references */
++static int showHelp(FILE *out, const char *zPattern, ShellExState *);
++static DotCmdRC process_input(ShellInState *psx);
++static DotCommand *builtInCommand(int ix);
++
/*
--** Text of help messages.
++** Read the content of file zName into memory obtained from sqlite3_malloc64()
++** and return a pointer to the buffer. The caller is responsible for freeing
++** the memory.
++**
++** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes
++** read.
**
--** The help text for each individual command begins with a line that starts
--** with ".". Subsequent lines are supplemental information.
++** For convenience, a nul-terminator byte is always appended to the data read
++** from the file before the buffer is returned. This byte is not included in
++** the final value of (*pnByte), if applicable.
**
--** There must be two or more spaces between the end of the command and the
--** start of the description of what that command does.
++** NULL is returned if any error is encountered. The final value of *pnByte
++** is undefined in this case.
*/
--static const char *(azHelp[]) = {
--#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) \
-- && !defined(SQLITE_SHELL_FIDDLE)
-- ".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",
++static char *readFile(const char *zName, int *pnByte){
++ FILE *in = fopen(zName, "rb");
++ long nIn;
++ size_t nRead;
++ char *pBuf;
++ int rc;
++ if( in==0 ) return 0;
++ rc = fseek(in, 0, SEEK_END);
++ if( rc!=0 ){
++ raw_printf(stderr, "Error: '%s' not seekable\n", zName);
++ fclose(in);
++ return 0;
++ }
++ nIn = ftell(in);
++ rewind(in);
++ pBuf = sqlite3_malloc64( nIn+1 );
++ if( pBuf==0 ){
++ raw_printf(stderr, "Error: out of memory\n");
++ fclose(in);
++ return 0;
++ }
++ nRead = fread(pBuf, nIn, 1, in);
++ fclose(in);
++ if( nRead!=1 ){
++ sqlite3_free(pBuf);
++ raw_printf(stderr, "Error: cannot read '%s'\n", zName);
++ return 0;
++ }
++ pBuf[nIn] = 0;
++ if( pnByte ) *pnByte = nIn;
++ return pBuf;
++}
++
++#if defined(SQLITE_ENABLE_SESSION)
++/*
++** Close a single OpenSession object and release all of its associated
++** resources.
++*/
++static void session_close(OpenSession *pSession){
++ int i;
++ sqlite3session_delete(pSession->p);
++ sqlite3_free(pSession->zName);
++ for(i=0; i<pSession->nFilter; i++){
++ sqlite3_free(pSession->azFilter[i]);
++ }
++ sqlite3_free(pSession->azFilter);
++ memset(pSession, 0, sizeof(OpenSession));
++}
#endif
--#ifndef SQLITE_OMIT_AUTHORIZATION
-- ".auth ON|OFF Show authorizer callbacks",
++
++/*
++** Close all OpenSession objects and release all associated resources.
++*/
++#if defined(SQLITE_ENABLE_SESSION)
++static void session_close_all(ShellInState *psi, int i){
++ int j;
++ struct AuxDb *pAuxDb = i<0 ? psi->pAuxDb : &psi->aAuxDb[i];
++ for(j=0; j<pAuxDb->nSession; j++){
++ session_close(&pAuxDb->aSession[j]);
++ }
++ pAuxDb->nSession = 0;
++}
++#else
++# define session_close_all(X,Y)
#endif
--#ifndef SQLITE_SHELL_FIDDLE
-- ".backup ?DB? FILE Backup DB (default \"main\") to FILE",
-- " Options:",
-- " --append Use the appendvfs",
-- " --async Write to FILE without journal and fsync()",
++
++/*
++** Implementation of the xFilter function for an open session. Omit
++** any tables named by ".session filter" but let all other table through.
++*/
++#if defined(SQLITE_ENABLE_SESSION)
++static int session_filter(void *pCtx, const char *zTab){
++ OpenSession *pSession = (OpenSession*)pCtx;
++ int i;
++ for(i=0; i<pSession->nFilter; i++){
++ if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0;
++ }
++ return 1;
++}
#endif
-- ".bail on|off Stop after hitting an error. Default OFF",
-- ".binary on|off Turn binary output on or off. Default OFF",
--#ifndef SQLITE_SHELL_FIDDLE
-- ".cd DIRECTORY Change the working directory to DIRECTORY",
++
++#if SHELL_DYNAMIC_EXTENSION
++static int notify_subscribers(ShellInState *psi, NoticeKind nk, void *pvs) {
++ int six = 0;
++ int rcFlags = 0;
++ ShellExState *psx = XSS(psi);
++ while( six < psi->numSubscriptions ){
++ struct EventSubscription *pes = psi->pSubscriptions + six++;
++ rcFlags |= pes->eventHandler(pes->pvUserData, nk, pvs, psx);
++ }
++ return rcFlags;
++}
#endif
-- ".changes on|off Show number of rows changed by SQL",
++
++/*
++** Try to deduce the type of file for zName based on its content. Return
++** one of the SHELL_OPEN_* constants.
++**
++** If the file does not exist or is empty but its name looks like a ZIP
++** archive and the dfltZip flag is true, then assume it is a ZIP archive.
++** Otherwise, assume an ordinary database regardless of the filename if
++** the type cannot be determined from content.
++*/
++u8 deduceDatabaseType(const char *zName, int dfltZip){
++ FILE *f = fopen(zName, "rb");
++ size_t n;
++ u8 rc = SHELL_OPEN_UNSPEC;
++ char zBuf[100];
++ if( f==0 ){
++ if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
++ return SHELL_OPEN_ZIPFILE;
++ }else{
++ return SHELL_OPEN_NORMAL;
++ }
++ }
++ n = fread(zBuf, 16, 1, f);
++ if( n==1 && memcmp(zBuf, "SQLite format 3", 16)==0 ){
++ fclose(f);
++ return SHELL_OPEN_NORMAL;
++ }
++ fseek(f, -25, SEEK_END);
++ n = fread(zBuf, 25, 1, f);
++ if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){
++ rc = SHELL_OPEN_APPENDVFS;
++ }else{
++ fseek(f, -22, SEEK_END);
++ n = fread(zBuf, 22, 1, f);
++ if( n==1 && zBuf[0]==0x50 && zBuf[1]==0x4b && zBuf[2]==0x05
++ && zBuf[3]==0x06 ){
++ rc = SHELL_OPEN_ZIPFILE;
++ }else if( n==0 && dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
++ rc = SHELL_OPEN_ZIPFILE;
++ }
++ }
++ fclose(f);
++ return rc;
++}
++
++#ifndef SQLITE_OMIT_DESERIALIZE
++/*
++** Reconstruct an in-memory database using the output from the "dbtotxt"
++** program. Read content from the file in p->aAuxDb[].zDbFilename.
++** If p->aAuxDb[].zDbFilename is 0, then read from the present input.
++*/
++static unsigned char *readHexDb(ShellInState *psi, int *pnData){
++ unsigned char *a = 0;
++ int n = 0;
++ int pgsz = 0;
++ int iOffset = 0;
++ int j, k, nlError;
++ int rc;
++ static const char *zEndMarker = "| end ";
++ const char *zDbFilename = psi->pAuxDb->zDbFilename;
++ /* Need next two objects only if redirecting input to get the hex. */
++ InSource inRedir = INSOURCE_FILE_REDIR(0, zDbFilename, psi->pInSource);
++ unsigned int x[16];
++ char zLine[1000];
++ if( zDbFilename ){
++ inRedir.inFile = fopen(zDbFilename, "r");
++ if( inRedir.inFile==0 ){
++ utf8_printf(STD_ERR, "cannot open \"%s\" for reading\n", zDbFilename);
++ return 0;
++ }
++ psi->pInSource = &inRedir;
++ }else{
++ /* Will read hex DB lines inline from present input, without redirect. */
++ if( INSOURCE_IS_INTERACTIVE(psi->pInSource) ){
++ printf("Reading hex DB from \"%s\", until end-marker input like:\n%s\n",
++ psi->pInSource->zSourceSay, zEndMarker);
++ fflush(STD_OUT);
++ }
++ }
++ *pnData = 0;
++ if( strLineGet(zLine,sizeof(zLine), psi->pInSource)==0 ) goto readHexDb_error;
++ rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz);
++ if( rc!=2 ) goto readHexDb_error;
++ if( n<0 ) goto readHexDb_error;
++ if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ) goto readHexDb_error;
++ n = (n+pgsz-1)&~(pgsz-1); /* Round n up to the next multiple of pgsz */
++ a = sqlite3_malloc( n ? n : 1 );
++ shell_check_oom(a);
++ memset(a, 0, n);
++ if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){
++ utf8_printf(STD_ERR, "invalid pagesize\n");
++ goto readHexDb_error;
++ }
++ while( strLineGet(zLine,sizeof(zLine), psi->pInSource)!=0 ){
++ rc = sscanf(zLine, "| page %d offset %d", &j, &k);
++ if( rc==2 ){
++ iOffset = k;
++ continue;
++ }
++ if( cli_strncmp(zLine, zEndMarker, 6)==0 ){
++ break;
++ }
++ rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
++ &j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7],
++ &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]);
++ if( rc==17 ){
++ k = iOffset+j;
++ if( k+16<=n && k>=0 ){
++ int ii;
++ for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff;
++ }
++ }
++ }
++ *pnData = n; /* Record success and size. */
++ readHexDb_cleanup:
++ if( psi->pInSource==&inRedir ){
++ fclose( inRedir.inFile );
++ psi->pInSource = inRedir.pFrom;
++ }
++ return a;
++
++ readHexDb_error:
++ nlError = psi->pInSource->lineno;
++ if( psi->pInSource!=&inRedir ){
++ /* Since taking input inline, consume through its end marker. */
++ while( strLineGet(zLine, sizeof(zLine), psi->pInSource)!=0 ){
++ if(cli_strncmp(zLine, zEndMarker, 6)==0 ) break;
++ }
++ }
++ sqlite3_free(a);
++ a = 0;
++ utf8_printf(STD_ERR,"Error on line %d within --hexdb input\n", nlError);
++ goto readHexDb_cleanup;
++}
++#endif /* SQLITE_OMIT_DESERIALIZE */
++
++/*
++** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X.
++*/
++static void shellUSleepFunc(
++ sqlite3_context *context,
++ int argcUnused,
++ sqlite3_value **argv
++){
++ int sleep = sqlite3_value_int(argv[0]);
++ (void)argcUnused;
++ sqlite3_sleep(sleep/1000);
++ sqlite3_result_int(context, sleep);
++}
++
++/* Flags for open_db().
++**
++** The default behavior of open_db() is to exit(1) if the database fails to
++** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error
++** but still returns without calling exit.
++**
++** The OPEN_DB_ZIPFILE flag causes open_db() to prefer to open files as a
++** ZIP archive if the file does not exist or is empty and its name matches
++** the *.zip pattern.
++*/
++#define OPEN_DB_KEEPALIVE 0x001 /* Return after error if true */
++#define OPEN_DB_ZIPFILE 0x002 /* Open as ZIP if name matches *.zip */
++
++/*
++** Make sure the database is open. If it is not, then open it. If
++** the database fails to open, print an error message and exit.
++*/
++static void open_db(ShellExState *psx, int openFlags){
++ ShellInState *psi = ISS(psx);
++ if( DBX(psx)==0 ){
++ sqlite3 **pDb = &DBX(psx);
++ const char *zDbFilename = psi->pAuxDb->zDbFilename;
++ if( psi->openMode==SHELL_OPEN_UNSPEC ){
++ if( zDbFilename==0 || zDbFilename[0]==0 ){
++ psi->openMode = SHELL_OPEN_NORMAL;
++ }else{
++ psi->openMode = deduceDatabaseType(zDbFilename,
++ (openFlags & OPEN_DB_ZIPFILE)!=0);
++ }
++ }
++ switch( psi->openMode ){
++ case SHELL_OPEN_APPENDVFS: {
++ sqlite3_open_v2
++ (zDbFilename, pDb,
++ SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|psi->openFlags,
++ "apndvfs");
++ break;
++ }
++ case SHELL_OPEN_HEXDB:
++ case SHELL_OPEN_DESERIALIZE: {
++ sqlite3_open(0, pDb);
++ break;
++ }
++ case SHELL_OPEN_ZIPFILE: {
++ sqlite3_open(":memory:", pDb);
++ break;
++ }
++ case SHELL_OPEN_READONLY: {
++ sqlite3_open_v2(zDbFilename, pDb,
++ SQLITE_OPEN_READONLY|psi->openFlags, 0);
++ break;
++ }
++ case SHELL_OPEN_UNSPEC:
++ case SHELL_OPEN_NORMAL: {
++ sqlite3_open_v2(zDbFilename, pDb,
++ SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|psi->openFlags, 0);
++ break;
++ }
++ }
++ globalDb = DBX(psx);
++ if( DBX(psx)==0 || SQLITE_OK!=sqlite3_errcode(DBX(psx)) ){
++ const char *zWhy = (DBX(psx)==0)? "(?)" : sqlite3_errmsg(DBX(psx));
++ utf8_printf(STD_ERR,"Error: unable to open database \"%s\": %s\n",
++ zDbFilename, zWhy);
++ if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){
++ exit(1);
++ }
++ sqlite3_close(DBX(psx));
++ sqlite3_open(":memory:", &DBX(psx));
++ if( DBX(psx)==0 || SQLITE_OK!=sqlite3_errcode(DBX(psx)) ){
++ utf8_printf(stderr,
++ "Also: unable to open substitute in-memory database.\n"
++ );
++ exit(1);
++ }else{
++ utf8_printf(stderr,
++ "Notice: using substitute in-memory database instead of \"%s\"\n",
++ zDbFilename);
++ }
++ }
++ sqlite3_db_config(globalDb, SQLITE_DBCONFIG_STMT_SCANSTATUS,(int)0,(int*)0);
++
++ /* Reflect the use or absence of --unsafe-testing invocation. */
++ {
++ int testmode_on = ShellHasFlag(psx,SHFLG_TestingMode);
++ sqlite3_db_config(globalDb, SQLITE_DBCONFIG_TRUSTED_SCHEMA,testmode_on,0);
++ sqlite3_db_config(globalDb, SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0);
++ }
++#ifndef SQLITE_OMIT_LOAD_EXTENSION
++ sqlite3_enable_load_extension(globalDb, 1);
++#endif
++ sqlite3_shathree_init(globalDb, 0, 0);
++ sqlite3_uint_init(globalDb, 0, 0);
++ sqlite3_decimal_init(globalDb, 0, 0);
++ sqlite3_base64_init(globalDb, 0, 0);
++ sqlite3_base85_init(globalDb, 0, 0);
++ sqlite3_regexp_init(globalDb, 0, 0);
++ sqlite3_ieee_init(globalDb, 0, 0);
++ sqlite3_series_init(globalDb, 0, 0);
#ifndef SQLITE_SHELL_FIDDLE
-- ".check GLOB Fail if output since .testcase does not match",
-- ".clone NEWDB Clone data into NEWDB from the existing database",
++ sqlite3_fileio_init(globalDb, 0, 0);
++ sqlite3_completion_init(globalDb, 0, 0);
#endif
-- ".connection [close] [#] Open or close an auxiliary database connection",
-- ".databases List names and files of attached databases",
-- ".dbconfig ?op? ?val? List or change sqlite3_db_config() options",
#if SQLITE_SHELL_HAVE_RECOVER
-- ".dbinfo ?DB? Show status information about the database",
- #endif
- ".dump ?OBJECTS? Render database content as SQL",
- " Options:",
- " --data-only Output only INSERT statements",
- " --newlines Allow unescaped newline characters in output",
- " --nosys Omit system tables (ex: \"sqlite_stat1\")",
- " --preserve-rowids Include ROWID values in the output",
- " OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump",
- " Additional LIKE patterns can be given in subsequent arguments",
- ".echo on|off Turn command echo on or off",
- ".eqp on|off|full|... Enable or disable automatic EXPLAIN QUERY PLAN",
- " Other Modes:",
- #ifdef SQLITE_DEBUG
- " test Show raw EXPLAIN QUERY PLAN output",
- " trace Like \"full\" but enable \"PRAGMA vdbe_trace\"",
++ sqlite3_dbdata_init(globalDb, 0, 0);
#endif
- " trigger Like \"full\" but also show trigger bytecode",
- #ifndef SQLITE_SHELL_FIDDLE
- ".excel Display the output of next command in spreadsheet",
- " --bom Put a UTF8 byte-order mark on intermediate file",
- #endif
- #ifndef SQLITE_SHELL_FIDDLE
- ".exit ?CODE? Exit this program with return-code CODE",
- ".dump ?OBJECTS? Render database content as SQL",
- " Options:",
- " --data-only Output only INSERT statements",
- " --newlines Allow unescaped newline characters in output",
- " --nosys Omit system tables (ex: \"sqlite_stat1\")",
- " --preserve-rowids Include ROWID values in the output",
- " OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump",
- " Additional LIKE patterns can be given in subsequent arguments",
- ".echo on|off Turn command echo on or off",
- ".eqp on|off|full|... Enable or disable automatic EXPLAIN QUERY PLAN",
- " Other Modes:",
-#ifdef SQLITE_DEBUG
- " test Show raw EXPLAIN QUERY PLAN output",
- " trace Like \"full\" but enable \"PRAGMA vdbe_trace\"",
++#ifdef SQLITE_HAVE_ZLIB
++ if( !psi->bSafeModeFuture ){
++ sqlite3_zipfile_init(globalDb, 0, 0);
++ sqlite3_sqlar_init(globalDb, 0, 0);
++ }
#endif
- ".expert EXPERIMENTAL. Suggest indexes for queries",
- ".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto",
- ".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",
- ".headers on|off Turn display of headers on or off",
- ".help ?-all? ?PATTERN? Show help text for PATTERN",
- " trigger Like \"full\" but also show trigger bytecode",
--#ifndef SQLITE_SHELL_FIDDLE
- ".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.",
- ".excel Display the output of next command in spreadsheet",
- " --bom Put a UTF8 byte-order mark on intermediate file",
++
++#ifdef SQLITE_SHELL_EXTFUNCS
++ /* Create a preprocessing mechanism for extensions to make
++ * their own provisions for being built into the shell.
++ * This is a short-span macro. See further below for usage.
++ */
++#define SHELL_SUB_MACRO(base, variant) base ## _ ## variant
++#define SHELL_SUBMACRO(base, variant) SHELL_SUB_MACRO(base, variant)
++ /* Let custom-included extensions get their ..._init() called.
++ * The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause
++ * the extension's sqlite3_*_init( db, pzErrorMsg, pApi )
++ * inititialization routine to be called.
++ */
++ {
++ int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db);
++ /* Let custom-included extensions expose their functionality.
++ * The WHATEVER_EXPOSE( db, pzErrorMsg ) macro should cause
++ * the SQL functions, virtual tables, collating sequences or
++ * VFS's implemented by the extension to be registered.
++ */
++ if( irc==SQLITE_OK
++ || irc==SQLITE_OK_LOAD_PERMANENTLY ){
++ SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, EXPOSE)(p->db, 0);
++ }
++#undef SHELL_SUB_MACRO
++#undef SHELL_SUBMACRO
++ }
#endif
-#ifndef SQLITE_SHELL_FIDDLE
- ".exit ?CODE? Exit this program with return-code CODE",
-#endif
- ".expert EXPERIMENTAL. Suggest indexes for queries",
- ".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto",
- ".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",
- ".headers on|off Turn display of headers on or off",
- ".help ?-all? ?PATTERN? Show help text for PATTERN",
-#ifndef SQLITE_SHELL_FIDDLE
- ".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.",
-#endif
--#ifndef SQLITE_OMIT_TEST_CONTROL
-- ",imposter INDEX TABLE Create imposter table TABLE on index INDEX",
--#endif
-- ".indexes ?TABLE? Show names of indexes",
-- " If TABLE is specified, only show indexes for",
-- " tables matching TABLE using the LIKE operator.",
--#ifdef SQLITE_ENABLE_IOTRACE
-- ",iotrace FILE Enable I/O diagnostic logging to FILE",
--#endif
-- ".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",
--#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE)
-- ".load FILE ?ENTRY? Load an extension library",
--#endif
--#if !defined(SQLITE_SHELL_FIDDLE)
-- ".log FILE|on|off Turn logging on or off. FILE can be stderr/stdout",
--#else
-- ".log on|off Turn logging on or off.",
--#endif
-- ".mode MODE ?OPTIONS? Set output mode",
-- " MODE is one of:",
-- " ascii Columns/rows delimited by 0x1F and 0x1E",
-- " box Tables using unicode box-drawing characters",
-- " csv Comma-separated values",
-- " column Output in columns. (See .width)",
-- " html HTML <table> code",
-- " insert SQL insert statements for TABLE",
-- " json Results in a JSON array",
-- " line One value per line",
-- " list Values delimited by \"|\"",
-- " markdown Markdown table format",
-- " qbox Shorthand for \"box --wrap 60 --quote\"",
-- " quote Escape answers as for SQL",
-- " table ASCII-art table",
-- " tabs Tab-separated values",
-- " tcl TCL list elements",
-- " OPTIONS: (for columnar modes or insert mode):",
-- " --wrap N Wrap output lines to no longer than N characters",
-- " --wordwrap B Wrap or not at word boundaries per B (on/off)",
-- " --ww Shorthand for \"--wordwrap 1\"",
-- " --quote Quote output text as SQL literals",
-- " --noquote Do not quote output text",
-- " TABLE The name of SQL table used for \"insert\" mode",
--#ifndef SQLITE_SHELL_FIDDLE
-- ".nonce STRING Suspend safe mode for one command if nonce matches",
--#endif
-- ".nullvalue STRING Use STRING in place of NULL values",
--#ifndef SQLITE_SHELL_FIDDLE
-- ".once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE",
-- " If FILE begins with '|' then open as a pipe",
-- " --bom Put a UTF8 byte-order mark at the beginning",
-- " -e Send output to the system text editor",
-- " -x Send output as CSV to a spreadsheet (same as \".excel\")",
-- /* Note that .open is (partially) available in WASM builds but is
-- ** currently only intended to be used by the fiddle tool, not
-- ** end users, so is "undocumented." */
-- ".open ?OPTIONS? ?FILE? Close existing database and reopen FILE",
-- " Options:",
-- " --append Use appendvfs to append database to the end of FILE",
++
++ sqlite3_create_function(globalDb, "shell_add_schema", 3, SQLITE_UTF8, 0,
++ shellAddSchemaName, 0, 0);
++ sqlite3_create_function(globalDb, "shell_module_schema", 1, SQLITE_UTF8, 0,
++ shellModuleSchema, 0, 0);
++ sqlite3_create_function(globalDb, "shell_putsnl", 1, SQLITE_UTF8, psx,
++ shellPutsFunc, 0, 0);
++ sqlite3_create_function(globalDb, "usleep",1,SQLITE_UTF8, 0,
++ shellUSleepFunc, 0, 0);
++#ifndef SQLITE_NOHAVE_SYSTEM
++ sqlite3_create_function(globalDb, "edit", 1, SQLITE_UTF8, 0,
++ editFunc, 0, 0);
++ sqlite3_create_function(globalDb, "edit", 2, SQLITE_UTF8, 0,
++ editFunc, 0, 0);
#endif
++ if( psi->openMode==SHELL_OPEN_ZIPFILE ){
++ char *zSql = smprintf("CREATE VIRTUAL TABLE zip USING zipfile(%Q);",
++ zDbFilename);
++ shell_check_oom(zSql);
++ sqlite3_exec(DBX(psx), zSql, 0, 0, 0);
++ sqlite3_free(zSql);
++ }
#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",
--#ifndef SQLITE_SHELL_FIDDLE
-- ".output ?FILE? Send output to FILE or stdout if FILE is omitted",
-- " If FILE begins with '|' then open it as a pipe.",
-- " 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",
--#endif
-- ".parameter CMD ... Manage SQL parameter bindings",
-- " clear Erase all bindings",
-- " init Initialize the TEMP table that holds bindings",
-- " list List the current parameter bindings",
-- " set PARAMETER VALUE Given SQL parameter PARAMETER a value of VALUE",
-- " PARAMETER should start with one of: $ : @ ?",
-- " unset PARAMETER Remove PARAMETER from the binding table",
-- ".print STRING... Print literal STRING",
--#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
-- ".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",
--#endif
-- ".prompt MAIN CONTINUE Replace the standard prompts",
--#ifndef SQLITE_SHELL_FIDDLE
-- ".quit Stop interpreting input stream, exit if primary.",
-- ".read FILE Read input from FILE or command output",
-- " If FILE begins with \"|\", it is a command that generates the input.",
--#endif
--#if SQLITE_SHELL_HAVE_RECOVER
-- ".recover Recover as much data as possible from corrupt db.",
-- " --ignore-freelist Ignore pages that appear to be on db freelist",
-- " --lost-and-found TABLE Alternative name for the lost-and-found table",
-- " --no-rowids Do not attempt to recover rowid values",
-- " that are not also INTEGER PRIMARY KEYs",
--#endif
--#ifndef SQLITE_SHELL_FIDDLE
-- ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE",
-- ".save ?OPTIONS? FILE Write database to FILE (an alias for .backup ...)",
--#endif
-- ".scanstats on|off|est Turn sqlite3_stmt_scanstatus() metrics on or off",
-- ".schema ?PATTERN? Show the CREATE statements matching PATTERN",
-- " Options:",
-- " --indent Try to pretty-print the schema",
-- " --nosys Omit objects whose names start with \"sqlite_\"",
-- ",selftest ?OPTIONS? Run tests defined in the SELFTEST table",
-- " Options:",
-- " --init Create a new SELFTEST table",
-- " -v Verbose output",
-- ".separator COL ?ROW? Change the column and row separators",
--#if defined(SQLITE_ENABLE_SESSION)
-- ".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.",
--#endif
-- ".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",
--#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
-- ".shell CMD ARGS... Run CMD ARGS... in a system shell",
--#endif
-- ".show Show the current values for various settings",
-- ".stats ?ARG? Show stats or turn stats on or off",
-- " off Turn off automatic stat display",
-- " on Turn on automatic stat display",
-- " stmt Show statement stats",
-- " vmstep Show the virtual machine step count only",
--#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
-- ".system CMD ARGS... Run CMD ARGS... in a system shell",
--#endif
-- ".tables ?TABLE? List names of tables matching LIKE pattern TABLE",
--#ifndef SQLITE_SHELL_FIDDLE
-- ",testcase NAME Begin redirecting output to 'testcase-out.txt'",
--#endif
-- ",testctrl CMD ... Run various sqlite3_test_control() operations",
-- " Run \".testctrl\" with no arguments for details",
-- ".timeout MS Try opening locked tables for MS milliseconds",
-- ".timer on|off Turn SQL timer on or off",
--#ifndef SQLITE_OMIT_TRACE
-- ".trace ?OPTIONS? Output each SQL statement as it is run",
-- " FILE Send output to FILE",
-- " stdout Send output to stdout",
-- " stderr Send output to stderr",
-- " off Disable tracing",
-- " --expanded Expand query parameters",
--#ifdef SQLITE_ENABLE_NORMALIZE
-- " --normalized Normal the SQL statements",
--#endif
-- " --plain Show SQL as it is input",
-- " --stmt Trace statement execution (SQLITE_TRACE_STMT)",
-- " --profile Profile statements (SQLITE_TRACE_PROFILE)",
-- " --row Trace each row (SQLITE_TRACE_ROW)",
-- " --close Trace connection close (SQLITE_TRACE_CLOSE)",
--#endif /* SQLITE_OMIT_TRACE */
--#ifdef SQLITE_DEBUG
-- ".unmodule NAME ... Unregister virtual table modules",
-- " --allexcept Unregister everything except those named",
--#endif
-- ".version Show source, library and compiler versions",
-- ".vfsinfo ?AUX? Information about the top-level VFS",
-- ".vfslist List all available VFSes",
-- ".vfsname ?AUX? Print the name of the VFS stack",
-- ".width NUM1 NUM2 ... Set minimum column widths for columnar output",
-- " Negative values right-justify",
--};
--
--/*
--** Output help text.
--**
--** zPattern describes the set of commands for which help text is provided.
--** If zPattern is NULL, then show all commands, but only give a one-line
--** description of each.
--**
--** Return the number of matches.
--*/
--static int showHelp(FILE *out, const char *zPattern){
-- int i = 0;
-- int j = 0;
-- int n = 0;
-- char *zPat;
-- if( zPattern==0
-- || zPattern[0]=='0'
-- || cli_strcmp(zPattern,"-a")==0
-- || cli_strcmp(zPattern,"-all")==0
-- || cli_strcmp(zPattern,"--all")==0
-- ){
-- enum HelpWanted { HW_NoCull = 0, HW_SummaryOnly = 1, HW_Undoc = 2 };
-- enum HelpHave { HH_Undoc = 2, HH_Summary = 1, HH_More = 0 };
-- /* Show all or most commands
-- ** *zPattern==0 => summary of documented commands only
-- ** *zPattern=='0' => whole help for undocumented commands
-- ** Otherwise => whole help for documented commands
-- */
-- enum HelpWanted hw = HW_SummaryOnly;
-- enum HelpHave hh = HH_More;
-- if( zPattern!=0 ){
-- hw = (*zPattern=='0')? HW_NoCull|HW_Undoc : HW_NoCull;
-- }
-- for(i=0; i<ArraySize(azHelp); i++){
-- switch( azHelp[i][0] ){
-- case ',':
-- hh = HH_Summary|HH_Undoc;
-- break;
-- case '.':
-- hh = HH_Summary;
-- break;
-- default:
-- hh &= ~HH_Summary;
-- break;
-- }
-- if( ((hw^hh)&HH_Undoc)==0 ){
-- if( (hh&HH_Summary)!=0 ){
-- utf8_printf(out, ".%s\n", azHelp[i]+1);
-- ++n;
-- }else if( (hw&HW_SummaryOnly)==0 ){
-- utf8_printf(out, "%s\n", azHelp[i]);
++ else
++ if( psi->openMode==SHELL_OPEN_DESERIALIZE
++ || psi->openMode==SHELL_OPEN_HEXDB ){
++ int rc;
++ int nData = 0;
++ unsigned char *aData;
++ if( psi->openMode==SHELL_OPEN_DESERIALIZE ){
++ aData = (unsigned char*)readFile(zDbFilename, &nData);
++ }else{
++ aData = readHexDb(psi, &nData);
++ if( aData==0 ){
++ return;
}
}
-- }
-- }else{
-- /* Seek documented commands for which zPattern is an exact prefix */
-- zPat = sqlite3_mprintf(".%s*", zPattern);
-- shell_check_oom(zPat);
-- for(i=0; i<ArraySize(azHelp); i++){
-- if( sqlite3_strglob(zPat, azHelp[i])==0 ){
-- utf8_printf(out, "%s\n", azHelp[i]);
-- j = i+1;
-- n++;
++ rc = sqlite3_deserialize(DBX(psx), "main", aData, nData, nData,
++ SQLITE_DESERIALIZE_RESIZEABLE |
++ SQLITE_DESERIALIZE_FREEONCLOSE);
++ if( rc ){
++ utf8_printf(STD_ERR, "Error: sqlite3_deserialize() returns %d\n", rc);
}
-- }
-- sqlite3_free(zPat);
-- if( n ){
-- if( n==1 ){
-- /* when zPattern is a prefix of exactly one command, then include
-- ** the details of that command, which should begin at offset j */
-- while( j<ArraySize(azHelp)-1 && azHelp[j][0]==' ' ){
-- utf8_printf(out, "%s\n", azHelp[j]);
-- j++;
-- }
++ if( psi->szMax>0 ){
++ sqlite3_file_control(DBX(psx), "main", SQLITE_FCNTL_SIZE_LIMIT,
++ &psi->szMax);
}
-- return n;
}
-- /* Look for documented commands that contain zPattern anywhere.
-- ** Show complete text of all documented commands that match. */
-- zPat = sqlite3_mprintf("%%%s%%", zPattern);
-- shell_check_oom(zPat);
-- for(i=0; i<ArraySize(azHelp); i++){
-- if( azHelp[i][0]==',' ){
-- while( i<ArraySize(azHelp)-1 && azHelp[i+1][0]==' ' ) ++i;
-- continue;
-- }
-- if( azHelp[i][0]=='.' ) j = i;
-- if( sqlite3_strlike(zPat, azHelp[i], 0)==0 ){
-- utf8_printf(out, "%s\n", azHelp[j]);
-- while( j<ArraySize(azHelp)-1 && azHelp[j+1][0]==' ' ){
-- j++;
-- utf8_printf(out, "%s\n", azHelp[j]);
-- }
-- i = j;
-- n++;
++#endif
++ if( DBX(psx)!=0 ){
++ if( psi->bSafeModeFuture ){
++ sqlite3_set_authorizer(DBX(psx), safeModeAuth, psx);
}
++ sqlite3_db_config(
++ DBX(psx), SQLITE_DBCONFIG_STMT_SCANSTATUS, psi->scanstatsOn,(int*)0);
}
-- sqlite3_free(zPat);
++#if SHELL_DYNAMIC_EXTENSION
++ notify_subscribers(psi, NK_DbUserAppeared, DBX(psx));
++#endif
}
-- return n;
}
--/* Forward reference */
--static int process_input(ShellState *p);
--
/*
--** Read the content of file zName into memory obtained from sqlite3_malloc64()
--** and return a pointer to the buffer. The caller is responsible for freeing
--** the memory.
--**
--** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes
--** read.
--**
--** For convenience, a nul-terminator byte is always appended to the data read
--** from the file before the buffer is returned. This byte is not included in
--** the final value of (*pnByte), if applicable.
--**
--** NULL is returned if any error is encountered. The final value of *pnByte
--** is undefined in this case.
++** Attempt to close the database connection. Report errors.
*/
--static char *readFile(const char *zName, int *pnByte){
-- FILE *in = fopen(zName, "rb");
-- long nIn;
-- size_t nRead;
-- char *pBuf;
++void close_db(sqlite3 *db){
int rc;
-- if( in==0 ) return 0;
-- rc = fseek(in, 0, SEEK_END);
-- if( rc!=0 ){
-- raw_printf(stderr, "Error: '%s' not seekable\n", zName);
-- fclose(in);
-- return 0;
-- }
-- nIn = ftell(in);
-- rewind(in);
-- pBuf = sqlite3_malloc64( nIn+1 );
-- if( pBuf==0 ){
-- raw_printf(stderr, "Error: out of memory\n");
-- fclose(in);
-- return 0;
++ if( db==globalDb ){
+++ /* This should only block for the time needed to handle ^C interrupt. */
++ sqlite3_mutex_enter(pGlobalDbLock);
++ globalDb = 0;
++ rc = sqlite3_close(db);
++ sqlite3_mutex_leave(pGlobalDbLock);
++ }else{
++ rc = sqlite3_close(db);
}
-- nRead = fread(pBuf, nIn, 1, in);
-- fclose(in);
-- if( nRead!=1 ){
-- sqlite3_free(pBuf);
-- raw_printf(stderr, "Error: cannot read '%s'\n", zName);
-- return 0;
++ if( rc ){
++ utf8_printf(STD_ERR, "Error: sqlite3_close() returns %d: %s\n",
++ rc, sqlite3_errmsg(db));
}
-- pBuf[nIn] = 0;
-- if( pnByte ) *pnByte = nIn;
-- return pBuf;
}
--#if defined(SQLITE_ENABLE_SESSION)
++#if HAVE_READLINE || HAVE_EDITLINE
/*
--** Close a single OpenSession object and release all of its associated
--** resources.
++** Readline completion callbacks
*/
--static void session_close(OpenSession *pSession){
-- int i;
-- sqlite3session_delete(pSession->p);
-- sqlite3_free(pSession->zName);
-- for(i=0; i<pSession->nFilter; i++){
-- sqlite3_free(pSession->azFilter[i]);
++static char *readline_completion_generator(const char *text, int state){
++ static sqlite3_stmt *pStmt = 0;
++ char *zRet;
++ if( state==0 ){
++ char *zSql;
++ sqlite3_finalize(pStmt);
++ zSql = smprintf("SELECT DISTINCT candidate COLLATE nocase"
++ " FROM completion(%Q) ORDER BY 1", text);
++ shell_check_oom(zSql);
++ sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0);
++ sqlite3_free(zSql);
}
-- sqlite3_free(pSession->azFilter);
-- memset(pSession, 0, sizeof(OpenSession));
--}
--#endif
--
--/*
--** Close all OpenSession objects and release all associated resources.
--*/
--#if defined(SQLITE_ENABLE_SESSION)
--static void session_close_all(ShellState *p, int i){
-- int j;
-- struct AuxDb *pAuxDb = i<0 ? p->pAuxDb : &p->aAuxDb[i];
-- for(j=0; j<pAuxDb->nSession; j++){
-- session_close(&pAuxDb->aSession[j]);
++ if( sqlite3_step(pStmt)==SQLITE_ROW ){
++ const char *z = (const char*)sqlite3_column_text(pStmt,0);
++ if( z!=0 ){
++ zRet = strdup(z);
++ shell_check_oom(zRet);
++ }
++ }else{
++ sqlite3_finalize(pStmt);
++ pStmt = 0;
++ zRet = 0;
}
-- pAuxDb->nSession = 0;
++ return zRet;
++}
++static char **readline_completion(const char *zText, int iStart, int iEnd){
++ (void)iStart;
++ (void)iEnd;
++ rl_attempted_completion_over = 1;
++ return rl_completion_matches(zText, readline_completion_generator);
}
--#else
--# define session_close_all(X,Y)
--#endif
++#elif HAVE_LINENOISE
/*
--** Implementation of the xFilter function for an open session. Omit
--** any tables named by ".session filter" but let all other table through.
++** Linenoise completion callback
*/
--#if defined(SQLITE_ENABLE_SESSION)
--static int session_filter(void *pCtx, const char *zTab){
-- OpenSession *pSession = (OpenSession*)pCtx;
-- int i;
-- for(i=0; i<pSession->nFilter; i++){
-- if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0;
++static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){
++ i64 nLine = strlen(zLine);
++ i64 i, iStart;
++ sqlite3_stmt *pStmt = 0;
++ char *zSql;
++ char zBuf[1000];
++
++ if( nLine>(i64)sizeof(zBuf)-30 ) return;
++ if( zLine[0]=='.' || zLine[0]=='#') return;
++ for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){}
++ if( i==nLine-1 ) return;
++ iStart = i+1;
++ memcpy(zBuf, zLine, iStart);
++ zSql = smprintf("SELECT DISTINCT candidate COLLATE nocase"
++ " FROM completion(%Q,%Q) ORDER BY 1",
++ &zLine[iStart], zLine);
++ shell_check_oom(zSql);
++ sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0);
++ sqlite3_free(zSql);
++ sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */
++ while( sqlite3_step(pStmt)==SQLITE_ROW ){
++ const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0);
++ int nCompletion = sqlite3_column_bytes(pStmt, 0);
++ if( iStart+nCompletion < (i64)sizeof(zBuf)-1 && zCompletion ){
++ memcpy(zBuf+iStart, zCompletion, nCompletion+1);
++ linenoiseAddCompletion(lc, zBuf);
++ }
}
-- return 1;
++ sqlite3_finalize(pStmt);
}
#endif
/*
--** Try to deduce the type of file for zName based on its content. Return
--** one of the SHELL_OPEN_* constants.
++** Do C-language style escape sequence translation.
**
--** If the file does not exist or is empty but its name looks like a ZIP
--** archive and the dfltZip flag is true, then assume it is a ZIP archive.
--** Otherwise, assume an ordinary database regardless of the filename if
--** the type cannot be determined from content.
++** \a -> alarm
++** \b -> backspace
++** \t -> tab
++** \n -> newline
++** \v -> vertical tab
++** \f -> form feed
++** \r -> carriage return
++** \s -> space
++** \" -> "
++** \' -> '
++** \\ -> backslash
++** \NNN -> ascii character NNN in octal
++** \xHH -> ascii character HH in hexadecimal
*/
--int deduceDatabaseType(const char *zName, int dfltZip){
-- FILE *f = fopen(zName, "rb");
-- size_t n;
-- int rc = SHELL_OPEN_UNSPEC;
-- char zBuf[100];
-- if( f==0 ){
-- if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
-- return SHELL_OPEN_ZIPFILE;
-- }else{
-- return SHELL_OPEN_NORMAL;
-- }
-- }
-- n = fread(zBuf, 16, 1, f);
-- if( n==1 && memcmp(zBuf, "SQLite format 3", 16)==0 ){
-- fclose(f);
-- return SHELL_OPEN_NORMAL;
-- }
-- fseek(f, -25, SEEK_END);
-- n = fread(zBuf, 25, 1, f);
-- if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){
-- rc = SHELL_OPEN_APPENDVFS;
-- }else{
-- fseek(f, -22, SEEK_END);
-- n = fread(zBuf, 22, 1, f);
-- if( n==1 && zBuf[0]==0x50 && zBuf[1]==0x4b && zBuf[2]==0x05
-- && zBuf[3]==0x06 ){
-- rc = SHELL_OPEN_ZIPFILE;
-- }else if( n==0 && dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
-- rc = SHELL_OPEN_ZIPFILE;
++static void resolve_backslashes(char *z){
++ int i, j;
++ char c;
++ while( *z && *z!='\\' ) z++;
++ for(i=j=0; (c = z[i])!=0; i++, j++){
++ if( c=='\\' && z[i+1]!=0 ){
++ c = z[++i];
++ if( c=='a' ){
++ c = '\a';
++ }else if( c=='b' ){
++ c = '\b';
++ }else if( c=='t' ){
++ c = '\t';
++ }else if( c=='n' ){
++ c = '\n';
++ }else if( c=='v' ){
++ c = '\v';
++ }else if( c=='f' ){
++ c = '\f';
++ }else if( c=='r' ){
++ c = '\r';
++ }else if( c=='"' ){
++ c = '"';
++ }else if( c=='\'' ){
++ c = '\'';
++ }else if( c=='\\' ){
++ c = '\\';
++ }else if( c=='x' ){
++ int nhd = 0, hdv;
++ u8 hv = 0;
++ while( nhd<2 && (c=z[i+1+nhd])!=0 && (hdv=hexDigitValue(c))>=0 ){
++ hv = (u8)((hv<<4)|hdv);
++ ++nhd;
++ }
++ i += nhd;
++ c = hv;
++ }else if( c>='0' && c<='7' ){
++ c -= '0';
++ if( z[i+1]>='0' && z[i+1]<='7' ){
++ i++;
++ c = (c<<3) + z[i] - '0';
++ if( z[i+1]>='0' && z[i+1]<='7' ){
++ i++;
++ c = (c<<3) + z[i] - '0';
++ }
++ }
++ }
}
++ z[j] = c;
}
-- fclose(f);
-- return rc;
++ if( j<i ) z[j] = 0;
}
--#ifndef SQLITE_OMIT_DESERIALIZE
/*
--** Reconstruct an in-memory database using the output from the "dbtotxt"
--** program. Read content from the file in p->aAuxDb[].zDbFilename.
--** If p->aAuxDb[].zDbFilename is 0, then read from standard input.
++** Interpret zArg as either an integer or a boolean value. Return 1 or 0
++** for TRUE and FALSE. Return the integer value if appropriate.
*/
--static unsigned char *readHexDb(ShellState *p, int *pnData){
-- unsigned char *a = 0;
-- int nLine;
-- int n = 0;
-- int pgsz = 0;
-- int iOffset = 0;
-- int j, k;
-- int rc;
-- FILE *in;
-- const char *zDbFilename = p->pAuxDb->zDbFilename;
-- unsigned int x[16];
-- char zLine[1000];
-- if( zDbFilename ){
-- in = fopen(zDbFilename, "r");
-- if( in==0 ){
-- utf8_printf(stderr, "cannot open \"%s\" for reading\n", zDbFilename);
-- return 0;
-- }
-- nLine = 0;
++static int booleanValue(const char *zArg){
++ static const char *zBoolNames[] = {
++ "no","yes", "off","on",
++#ifdef BOOLNAMES_ARE_BOOLEAN
++ "false","true",
++#endif
++ 0
++ };
++ int i;
++ if( zArg[0]=='0' && zArg[1]=='x' ){
++ for(i=2; hexDigitValue(zArg[i])>=0; i++){}
}else{
-- in = p->in;
-- nLine = p->lineno;
-- if( in==0 ) in = stdin;
-- }
-- *pnData = 0;
-- nLine++;
-- if( fgets(zLine, sizeof(zLine), in)==0 ) goto readHexDb_error;
-- rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz);
-- if( rc!=2 ) goto readHexDb_error;
-- if( n<0 ) goto readHexDb_error;
-- if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ) goto readHexDb_error;
-- n = (n+pgsz-1)&~(pgsz-1); /* Round n up to the next multiple of pgsz */
-- a = sqlite3_malloc( n ? n : 1 );
-- shell_check_oom(a);
-- memset(a, 0, n);
-- if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){
-- utf8_printf(stderr, "invalid pagesize\n");
-- goto readHexDb_error;
-- }
-- for(nLine++; fgets(zLine, sizeof(zLine), in)!=0; nLine++){
-- rc = sscanf(zLine, "| page %d offset %d", &j, &k);
-- if( rc==2 ){
-- iOffset = k;
-- continue;
-- }
-- if( cli_strncmp(zLine, "| end ", 6)==0 ){
-- break;
-- }
-- rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
-- &j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7],
-- &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]);
-- if( rc==17 ){
-- k = iOffset+j;
-- if( k+16<=n && k>=0 ){
-- int ii;
-- for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff;
-- }
-- }
++ for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){}
}
-- *pnData = n;
-- if( in!=p->in ){
-- fclose(in);
-- }else{
-- p->lineno = nLine;
++ if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff);
++ for( i=0; zBoolNames[i]!=0; ++i ){
++ if( sqlite3_stricmp(zArg, zBoolNames[i])==0 ) return i&1;
}
-- return a;
++ utf8_printf(STD_ERR, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n",
++ zArg);
++ return 0;
++}
--readHexDb_error:
-- if( in!=p->in ){
-- fclose(in);
++/*
++** Set or clear a shell flag according to a boolean value.
++*/
++static void setOrClearFlag(ShellExState *psx, unsigned mFlag, const char *zArg){
++ if( booleanValue(zArg) ){
++ ShellSetFlag(psx, mFlag);
}else{
-- while( fgets(zLine, sizeof(zLine), p->in)!=0 ){
-- nLine++;
-- if(cli_strncmp(zLine, "| end ", 6)==0 ) break;
-- }
-- p->lineno = nLine;
++ ShellClearFlag(psx, mFlag);
}
-- sqlite3_free(a);
-- utf8_printf(stderr,"Error on line %d of --hexdb input\n", nLine);
-- return 0;
}
--#endif /* SQLITE_OMIT_DESERIALIZE */
/*
--** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X.
++** Close an output file, provided it is not stderr or stdout
*/
--static void shellUSleepFunc(
-- sqlite3_context *context,
-- int argcUnused,
-- sqlite3_value **argv
--){
-- int sleep = sqlite3_value_int(argv[0]);
-- (void)argcUnused;
-- sqlite3_sleep(sleep/1000);
-- sqlite3_result_int(context, sleep);
++static void output_file_close(FILE *f){
++ if( f && f!=STD_OUT && f!=STD_ERR ) fclose(f);
}
--/* Flags for open_db().
--**
--** The default behavior of open_db() is to exit(1) if the database fails to
--** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error
--** but still returns without calling exit.
--**
--** The OPEN_DB_ZIPFILE flag causes open_db() to prefer to open files as a
--** ZIP archive if the file does not exist or is empty and its name matches
--** the *.zip pattern.
++/*
++** Try to open an output file. The names "stdout" and "stderr" are
++** recognized and do the right thing. NULL is returned if the output
++** filename is "off".
*/
--#define OPEN_DB_KEEPALIVE 0x001 /* Return after error if true */
--#define OPEN_DB_ZIPFILE 0x002 /* Open as ZIP if name matches *.zip */
++static FILE *output_file_open(const char *zFile, int bTextMode){
++ FILE *f;
++ if( cli_strcmp(zFile,"stdout")==0 ){
++ f = STD_OUT;
++ }else if( cli_strcmp(zFile, "stderr")==0 ){
++ f = STD_ERR;
++ }else if( cli_strcmp(zFile, "off")==0 ){
++ f = 0;
++ }else{
++ f = fopen(zFile, bTextMode ? "w" : "wb");
++ if( f==0 ){
++ utf8_printf(STD_ERR, "Error: cannot open \"%s\"\n", zFile);
++ }
++ }
++ return f;
++}
++#ifndef SQLITE_OMIT_TRACE
/*
--** Make sure the database is open. If it is not, then open it. If
--** the database fails to open, print an error message and exit.
++** A routine for handling output from sqlite3_trace().
*/
--static void open_db(ShellState *p, int openFlags){
-- if( p->db==0 ){
-- const char *zDbFilename = p->pAuxDb->zDbFilename;
-- if( p->openMode==SHELL_OPEN_UNSPEC ){
-- if( zDbFilename==0 || zDbFilename[0]==0 ){
-- p->openMode = SHELL_OPEN_NORMAL;
-- }else{
-- p->openMode = (u8)deduceDatabaseType(zDbFilename,
-- (openFlags & OPEN_DB_ZIPFILE)!=0);
-- }
-- }
-- switch( p->openMode ){
-- case SHELL_OPEN_APPENDVFS: {
-- sqlite3_open_v2(zDbFilename, &p->db,
-- SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, "apndvfs");
-- break;
-- }
-- case SHELL_OPEN_HEXDB:
-- case SHELL_OPEN_DESERIALIZE: {
-- sqlite3_open(0, &p->db);
-- break;
-- }
-- case SHELL_OPEN_ZIPFILE: {
-- sqlite3_open(":memory:", &p->db);
-- break;
-- }
-- case SHELL_OPEN_READONLY: {
-- sqlite3_open_v2(zDbFilename, &p->db,
-- SQLITE_OPEN_READONLY|p->openFlags, 0);
++static int sql_trace_callback(
++ unsigned mType, /* The trace type */
++ void *pArg, /* The shell state pointer */
++ void *pP, /* Usually a pointer to sqlite_stmt */
++ void *pX /* Auxiliary output */
++){
++ ShellInState *psi = (ShellInState*)pArg;
++ sqlite3_stmt *pStmt;
++ const char *zSql;
++ i64 nSql;
++ if( psi->traceOut==0 ) return 0;
++ if( mType==SQLITE_TRACE_CLOSE ){
++ utf8_printf(psi->traceOut, "-- closing database connection\n");
++ return 0;
++ }
++ if( mType!=SQLITE_TRACE_ROW && pX!=0 && ((const char*)pX)[0]=='-' ){
++ zSql = (const char*)pX;
++ }else{
++ pStmt = (sqlite3_stmt*)pP;
++ switch( psi->eTraceType ){
++ case SHELL_TRACE_EXPANDED: {
++ zSql = sqlite3_expanded_sql(pStmt);
break;
}
-- case SHELL_OPEN_UNSPEC:
-- case SHELL_OPEN_NORMAL: {
-- sqlite3_open_v2(zDbFilename, &p->db,
-- SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, 0);
++#ifdef SQLITE_ENABLE_NORMALIZE
++ case SHELL_TRACE_NORMALIZED: {
++ zSql = sqlite3_normalized_sql(pStmt);
break;
}
-- }
-- globalDb = p->db;
-- if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){
-- utf8_printf(stderr,"Error: unable to open database \"%s\": %s\n",
-- zDbFilename, sqlite3_errmsg(p->db));
-- if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){
-- exit(1);
-- }
-- sqlite3_close(p->db);
-- sqlite3_open(":memory:", &p->db);
-- if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){
-- utf8_printf(stderr,
-- "Also: unable to open substitute in-memory database.\n"
-- );
-- exit(1);
-- }else{
-- utf8_printf(stderr,
-- "Notice: using substitute in-memory database instead of \"%s\"\n",
-- zDbFilename);
-- }
-- }
-- sqlite3_db_config(p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, (int)0, (int*)0);
--
-- /* Reflect the use or absence of --unsafe-testing invocation. */
-- {
-- int testmode_on = ShellHasFlag(p,SHFLG_TestingMode);
-- sqlite3_db_config(p->db, SQLITE_DBCONFIG_TRUSTED_SCHEMA, testmode_on,0);
-- sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0);
-- }
--
--#ifndef SQLITE_OMIT_LOAD_EXTENSION
-- sqlite3_enable_load_extension(p->db, 1);
--#endif
-- sqlite3_shathree_init(p->db, 0, 0);
-- sqlite3_uint_init(p->db, 0, 0);
-- sqlite3_decimal_init(p->db, 0, 0);
-- sqlite3_base64_init(p->db, 0, 0);
-- sqlite3_base85_init(p->db, 0, 0);
-- sqlite3_regexp_init(p->db, 0, 0);
-- sqlite3_ieee_init(p->db, 0, 0);
-- sqlite3_series_init(p->db, 0, 0);
--#if SQLITE_SHELL_HAVE_RECOVER
-- sqlite3_dbdata_init(p->db, 0, 0);
--#endif
--#ifndef SQLITE_SHELL_FIDDLE
-- sqlite3_fileio_init(p->db, 0, 0);
-- sqlite3_completion_init(p->db, 0, 0);
--#endif
--#ifdef SQLITE_HAVE_ZLIB
-- if( !p->bSafeModePersist ){
-- sqlite3_zipfile_init(p->db, 0, 0);
-- sqlite3_sqlar_init(p->db, 0, 0);
-- }
--#endif
--#ifdef SQLITE_SHELL_EXTFUNCS
-- /* Create a preprocessing mechanism for extensions to make
-- * their own provisions for being built into the shell.
-- * This is a short-span macro. See further below for usage.
-- */
--#define SHELL_SUB_MACRO(base, variant) base ## _ ## variant
--#define SHELL_SUBMACRO(base, variant) SHELL_SUB_MACRO(base, variant)
-- /* Let custom-included extensions get their ..._init() called.
-- * The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause
-- * the extension's sqlite3_*_init( db, pzErrorMsg, pApi )
-- * inititialization routine to be called.
-- */
-- {
-- int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db);
-- /* Let custom-included extensions expose their functionality.
-- * The WHATEVER_EXPOSE( db, pzErrorMsg ) macro should cause
-- * the SQL functions, virtual tables, collating sequences or
-- * VFS's implemented by the extension to be registered.
-- */
-- if( irc==SQLITE_OK
-- || irc==SQLITE_OK_LOAD_PERMANENTLY ){
-- SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, EXPOSE)(p->db, 0);
-- }
--#undef SHELL_SUB_MACRO
--#undef SHELL_SUBMACRO
-- }
--#endif
--
-- sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0,
-- shellAddSchemaName, 0, 0);
-- sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0,
-- shellModuleSchema, 0, 0);
-- sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p,
-- shellPutsFunc, 0, 0);
-- sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0,
-- shellUSleepFunc, 0, 0);
--#ifndef SQLITE_NOHAVE_SYSTEM
-- sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0,
-- editFunc, 0, 0);
-- sqlite3_create_function(p->db, "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);
-- shell_check_oom(zSql);
-- sqlite3_exec(p->db, zSql, 0, 0, 0);
-- sqlite3_free(zSql);
-- }
--#ifndef SQLITE_OMIT_DESERIALIZE
-- else
-- if( p->openMode==SHELL_OPEN_DESERIALIZE || p->openMode==SHELL_OPEN_HEXDB ){
-- int rc;
-- int nData = 0;
-- unsigned char *aData;
-- if( p->openMode==SHELL_OPEN_DESERIALIZE ){
-- aData = (unsigned char*)readFile(zDbFilename, &nData);
-- }else{
-- aData = readHexDb(p, &nData);
-- }
-- if( aData==0 ){
-- return;
-- }
-- rc = sqlite3_deserialize(p->db, "main", aData, nData, nData,
-- SQLITE_DESERIALIZE_RESIZEABLE |
-- SQLITE_DESERIALIZE_FREEONCLOSE);
-- if( rc ){
-- utf8_printf(stderr, "Error: sqlite3_deserialize() returns %d\n", rc);
-- }
-- if( p->szMax>0 ){
-- sqlite3_file_control(p->db, "main", SQLITE_FCNTL_SIZE_LIMIT, &p->szMax);
++ default: {
++ zSql = sqlite3_sql(pStmt);
++ break;
}
}
--#endif
}
-- if( p->db!=0 ){
-- if( p->bSafeModePersist ){
-- sqlite3_set_authorizer(p->db, safeModeAuth, p);
++ if( zSql==0 ) return 0;
++ nSql = strlen(zSql);
++ if( nSql>1000000000 ) nSql = 1000000000; /* clamp to 1 billion */
++ while( nSql>0 && zSql[nSql-1]==';' ){ nSql--; }
++ switch( mType ){
++ case SQLITE_TRACE_ROW:
++ case SQLITE_TRACE_STMT: {
++ utf8_printf(psi->traceOut, "%.*s;\n", (int)nSql, zSql);
++ break;
++ }
++ case SQLITE_TRACE_PROFILE: {
++ sqlite3_int64 nNanosec = pX ? *(sqlite3_int64*)pX : 0;
++ utf8_printf(psi->traceOut,"%.*s; -- %lld ns\n", (int)nSql,zSql,nNanosec);
++ break;
}
-- sqlite3_db_config(
-- p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0
-- );
}
++ return 0;
}
++#endif
/*
--** Attempt to close the databaes connection. Report errors.
++** A no-op routine that runs with the ".breakpoint" dot-command.
++** This is a useful spot to set a debugger breakpoint.
++**
++** This routine does not do anything practical. The code are there simply
++** to prevent the compiler from optimizing this routine out.
*/
--void close_db(sqlite3 *db){
-- int rc = sqlite3_close(db);
-- if( rc ){
-- utf8_printf(stderr, "Error: sqlite3_close() returns %d: %s\n",
-- rc, sqlite3_errmsg(db));
-- }
++static void test_breakpoint(void){
++ static int nCall = 0;
++ if( (nCall++)==0xffffffff ) printf("Many .breakpoints have run\n");
}
--#if HAVE_READLINE || HAVE_EDITLINE
/*
--** Readline completion callbacks
++** An object used to read a CSV and other files for import.
*/
--static char *readline_completion_generator(const char *text, int state){
-- static sqlite3_stmt *pStmt = 0;
-- char *zRet;
-- if( state==0 ){
-- char *zSql;
-- sqlite3_finalize(pStmt);
-- zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase"
-- " FROM completion(%Q) ORDER BY 1", text);
-- shell_check_oom(zSql);
-- sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0);
-- sqlite3_free(zSql);
-- }
-- if( sqlite3_step(pStmt)==SQLITE_ROW ){
-- const char *z = (const char*)sqlite3_column_text(pStmt,0);
-- zRet = z ? strdup(z) : 0;
-- }else{
-- sqlite3_finalize(pStmt);
-- pStmt = 0;
-- zRet = 0;
++typedef struct ImportCtx ImportCtx;
++struct ImportCtx {
++ const char *zFile; /* Name of the input file */
++ FILE *in; /* Read the CSV text from this input stream */
++ int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close in */
++ char *z; /* Accumulated text for a field */
++ int n; /* Number of bytes in z */
++ int nAlloc; /* Space allocated for z[] */
++ int nLine; /* Current line number */
++ int nRow; /* Number of rows imported */
++ int nErr; /* Number of errors encountered */
++ int bNotFirst; /* True if one or more bytes already read */
++ int cTerm; /* Character that terminated the most recent field */
++ int cColSep; /* The column separator character. (Usually ",") */
++ int cRowSep; /* The row separator character. (Usually "\n") */
++};
++
++/* Clean up resourced used by an ImportCtx */
++static void import_cleanup(ImportCtx *p){
++ if( p->in!=0 && p->xCloser!=0 ){
++ p->xCloser(p->in);
++ p->in = 0;
}
-- return zRet;
++ sqlite3_free(p->z);
++ p->z = 0;
}
--static char **readline_completion(const char *zText, int iStart, int iEnd){
-- (void)iStart;
-- (void)iEnd;
-- rl_attempted_completion_over = 1;
-- return rl_completion_matches(zText, readline_completion_generator);
++
++/* Append a single byte to z[] */
++static void import_append_char(ImportCtx *p, int c){
++ if( p->n+1>=p->nAlloc ){
++ p->nAlloc += p->nAlloc + 100;
++ p->z = sqlite3_realloc64(p->z, p->nAlloc);
++ shell_check_oom(p->z);
++ }
++ p->z[p->n++] = (char)c;
}
--#elif HAVE_LINENOISE
--/*
--** Linenoise completion callback
- */
- static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){
- i64 nLine = strlen(zLine);
- i64 i, iStart;
- sqlite3_stmt *pStmt = 0;
- char *zSql;
- char zBuf[1000];
-
- if( nLine>(i64)sizeof(zBuf)-30 ) return;
- if( zLine[0]=='.' || zLine[0]=='#') return;
- for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){}
- if( i==nLine-1 ) return;
- iStart = i+1;
- memcpy(zBuf, zLine, iStart);
- zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase"
- " FROM completion(%Q,%Q) ORDER BY 1",
- &zLine[iStart], zLine);
- shell_check_oom(zSql);
- sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0);
- sqlite3_free(zSql);
- sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */
- while( sqlite3_step(pStmt)==SQLITE_ROW ){
- const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0);
- int nCompletion = sqlite3_column_bytes(pStmt, 0);
- if( iStart+nCompletion < (i64)sizeof(zBuf)-1 && zCompletion ){
- memcpy(zBuf+iStart, zCompletion, nCompletion+1);
- linenoiseAddCompletion(lc, zBuf);
- }
- }
- sqlite3_finalize(pStmt);
- }
- #endif
-
- /*
- ** Do C-language style dequoting.
- **
- ** \a -> alarm
- ** \b -> backspace
- ** \t -> tab
- ** \n -> newline
- ** \v -> vertical tab
- ** \f -> form feed
- ** \r -> carriage return
- ** \s -> space
- ** \" -> "
- ** \' -> '
- ** \\ -> backslash
- ** \NNN -> ascii character NNN in octal
- ** \xHH -> ascii character HH in hexadecimal
- */
- static void resolve_backslashes(char *z){
- int i, j;
- char c;
- while( *z && *z!='\\' ) z++;
- for(i=j=0; (c = z[i])!=0; i++, j++){
- if( c=='\\' && z[i+1]!=0 ){
- c = z[++i];
- if( c=='a' ){
- c = '\a';
- }else if( c=='b' ){
- c = '\b';
- }else if( c=='t' ){
- c = '\t';
- }else if( c=='n' ){
- c = '\n';
- }else if( c=='v' ){
- c = '\v';
- }else if( c=='f' ){
- c = '\f';
- }else if( c=='r' ){
- c = '\r';
- }else if( c=='"' ){
- c = '"';
- }else if( c=='\'' ){
- c = '\'';
- }else if( c=='\\' ){
- c = '\\';
- }else if( c=='x' ){
- int nhd = 0, hdv;
- u8 hv = 0;
- while( nhd<2 && (c=z[i+1+nhd])!=0 && (hdv=hexDigitValue(c))>=0 ){
- hv = (u8)((hv<<4)|hdv);
- ++nhd;
- }
- i += nhd;
- c = (u8)hv;
- }else if( c>='0' && c<='7' ){
- c -= '0';
- if( z[i+1]>='0' && z[i+1]<='7' ){
- i++;
- c = (c<<3) + z[i] - '0';
- if( z[i+1]>='0' && z[i+1]<='7' ){
- i++;
- c = (c<<3) + z[i] - '0';
- }
- }
- }
- }
- z[j] = c;
- }
- if( j<i ) z[j] = 0;
- }
-
- /*
- ** Interpret zArg as either an integer or a boolean value. Return 1 or 0
- ** for TRUE and FALSE. Return the integer value if appropriate.
- */
- static int booleanValue(const char *zArg){
- int i;
- if( zArg[0]=='0' && zArg[1]=='x' ){
- for(i=2; hexDigitValue(zArg[i])>=0; i++){}
- }else{
- for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){}
- }
- if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff);
- if( sqlite3_stricmp(zArg, "on")==0 || sqlite3_stricmp(zArg,"yes")==0 ){
- return 1;
- }
- if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){
- return 0;
- }
- utf8_printf(stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n",
- zArg);
- return 0;
- }
-
- /*
- ** Set or clear a shell flag according to a boolean value.
- */
- static void setOrClearFlag(ShellState *p, unsigned mFlag, const char *zArg){
- if( booleanValue(zArg) ){
- ShellSetFlag(p, mFlag);
- }else{
- ShellClearFlag(p, mFlag);
- }
- }
-
- /*
- ** Close an output file, assuming it is not stderr or stdout
- */
- static void output_file_close(FILE *f){
- if( f && f!=stdout && f!=stderr ) fclose(f);
- }
-
- /*
- ** Try to open an output file. The names "stdout" and "stderr" are
- ** recognized and do the right thing. NULL is returned if the output
- ** filename is "off".
- */
- static FILE *output_file_open(const char *zFile, int bTextMode){
- FILE *f;
- if( cli_strcmp(zFile,"stdout")==0 ){
- f = stdout;
- }else if( cli_strcmp(zFile, "stderr")==0 ){
- f = stderr;
- }else if( cli_strcmp(zFile, "off")==0 ){
- f = 0;
- }else{
- f = fopen(zFile, bTextMode ? "w" : "wb");
- if( f==0 ){
- utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile);
- }
- }
- return f;
- }
-
- #ifndef SQLITE_OMIT_TRACE
- /*
- ** A routine for handling output from sqlite3_trace().
- */
- static int sql_trace_callback(
- unsigned mType, /* The trace type */
- void *pArg, /* The ShellState pointer */
- void *pP, /* Usually a pointer to sqlite_stmt */
- void *pX /* Auxiliary output */
- ){
- ShellState *p = (ShellState*)pArg;
- sqlite3_stmt *pStmt;
- const char *zSql;
- i64 nSql;
- if( p->traceOut==0 ) return 0;
- if( mType==SQLITE_TRACE_CLOSE ){
- utf8_printf(p->traceOut, "-- closing database connection\n");
- return 0;
- }
- if( mType!=SQLITE_TRACE_ROW && pX!=0 && ((const char*)pX)[0]=='-' ){
- zSql = (const char*)pX;
- }else{
- pStmt = (sqlite3_stmt*)pP;
- switch( p->eTraceType ){
- case SHELL_TRACE_EXPANDED: {
- zSql = sqlite3_expanded_sql(pStmt);
- break;
- }
- #ifdef SQLITE_ENABLE_NORMALIZE
- case SHELL_TRACE_NORMALIZED: {
- zSql = sqlite3_normalized_sql(pStmt);
- break;
- }
- #endif
- default: {
- zSql = sqlite3_sql(pStmt);
- break;
- }
- }
- }
- if( zSql==0 ) return 0;
- nSql = strlen(zSql);
- if( nSql>1000000000 ) nSql = 1000000000;
- while( nSql>0 && zSql[nSql-1]==';' ){ nSql--; }
- switch( mType ){
- case SQLITE_TRACE_ROW:
- case SQLITE_TRACE_STMT: {
- utf8_printf(p->traceOut, "%.*s;\n", (int)nSql, zSql);
- break;
- }
- case SQLITE_TRACE_PROFILE: {
- sqlite3_int64 nNanosec = pX ? *(sqlite3_int64*)pX : 0;
- utf8_printf(p->traceOut, "%.*s; -- %lld ns\n", (int)nSql, zSql, nNanosec);
- break;
- }
- }
- return 0;
- }
- #endif
-
- /*
- ** A no-op routine that runs with the ".breakpoint" doc-command. This is
- ** a useful spot to set a debugger breakpoint.
- **
- ** This routine does not do anything practical. The code are there simply
- ** to prevent the compiler from optimizing this routine out.
- */
- static void test_breakpoint(void){
- static unsigned int nCall = 0;
- if( (nCall++)==0xffffffff ) printf("Many .breakpoints have run\n");
- }
-
- /*
- ** An object used to read a CSV and other files for import.
- */
- typedef struct ImportCtx ImportCtx;
- struct ImportCtx {
- const char *zFile; /* Name of the input file */
- FILE *in; /* Read the CSV text from this input stream */
- int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close in */
- char *z; /* Accumulated text for a field */
- int n; /* Number of bytes in z */
- int nAlloc; /* Space allocated for z[] */
- int nLine; /* Current line number */
- int nRow; /* Number of rows imported */
- int nErr; /* Number of errors encountered */
- int bNotFirst; /* True if one or more bytes already read */
- int cTerm; /* Character that terminated the most recent field */
- int cColSep; /* The column separator character. (Usually ",") */
- int cRowSep; /* The row separator character. (Usually "\n") */
- };
-
- /* Clean up resourced used by an ImportCtx */
- static void import_cleanup(ImportCtx *p){
- if( p->in!=0 && p->xCloser!=0 ){
- p->xCloser(p->in);
- p->in = 0;
- }
- sqlite3_free(p->z);
- p->z = 0;
- }
-
- /* Append a single byte to z[] */
- static void import_append_char(ImportCtx *p, int c){
- if( p->n+1>=p->nAlloc ){
- p->nAlloc += p->nAlloc + 100;
- p->z = sqlite3_realloc64(p->z, p->nAlloc);
- shell_check_oom(p->z);
- }
- p->z[p->n++] = (char)c;
- }
-
- /* Read a single field of CSV text. Compatible with rfc4180 and extended
- ** with the option of having a separator other than ",".
- **
- ** + Input comes from p->in.
- ** + Store results in p->z of length p->n. Space to hold p->z comes
- ** from sqlite3_malloc64().
- ** + Use p->cSep as the column separator. The default is ",".
- ** + Use p->rSep as the row separator. The default is "\n".
- ** + Keep track of the line number in p->nLine.
- ** + Store the character that terminates the field in p->cTerm. Store
- ** EOF on end-of-file.
- ** + Report syntax errors on stderr
++/* Read a single field of CSV text. Compatible with rfc4180 and extended
++** with the option of having a separator other than ",".
++**
++** + Input comes from p->in.
++** + Store results in p->z of length p->n. Space to hold p->z comes
++** from sqlite3_malloc64().
++** + Use p->cSep as the column separator. The default is ",".
++** + Use p->rSep as the row separator. The default is "\n".
++** + Keep track of the line number in p->nLine.
++** + Store the character that terminates the field in p->cTerm. Store
++** EOF on end-of-file.
++** + Report syntax errors on stderr
*/
-static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){
- i64 nLine = strlen(zLine);
- i64 i, iStart;
- sqlite3_stmt *pStmt = 0;
- char *zSql;
- char zBuf[1000];
-
- if( nLine>(i64)sizeof(zBuf)-30 ) return;
- if( zLine[0]=='.' || zLine[0]=='#') return;
- for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){}
- if( i==nLine-1 ) return;
- iStart = i+1;
- memcpy(zBuf, zLine, iStart);
- zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase"
- " FROM completion(%Q,%Q) ORDER BY 1",
- &zLine[iStart], zLine);
- shell_check_oom(zSql);
- sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0);
- sqlite3_free(zSql);
- sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */
- while( sqlite3_step(pStmt)==SQLITE_ROW ){
- const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0);
- int nCompletion = sqlite3_column_bytes(pStmt, 0);
- if( iStart+nCompletion < (i64)sizeof(zBuf)-1 && zCompletion ){
- memcpy(zBuf+iStart, zCompletion, nCompletion+1);
- linenoiseAddCompletion(lc, zBuf);
- }
+static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){
+ int c;
+ int cSep = (u8)p->cColSep;
+ int rSep = (u8)p->cRowSep;
+ p->n = 0;
+ c = fgetc(p->in);
+ if( c==EOF || seenInterrupt ){
+ p->cTerm = EOF;
+ return 0;
}
- sqlite3_finalize(pStmt);
-}
-#endif
-
-/*
-** Do C-language style dequoting.
-**
-** \a -> alarm
-** \b -> backspace
-** \t -> tab
-** \n -> newline
-** \v -> vertical tab
-** \f -> form feed
-** \r -> carriage return
-** \s -> space
-** \" -> "
-** \' -> '
-** \\ -> backslash
-** \NNN -> ascii character NNN in octal
-** \xHH -> ascii character HH in hexadecimal
-*/
-static void resolve_backslashes(char *z){
- int i, j;
- char c;
- while( *z && *z!='\\' ) z++;
- for(i=j=0; (c = z[i])!=0; i++, j++){
- if( c=='\\' && z[i+1]!=0 ){
- c = z[++i];
- if( c=='a' ){
- c = '\a';
- }else if( c=='b' ){
- c = '\b';
- }else if( c=='t' ){
- c = '\t';
- }else if( c=='n' ){
- c = '\n';
- }else if( c=='v' ){
- c = '\v';
- }else if( c=='f' ){
- c = '\f';
- }else if( c=='r' ){
- c = '\r';
- }else if( c=='"' ){
- c = '"';
- }else if( c=='\'' ){
- c = '\'';
- }else if( c=='\\' ){
- c = '\\';
- }else if( c=='x' ){
- int nhd = 0, hdv;
- u8 hv = 0;
- while( nhd<2 && (c=z[i+1+nhd])!=0 && (hdv=hexDigitValue(c))>=0 ){
- hv = (u8)((hv<<4)|hdv);
- ++nhd;
+ if( c=='"' ){
+ int pc, ppc;
+ int startLine = p->nLine;
+ int cQuote = c;
+ pc = ppc = 0;
+ while( 1 ){
+ c = fgetc(p->in);
+ if( c==rSep ) p->nLine++;
+ if( c==cQuote ){
+ if( pc==cQuote ){
+ pc = 0;
+ continue;
}
- i += nhd;
- c = (u8)hv;
- }else if( c>='0' && c<='7' ){
- c -= '0';
- if( z[i+1]>='0' && z[i+1]<='7' ){
- i++;
- c = (c<<3) + z[i] - '0';
- if( z[i+1]>='0' && z[i+1]<='7' ){
- i++;
- c = (c<<3) + z[i] - '0';
- }
+ }
+ if( (c==cSep && pc==cQuote)
+ || (c==rSep && pc==cQuote)
+ || (c==rSep && pc=='\r' && ppc==cQuote)
+ || (c==EOF && pc==cQuote)
+ ){
+ do{ p->n--; }while( p->z[p->n]!=cQuote );
+ p->cTerm = c;
+ break;
+ }
+ if( pc==cQuote && c!='\r' ){
- utf8_printf(stderr, "%s:%d: unescaped %c character\n",
++ utf8_printf(STD_ERR, "%s:%d: unescaped %c character\n",
+ p->zFile, p->nLine, cQuote);
+ }
+ if( c==EOF ){
- utf8_printf(stderr, "%s:%d: unterminated %c-quoted field\n",
++ utf8_printf(STD_ERR, "%s:%d: unterminated %c-quoted field\n",
+ p->zFile, startLine, cQuote);
+ p->cTerm = c;
+ break;
+ }
+ import_append_char(p, c);
+ ppc = pc;
+ pc = c;
+ }
+ }else{
+ /* If this is the first field being parsed and it begins with the
+ ** UTF-8 BOM (0xEF BB BF) then skip the BOM */
+ if( (c&0xff)==0xef && p->bNotFirst==0 ){
+ import_append_char(p, c);
+ c = fgetc(p->in);
+ if( (c&0xff)==0xbb ){
+ import_append_char(p, c);
+ c = fgetc(p->in);
+ if( (c&0xff)==0xbf ){
+ p->bNotFirst = 1;
+ p->n = 0;
+ return csv_read_one_field(p);
}
}
}
- z[j] = c;
+ while( c!=EOF && c!=cSep && c!=rSep ){
+ import_append_char(p, c);
+ c = fgetc(p->in);
+ }
+ if( c==rSep ){
+ p->nLine++;
+ if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--;
+ }
+ p->cTerm = c;
}
- if( j<i ) z[j] = 0;
+ if( p->z ) p->z[p->n] = 0;
+ p->bNotFirst = 1;
+ return p->z;
}
-/*
-** Interpret zArg as either an integer or a boolean value. Return 1 or 0
-** for TRUE and FALSE. Return the integer value if appropriate.
+/* Read a single field of ASCII delimited text.
+**
+** + Input comes from p->in.
+** + Store results in p->z of length p->n. Space to hold p->z comes
+** from sqlite3_malloc64().
+** + Use p->cSep as the column separator. The default is "\x1F".
+** + Use p->rSep as the row separator. The default is "\x1E".
+** + Keep track of the row number in p->nLine.
+** + Store the character that terminates the field in p->cTerm. Store
+** EOF on end-of-file.
+** + Report syntax errors on stderr
*/
-static int booleanValue(const char *zArg){
- int i;
- if( zArg[0]=='0' && zArg[1]=='x' ){
- for(i=2; hexDigitValue(zArg[i])>=0; i++){}
- }else{
- for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){}
- }
- if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff);
- if( sqlite3_stricmp(zArg, "on")==0 || sqlite3_stricmp(zArg,"yes")==0 ){
- return 1;
- }
- if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){
+static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p){
+ int c;
+ int cSep = (u8)p->cColSep;
+ int rSep = (u8)p->cRowSep;
+ p->n = 0;
+ c = fgetc(p->in);
+ if( c==EOF || seenInterrupt ){
+ p->cTerm = EOF;
return 0;
}
- utf8_printf(stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n",
- zArg);
- return 0;
-}
-
-/*
-** Set or clear a shell flag according to a boolean value.
-*/
-static void setOrClearFlag(ShellState *p, unsigned mFlag, const char *zArg){
- if( booleanValue(zArg) ){
- ShellSetFlag(p, mFlag);
- }else{
- ShellClearFlag(p, mFlag);
+ while( c!=EOF && c!=cSep && c!=rSep ){
+ import_append_char(p, c);
+ c = fgetc(p->in);
}
-}
-
-/*
-** Close an output file, assuming it is not stderr or stdout
-*/
-static void output_file_close(FILE *f){
- if( f && f!=stdout && f!=stderr ) fclose(f);
-}
-
-/*
-** Try to open an output file. The names "stdout" and "stderr" are
-** recognized and do the right thing. NULL is returned if the output
-** filename is "off".
-*/
-static FILE *output_file_open(const char *zFile, int bTextMode){
- FILE *f;
- if( cli_strcmp(zFile,"stdout")==0 ){
- f = stdout;
- }else if( cli_strcmp(zFile, "stderr")==0 ){
- f = stderr;
- }else if( cli_strcmp(zFile, "off")==0 ){
- f = 0;
- }else{
- f = fopen(zFile, bTextMode ? "w" : "wb");
- if( f==0 ){
- utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile);
- }
+ if( c==rSep ){
+ p->nLine++;
}
- return f;
+ p->cTerm = c;
+ if( p->z ) p->z[p->n] = 0;
+ return p->z;
}
-#ifndef SQLITE_OMIT_TRACE
/*
-** A routine for handling output from sqlite3_trace().
+** Try to transfer data for table zTable. If an error is seen while
+** moving forward, try to go backwards. The backwards movement won't
+** work for WITHOUT ROWID tables.
*/
-static int sql_trace_callback(
- unsigned mType, /* The trace type */
- void *pArg, /* The ShellState pointer */
- void *pP, /* Usually a pointer to sqlite_stmt */
- void *pX /* Auxiliary output */
-){
- ShellState *p = (ShellState*)pArg;
- sqlite3_stmt *pStmt;
- const char *zSql;
- i64 nSql;
- if( p->traceOut==0 ) return 0;
- if( mType==SQLITE_TRACE_CLOSE ){
- utf8_printf(p->traceOut, "-- closing database connection\n");
- return 0;
- }
- if( mType!=SQLITE_TRACE_ROW && pX!=0 && ((const char*)pX)[0]=='-' ){
- zSql = (const char*)pX;
- }else{
- pStmt = (sqlite3_stmt*)pP;
- switch( p->eTraceType ){
- case SHELL_TRACE_EXPANDED: {
- zSql = sqlite3_expanded_sql(pStmt);
- break;
- }
-#ifdef SQLITE_ENABLE_NORMALIZE
- case SHELL_TRACE_NORMALIZED: {
- zSql = sqlite3_normalized_sql(pStmt);
- break;
- }
-#endif
- default: {
- zSql = sqlite3_sql(pStmt);
- break;
- }
- }
- }
- if( zSql==0 ) return 0;
- nSql = strlen(zSql);
- if( nSql>1000000000 ) nSql = 1000000000;
- while( nSql>0 && zSql[nSql-1]==';' ){ nSql--; }
- switch( mType ){
- case SQLITE_TRACE_ROW:
- case SQLITE_TRACE_STMT: {
- utf8_printf(p->traceOut, "%.*s;\n", (int)nSql, zSql);
- break;
- }
- case SQLITE_TRACE_PROFILE: {
- sqlite3_int64 nNanosec = pX ? *(sqlite3_int64*)pX : 0;
- utf8_printf(p->traceOut, "%.*s; -- %lld ns\n", (int)nSql, zSql, nNanosec);
- break;
- }
- }
- return 0;
-}
-#endif
-
-/*
-** A no-op routine that runs with the ".breakpoint" doc-command. This is
-** a useful spot to set a debugger breakpoint.
-**
-** This routine does not do anything practical. The code are there simply
-** to prevent the compiler from optimizing this routine out.
-*/
-static void test_breakpoint(void){
- static unsigned int nCall = 0;
- if( (nCall++)==0xffffffff ) printf("Many .breakpoints have run\n");
-}
-
-/*
-** An object used to read a CSV and other files for import.
-*/
-typedef struct ImportCtx ImportCtx;
-struct ImportCtx {
- const char *zFile; /* Name of the input file */
- FILE *in; /* Read the CSV text from this input stream */
- int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close in */
- char *z; /* Accumulated text for a field */
- int n; /* Number of bytes in z */
- int nAlloc; /* Space allocated for z[] */
- int nLine; /* Current line number */
- int nRow; /* Number of rows imported */
- int nErr; /* Number of errors encountered */
- int bNotFirst; /* True if one or more bytes already read */
- int cTerm; /* Character that terminated the most recent field */
- int cColSep; /* The column separator character. (Usually ",") */
- int cRowSep; /* The row separator character. (Usually "\n") */
-};
-
-/* Clean up resourced used by an ImportCtx */
-static void import_cleanup(ImportCtx *p){
- if( p->in!=0 && p->xCloser!=0 ){
- p->xCloser(p->in);
- p->in = 0;
- }
- sqlite3_free(p->z);
- p->z = 0;
-}
-
-/* Append a single byte to z[] */
-static void import_append_char(ImportCtx *p, int c){
- if( p->n+1>=p->nAlloc ){
- p->nAlloc += p->nAlloc + 100;
- p->z = sqlite3_realloc64(p->z, p->nAlloc);
- shell_check_oom(p->z);
- }
- p->z[p->n++] = (char)c;
-}
-
-/* Read a single field of CSV text. Compatible with rfc4180 and extended
-** with the option of having a separator other than ",".
-**
-** + Input comes from p->in.
-** + Store results in p->z of length p->n. Space to hold p->z comes
-** from sqlite3_malloc64().
-** + Use p->cSep as the column separator. The default is ",".
-** + Use p->rSep as the row separator. The default is "\n".
-** + Keep track of the line number in p->nLine.
-** + Store the character that terminates the field in p->cTerm. Store
-** EOF on end-of-file.
-** + Report syntax errors on stderr
-*/
-static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){
- int c;
- int cSep = (u8)p->cColSep;
- int rSep = (u8)p->cRowSep;
- p->n = 0;
- c = fgetc(p->in);
- if( c==EOF || seenInterrupt ){
- p->cTerm = EOF;
- return 0;
- }
- if( c=='"' ){
- int pc, ppc;
- int startLine = p->nLine;
- int cQuote = c;
- pc = ppc = 0;
- while( 1 ){
- c = fgetc(p->in);
- if( c==rSep ) p->nLine++;
- if( c==cQuote ){
- if( pc==cQuote ){
- pc = 0;
- continue;
- }
- }
- if( (c==cSep && pc==cQuote)
- || (c==rSep && pc==cQuote)
- || (c==rSep && pc=='\r' && ppc==cQuote)
- || (c==EOF && pc==cQuote)
- ){
- do{ p->n--; }while( p->z[p->n]!=cQuote );
- p->cTerm = c;
- break;
- }
- if( pc==cQuote && c!='\r' ){
- utf8_printf(stderr, "%s:%d: unescaped %c character\n",
- p->zFile, p->nLine, cQuote);
- }
- if( c==EOF ){
- utf8_printf(stderr, "%s:%d: unterminated %c-quoted field\n",
- p->zFile, startLine, cQuote);
- p->cTerm = c;
- break;
- }
- import_append_char(p, c);
- ppc = pc;
- pc = c;
- }
- }else{
- /* If this is the first field being parsed and it begins with the
- ** UTF-8 BOM (0xEF BB BF) then skip the BOM */
- if( (c&0xff)==0xef && p->bNotFirst==0 ){
- import_append_char(p, c);
- c = fgetc(p->in);
- if( (c&0xff)==0xbb ){
- import_append_char(p, c);
- c = fgetc(p->in);
- if( (c&0xff)==0xbf ){
- p->bNotFirst = 1;
- p->n = 0;
- return csv_read_one_field(p);
- }
- }
- }
- while( c!=EOF && c!=cSep && c!=rSep ){
- import_append_char(p, c);
- c = fgetc(p->in);
- }
- if( c==rSep ){
- p->nLine++;
- if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--;
- }
- p->cTerm = c;
- }
- if( p->z ) p->z[p->n] = 0;
- p->bNotFirst = 1;
- return p->z;
-}
-
-/* Read a single field of ASCII delimited text.
-**
-** + Input comes from p->in.
-** + Store results in p->z of length p->n. Space to hold p->z comes
-** from sqlite3_malloc64().
-** + Use p->cSep as the column separator. The default is "\x1F".
-** + Use p->rSep as the row separator. The default is "\x1E".
-** + Keep track of the row number in p->nLine.
-** + Store the character that terminates the field in p->cTerm. Store
-** EOF on end-of-file.
-** + Report syntax errors on stderr
-*/
-static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p){
- int c;
- int cSep = (u8)p->cColSep;
- int rSep = (u8)p->cRowSep;
- p->n = 0;
- c = fgetc(p->in);
- if( c==EOF || seenInterrupt ){
- p->cTerm = EOF;
- return 0;
- }
- while( c!=EOF && c!=cSep && c!=rSep ){
- import_append_char(p, c);
- c = fgetc(p->in);
- }
- if( c==rSep ){
- p->nLine++;
- }
- p->cTerm = c;
- if( p->z ) p->z[p->n] = 0;
- return p->z;
-}
-
-/*
-** Try to transfer data for table zTable. If an error is seen while
-** moving forward, try to go backwards. The backwards movement won't
-** work for WITHOUT ROWID tables.
-*/
-static void tryToCloneData(
- ShellState *p,
- sqlite3 *newDb,
- const char *zTable
+static void tryToCloneData(
- ShellState *p,
++ ShellExState *psx,
+ sqlite3 *newDb,
+ const char *zTable
){
sqlite3_stmt *pQuery = 0;
sqlite3_stmt *pInsert = 0;
}
}
-
- /*
- ** The implementation of dot-command ".lint fkey-indexes".
- */
- static int lintFkeyIndexes(
- ShellState *pState, /* Current shell tool state */
- char **azArg, /* Array of arguments passed to dot command */
- int nArg /* Number of entries in azArg[] */
- ){
- sqlite3 *db = pState->db; /* Database handle to query "main" db of */
- FILE *out = pState->out; /* Stream to write non-error output to */
- int bVerbose = 0; /* If -verbose is present */
- int bGroupByParent = 0; /* If -groupbyparent is present */
- int i; /* To iterate through azArg[] */
- const char *zIndent = ""; /* How much to indent CREATE INDEX by */
- int rc; /* Return code */
- sqlite3_stmt *pSql = 0; /* Compiled version of SQL statement below */
-
- /*
- ** This SELECT statement returns one row for each foreign key constraint
- ** in the schema of the main database. The column values are:
- **
- ** 0. The text of an SQL statement similar to:
- **
- ** "EXPLAIN QUERY PLAN SELECT 1 FROM child_table WHERE child_key=?"
- **
- ** This SELECT is similar to the one that the foreign keys implementation
- ** needs to run internally on child tables. If there is an index that can
- ** be used to optimize this query, then it can also be used by the FK
- ** implementation to optimize DELETE or UPDATE statements on the parent
- ** table.
- **
- ** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by
- ** the EXPLAIN QUERY PLAN command matches this pattern, then the schema
- ** contains an index that can be used to optimize the query.
- **
- ** 2. Human readable text that describes the child table and columns. e.g.
- **
- ** "child_table(child_key1, child_key2)"
- **
- ** 3. Human readable text that describes the parent table and columns. e.g.
- **
- ** "parent_table(parent_key1, parent_key2)"
- **
- ** 4. A full CREATE INDEX statement for an index that could be used to
- ** optimize DELETE or UPDATE statements on the parent table. e.g.
- **
- ** "CREATE INDEX child_table_child_key ON child_table(child_key)"
- **
- ** 5. The name of the parent table.
- **
- ** These six values are used by the C logic below to generate the report.
- */
- const char *zSql =
- "SELECT "
- " 'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '"
- " || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' "
- " || fkey_collate_clause("
- " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')"
- ", "
- " 'SEARCH ' || s.name || ' USING COVERING INDEX*('"
- " || group_concat('*=?', ' AND ') || ')'"
- ", "
- " s.name || '(' || group_concat(f.[from], ', ') || ')'"
- ", "
- " f.[table] || '(' || group_concat(COALESCE(f.[to], p.[name])) || ')'"
- ", "
- " 'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))"
- " || ' ON ' || quote(s.name) || '('"
- " || group_concat(quote(f.[from]) ||"
- " fkey_collate_clause("
- " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')"
- " || ');'"
- ", "
- " f.[table] "
- "FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f "
- "LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) "
- "GROUP BY s.name, f.id "
- "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)"
- ;
- const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)";
-
- for(i=2; i<nArg; i++){
- int n = strlen30(azArg[i]);
- if( n>1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){
- bVerbose = 1;
- }
- else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){
- bGroupByParent = 1;
- zIndent = " ";
- }
- else{
- raw_printf(stderr, "Usage: %s %s ?-verbose? ?-groupbyparent?\n",
- azArg[0], azArg[1]
- );
- return SQLITE_ERROR;
- }
- }
-
- /* 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 ) continue;
- if( 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(stderr, "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 = sqlite3_mprintf("%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 ){
- raw_printf(stderr, "%s\n", sqlite3_errmsg(db));
- }
-
- rc2 = sqlite3_finalize(pSql);
- if( rc==SQLITE_OK && rc2!=SQLITE_OK ){
- rc = rc2;
- raw_printf(stderr, "%s\n", sqlite3_errmsg(db));
- }
- }else{
- raw_printf(stderr, "%s\n", sqlite3_errmsg(db));
- }
-
- return rc;
- }
-
- /*
- ** Implementation of ".lint" dot command.
- */
- static int lintDotCommand(
- ShellState *pState, /* Current shell tool state */
- char **azArg, /* Array of arguments passed to dot command */
- int nArg /* Number of entries in azArg[] */
- ){
- int n;
- n = (nArg>=2 ? strlen30(azArg[1]) : 0);
- if( n<1 || sqlite3_strnicmp(azArg[1], "fkey-indexes", n) ) goto usage;
- return lintFkeyIndexes(pState, azArg, nArg);
-
- usage:
- raw_printf(stderr, "Usage %s sub-command ?switches...?\n", azArg[0]);
- raw_printf(stderr, "Where sub-commands are:\n");
- raw_printf(stderr, " fkey-indexes\n");
- return SQLITE_ERROR;
- }
-
+#if !defined SQLITE_OMIT_VIRTUALTABLE
+static void shellPrepare(
+ sqlite3 *db,
+ int *pRc,
+ const char *zSql,
+ sqlite3_stmt **ppStmt
+){
+ *ppStmt = 0;
+ if( *pRc==SQLITE_OK ){
+ int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0);
+ if( rc!=SQLITE_OK ){
- raw_printf(stderr, "sql error: %s (%d)\n",
++ raw_printf(STD_ERR, "sql error: %s (%d)\n",
+ sqlite3_errmsg(db), sqlite3_errcode(db)
+ );
+ *pRc = rc;
+ }
+ }
+}
/*
-** The implementation of dot-command ".lint fkey-indexes".
+** Create a prepared statement using printf-style arguments for the SQL.
+**
+** This routine is could be marked "static". But it is not always used,
+** depending on compile-time options. By omitting the "static", we avoid
+** nuisance compiler warnings about "defined but not used".
*/
-static int lintFkeyIndexes(
- ShellState *pState, /* Current shell tool state */
- char **azArg, /* Array of arguments passed to dot command */
- int nArg /* Number of entries in azArg[] */
-){
- sqlite3 *db = pState->db; /* Database handle to query "main" db of */
- FILE *out = pState->out; /* Stream to write non-error output to */
- int bVerbose = 0; /* If -verbose is present */
- int bGroupByParent = 0; /* If -groupbyparent is present */
- int i; /* To iterate through azArg[] */
- const char *zIndent = ""; /* How much to indent CREATE INDEX by */
- int rc; /* Return code */
- sqlite3_stmt *pSql = 0; /* Compiled version of SQL statement below */
-
- /*
- ** This SELECT statement returns one row for each foreign key constraint
- ** in the schema of the main database. The column values are:
- **
- ** 0. The text of an SQL statement similar to:
- **
- ** "EXPLAIN QUERY PLAN SELECT 1 FROM child_table WHERE child_key=?"
- **
- ** This SELECT is similar to the one that the foreign keys implementation
- ** needs to run internally on child tables. If there is an index that can
- ** be used to optimize this query, then it can also be used by the FK
- ** implementation to optimize DELETE or UPDATE statements on the parent
- ** table.
- **
- ** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by
- ** the EXPLAIN QUERY PLAN command matches this pattern, then the schema
- ** contains an index that can be used to optimize the query.
- **
- ** 2. Human readable text that describes the child table and columns. e.g.
- **
- ** "child_table(child_key1, child_key2)"
- **
- ** 3. Human readable text that describes the parent table and columns. e.g.
- **
- ** "parent_table(parent_key1, parent_key2)"
- **
- ** 4. A full CREATE INDEX statement for an index that could be used to
- ** optimize DELETE or UPDATE statements on the parent table. e.g.
- **
- ** "CREATE INDEX child_table_child_key ON child_table(child_key)"
- **
- ** 5. The name of the parent table.
- **
- ** These six values are used by the C logic below to generate the report.
- */
- const char *zSql =
- "SELECT "
- " 'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '"
- " || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' "
- " || fkey_collate_clause("
- " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')"
- ", "
- " 'SEARCH ' || s.name || ' USING COVERING INDEX*('"
- " || group_concat('*=?', ' AND ') || ')'"
- ", "
- " s.name || '(' || group_concat(f.[from], ', ') || ')'"
- ", "
- " f.[table] || '(' || group_concat(COALESCE(f.[to], p.[name])) || ')'"
- ", "
- " 'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))"
- " || ' ON ' || quote(s.name) || '('"
- " || group_concat(quote(f.[from]) ||"
- " fkey_collate_clause("
- " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')"
- " || ');'"
- ", "
- " f.[table] "
- "FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f "
- "LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) "
- "GROUP BY s.name, f.id "
- "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)"
- ;
- const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)";
-
- for(i=2; i<nArg; i++){
- int n = strlen30(azArg[i]);
- if( n>1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){
- bVerbose = 1;
- }
- else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){
- bGroupByParent = 1;
- zIndent = " ";
- }
- else{
- raw_printf(stderr, "Usage: %s %s ?-verbose? ?-groupbyparent?\n",
- azArg[0], azArg[1]
- );
- return SQLITE_ERROR;
- }
- }
-
- /* 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 ) continue;
- if( 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(stderr, "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 = sqlite3_mprintf("%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 ){
- raw_printf(stderr, "%s\n", sqlite3_errmsg(db));
- }
-
- rc2 = sqlite3_finalize(pSql);
- if( rc==SQLITE_OK && rc2!=SQLITE_OK ){
- rc = rc2;
- raw_printf(stderr, "%s\n", sqlite3_errmsg(db));
- }
- }else{
- raw_printf(stderr, "%s\n", sqlite3_errmsg(db));
- }
-
- return rc;
-}
-
-/*
-** Implementation of ".lint" dot command.
-*/
-static int lintDotCommand(
- ShellState *pState, /* Current shell tool state */
- char **azArg, /* Array of arguments passed to dot command */
- int nArg /* Number of entries in azArg[] */
-){
- int n;
- n = (nArg>=2 ? strlen30(azArg[1]) : 0);
- if( n<1 || sqlite3_strnicmp(azArg[1], "fkey-indexes", n) ) goto usage;
- return lintFkeyIndexes(pState, azArg, nArg);
-
- usage:
- raw_printf(stderr, "Usage %s sub-command ?switches...?\n", azArg[0]);
- raw_printf(stderr, "Where sub-commands are:\n");
- raw_printf(stderr, " fkey-indexes\n");
- return SQLITE_ERROR;
-}
-
-#if !defined SQLITE_OMIT_VIRTUALTABLE
-static void shellPrepare(
- sqlite3 *db,
- int *pRc,
- const char *zSql,
- sqlite3_stmt **ppStmt
-){
- *ppStmt = 0;
- if( *pRc==SQLITE_OK ){
- int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0);
- if( rc!=SQLITE_OK ){
- raw_printf(stderr, "sql error: %s (%d)\n",
- sqlite3_errmsg(db), sqlite3_errcode(db)
- );
- *pRc = rc;
- }
- }
-}
-
-/*
-** Create a prepared statement using printf-style arguments for the SQL.
-**
-** This routine is could be marked "static". But it is not always used,
-** depending on compile-time options. By omitting the "static", we avoid
-** nuisance compiler warnings about "defined but not used".
-*/
-void shellPreparePrintf(
- sqlite3 *db,
- int *pRc,
- sqlite3_stmt **ppStmt,
- const char *zFmt,
- ...
+void shellPreparePrintf(
+ sqlite3 *db,
+ int *pRc,
+ sqlite3_stmt **ppStmt,
+ const char *zFmt,
+ ...
){
*ppStmt = 0;
if( *pRc==SQLITE_OK ){
return SQLITE_OK;
}
--/*
--** This function is called to recover data from the database. A script
--** to construct a new database containing all recovered data is output
--** on stream pState->out.
--*/
--static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){
-- int rc = SQLITE_OK;
-- const char *zRecoveryDb = ""; /* Name of "recovery" database. Debug only */
-- const char *zLAF = "lost_and_found";
-- int bFreelist = 1; /* 0 if --ignore-freelist is specified */
-- int bRowids = 1; /* 0 if --no-rowids */
-- sqlite3_recover *p = 0;
-- int i = 0;
++#endif /* SQLITE_SHELL_HAVE_RECOVER */
-- for(i=1; i<nArg; i++){
-- char *z = azArg[i];
-- int n;
-- if( z[0]=='-' && z[1]=='-' ) z++;
-- n = strlen30(z);
-- if( n<=17 && memcmp("-ignore-freelist", z, n)==0 ){
-- bFreelist = 0;
-- }else
-- if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
-- /* This option determines the name of the ATTACH-ed database used
-- ** internally by the recovery extension. The default is "" which
-- ** means to use a temporary database that is automatically deleted
-- ** when closed. This option is undocumented and might disappear at
-- ** any moment. */
-- i++;
-- zRecoveryDb = azArg[i];
-- }else
-- if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
-- i++;
-- zLAF = azArg[i];
-- }else
-- if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
-- bRowids = 0;
-- }
-- else{
-- utf8_printf(stderr, "unexpected option: %s\n", azArg[i]);
-- showHelp(pState->out, azArg[0]);
-- return 1;
++#ifndef SQLITE_SHELL_FIDDLE
++static DotCmdRC
++writeDb( char *azArg[], int nArg, ShellExState *psx, char **pzErr ){
++ int rc = 0;
++ const char *zDestFile = 0;
++ const char *zDb = 0;
++ sqlite3 *pDest;
++ sqlite3_backup *pBackup;
++ int j;
++ int bAsync = 0;
++ const char *zVfs = 0;
++ if( ISS(psx)->bSafeMode ) return DCR_AbortError;
++ for(j=1; j<nArg; j++){
++ const char *z = azArg[j];
++ if( z[0]=='-' ){
++ if( z[1]=='-' ) z++;
++ if( cli_strcmp(z, "-append")==0 ){
++ zVfs = "apndvfs";
++ }else if( cli_strcmp(z, "-async")==0 ){
++ bAsync = 1;
++ }else{
++ return DCR_Unknown|j;
++ }
++ }else if( zDestFile==0 ){
++ zDestFile = azArg[j];
++ }else if( zDb==0 ){
++ zDb = zDestFile;
++ zDestFile = azArg[j];
++ }else{
++ return DCR_TooMany|j;
}
}
--
-- p = sqlite3_recover_init_sql(
-- pState->db, "main", recoverSqlCb, (void*)pState
-- );
--
-- sqlite3_recover_config(p, 789, (void*)zRecoveryDb); /* Debug use only */
-- sqlite3_recover_config(p, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF);
-- sqlite3_recover_config(p, SQLITE_RECOVER_ROWIDS, (void*)&bRowids);
-- sqlite3_recover_config(p, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist);
--
-- sqlite3_recover_run(p);
-- if( sqlite3_recover_errcode(p)!=SQLITE_OK ){
-- const char *zErr = sqlite3_recover_errmsg(p);
-- int errCode = sqlite3_recover_errcode(p);
-- raw_printf(stderr, "sql error: %s (%d)\n", zErr, errCode);
++ if( zDestFile==0 ){
++ return DCR_Missing;
}
-- rc = sqlite3_recover_finish(p);
-- return rc;
-}
-#endif /* SQLITE_SHELL_HAVE_RECOVER */
-
++ if( zDb==0 ) zDb = "main";
++ rc = sqlite3_open_v2(zDestFile, &pDest,
++ SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs);
++ if( rc!=SQLITE_OK ){
++ *pzErr = smprintf("cannot open \"%s\"\n", zDestFile);
++ close_db(pDest);
++ return DCR_Error;
++ }
++ if( bAsync ){
++ sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
++ 0, 0, 0);
++ }
++ open_db(psx, 0);
++ pBackup = sqlite3_backup_init(pDest, "main", DBX(psx), zDb);
++ if( pBackup==0 ){
++ *pzErr = smprintf("%s failed, %s\n", azArg[0], sqlite3_errmsg(pDest));
++ close_db(pDest);
++ return DCR_Error;
++ }
++ while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
++ sqlite3_backup_finish(pBackup);
++ if( rc==SQLITE_DONE ){
++ rc = 0;
++ }else{
++ *pzErr = smprintf("%s\n", sqlite3_errmsg(pDest));
++ rc = 1;
++ }
++ close_db(pDest);
++ return DCR_Ok|rc;
+}
- #endif /* SQLITE_SHELL_HAVE_RECOVER */
-
++#endif /* !defined(SQLITE_SHELL_FIDDLE) */
/*
* zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it.
zSql[nSql] = ';';
zSql[nSql+1] = 0;
rc = sqlite3_complete(zSql);
+++ if( rc==SQLITE_NOMEM ) shell_out_of_memory();
zSql[nSql] = 0;
return rc;
}
char zPrefix[100];
const char *zErrorTail;
const char *zErrorType;
-- if( zErrMsg==0 ){
-- zErrorType = "Error";
-- zErrorTail = sqlite3_errmsg(p->db);
-- }else if( cli_strncmp(zErrMsg, "in prepare, ",12)==0 ){
-- zErrorType = "Parse error";
-- zErrorTail = &zErrMsg[12];
-- }else if( cli_strncmp(zErrMsg, "stepping, ", 10)==0 ){
-- zErrorType = "Runtime error";
-- zErrorTail = &zErrMsg[10];
-- }else{
-- zErrorType = "Error";
-- zErrorTail = zErrMsg;
-- }
-- if( in!=0 || !stdin_is_interactive ){
-- sqlite3_snprintf(sizeof(zPrefix), zPrefix,
-- "%s near line %d:", zErrorType, startline);
-- }else{
-- sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType);
++ if( psx->shellAbruptExit==0 ){
++ if( zErrMsg==0 ){
++ zErrorType = "Error";
++ zErrorTail = sqlite3_errmsg(DBX(psx));
++ }else if( cli_strncmp(zErrMsg, "in prepare, ",12)==0 ){
++ zErrorType = "Parse error";
++ zErrorTail = &zErrMsg[12];
++ }else if( cli_strncmp(zErrMsg, "stepping, ", 10)==0 ){
++ zErrorType = "Runtime error";
++ zErrorTail = &zErrMsg[10];
++ }else{
++ zErrorType = "Error";
++ zErrorTail = zErrMsg;
++ }
++ if( bAltIn || !stdin_is_interactive ){
++ sqlite3_snprintf(sizeof(zPrefix), zPrefix,
++ "%s near line %d:", zErrorType, startline);
++ }else{
++ sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType);
++ }
++ utf8_printf(STD_ERR, "%s %s\n", zPrefix, zErrorTail);
}
-- utf8_printf(stderr, "%s %s\n", zPrefix, zErrorTail);
sqlite3_free(zErrMsg);
-- zErrMsg = 0;
return 1;
-- }else if( ShellHasFlag(p, SHFLG_CountChanges) ){
-- char zLineBuf[2000];
++ }else if( ShellHasFlag(psx, SHFLG_CountChanges) ){
- char zLineBuf[2000];
+++ char zLineBuf[36+2*20];
sqlite3_snprintf(sizeof(zLineBuf), zLineBuf,
"changes: %lld total_changes: %lld",
-- sqlite3_changes64(p->db), sqlite3_total_changes64(p->db));
-- raw_printf(p->out, "%s\n", zLineBuf);
++ sqlite3_changes64(DBX(psx)), sqlite3_total_changes64(DBX(psx)));
++ raw_printf(ISS(psx)->out, "%s\n", zLineBuf);
}
return 0;
}
--static void echo_group_input(ShellState *p, const char *zDo){
-- if( ShellHasFlag(p, SHFLG_Echo) ) utf8_printf(p->out, "%s\n", zDo);
++#if SHELL_EXTENDED_PARSING
++/* Resumable line classsifier for dot-commands
++**
++** Determines if a dot-command is open, having either an unclosed
++** quoted argument or an escape sequence opener ('\') at its end.
++**
++** The FSM design/behavior assumes/requires that a terminating '\'
++** is not part of the character sequence being classified -- that
++** it represents an escaped newline which is removed as physical
++** lines are spliced to accumulate logical lines.
++**
++** The line or added line-portion is passed as zCmd.
++** The pScanState pointer must reference an (opaque) DCmd_ScanState,
++** which must be set to DCSS_Start to initialize the scanner state.
++** Resumed scanning should always be done with zCmd logically just
++** past the last non-0 char of the text previously passed in, with
++** any previously scanned, trailing newline escape first trimmed.
++** Returns are: 0 => not open (aka complete), 1 => is open (incomplete)
++** The following macros may be applied to the scan state:
++*/
++#define DCSS_InDarkArg(dcss) (((dcss)&argPosMask)==inDqArg)
++#define DCSS_EndEscaped(dcss) (((dcss)&endEscaped)!=0)
++#define DCSS_IsOpen(dcss) (((dcss)&isOpenMask)!=0)
++typedef enum {
++ DCSS_Start = 0,
++ twixtArgs = 0, inSqArg = 1, inDarkArg = 2, inDqArg = 3, /* ordered */
++ endEscaped = 4, /* bit used */
++ argPosMask = 3, /* bits used */
++ isOpenMask = 1|4 /* bit test */
++} DCmd_ScanState;
++
++static void dot_command_scan(char *zCmd, DCmd_ScanState *pScanState,
++ SCAN_TRACKER_REFTYPE pst){
++ DCmd_ScanState ss = *pScanState & ~endEscaped;
++ char c = (ss&isOpenMask)? 1 : *zCmd++;
++ while( c!=0 ){
++ switch( ss ){
++ case twixtArgs:
++ CONTINUE_PROMPT_AWAITC(pst, 0);
++ while( IsSpace(c) ){
++ if( (c=*zCmd++)==0 ) goto atEnd;
++ }
++ switch( c ){
++ case '\\':
++ if( *zCmd==0 ){
++ ss |= endEscaped;
++ goto atEnd;
++ }else goto inDark;
++ case '\'': ss = inSqArg; goto inSq;
++ case '"': ss = inDqArg; goto inDq;
++ default: ss = inDarkArg; goto inDark;
++ }
++ inSq:
++ case inSqArg:
++ CONTINUE_PROMPT_AWAITC(pst, '\'');
++ while( (c=*zCmd++)!='\'' ){
++ if( c==0 ) goto atEnd;
++ if( c=='\\' && *zCmd==0 ){
++ ss |= endEscaped;
++ goto atEnd;
++ }
++ }
++ ss = twixtArgs;
++ c = *zCmd++;
++ continue;
++ inDq:
++ case inDqArg:
++ CONTINUE_PROMPT_AWAITC(pst, '"');
++ do {
++ if( (c=*zCmd++)==0 ) goto atEnd;
++ if( c=='\\' ){
++ if( (c=*zCmd++)==0 ){
++ ss |= endEscaped;
++ goto atEnd;
++ }
++ if( (c=*zCmd++)==0 ) goto atEnd;
++ }
++ } while( c!='"' );
++ ss = twixtArgs;
++ c = *zCmd++;
++ continue;
++ inDark:
++ case inDarkArg:
++ CONTINUE_PROMPT_AWAITC(pst, 0);
++ while( !IsSpace(c) ){
++ if( c=='\\' && *zCmd==0 ){
++ ss |= endEscaped;
++ goto atEnd;
++ }
++ if( (c=*zCmd++)==0 ) goto atEnd;
++ }
++ ss = twixtArgs;
++ c = *zCmd++;
++ continue;
++ case endEscaped: case isOpenMask: default:
++ ; /* Not reachable, but quiet compilers unable to see this. */
++ }
++ }
++ atEnd:
++ *pScanState = ss;
}
++#else
++# define dot_command_scan(x,y,z)
++#endif
--#ifdef SQLITE_SHELL_FIDDLE
++/* Utility functions for process_input. */
++
++#if SHELL_EXTENDED_PARSING
/*
--** Alternate one_input_line() impl for wasm mode. This is not in the primary
--** impl because we need the global shellState and cannot access it from that
--** function without moving lots of code around (creating a larger/messier diff).
++** Process dot-command line with its scan state to:
++** 1. Setup for requested line-splicing; and
++** 2. Say whether it is complete.
++** The last two out parameters are the line's length, which may be
++** adjusted, and the char to be used for joining a subsequent line.
++** This is broken out of process_input() mainly for readability.
++** The return is TRUE for dot-command ready to run, else false.
*/
--static char *one_input_line(FILE *in, char *zPrior, int isContinuation){
-- /* Parse the next line from shellState.wasm.zInput. */
-- const char *zBegin = shellState.wasm.zPos;
-- const char *z = zBegin;
-- char *zLine = 0;
-- i64 nZ = 0;
--
-- UNUSED_PARAMETER(in);
-- UNUSED_PARAMETER(isContinuation);
-- if(!z || !*z){
++static int line_join_done(DCmd_ScanState dcss, char *zLine,
++ i64 *pnLength, char *pcLE){
++ /* It is ready only if has no open argument or escaped newline. */
++ int bOpen = DCSS_IsOpen(dcss);
++ if( !DCSS_EndEscaped(dcss) ){
++ *pcLE = '\n';
++ return !bOpen;
++ }else{
++ *pcLE = (bOpen || DCSS_InDarkArg(dcss))? 0 : ' ';
++ /* Swallow the trailing escape character. */
++ zLine[--*pnLength] = 0;
return 0;
}
-- while(*z && isspace(*z)) ++z;
-- zBegin = z;
-- for(; *z && '\n'!=*z; ++nZ, ++z){}
-- if(nZ>0 && '\r'==zBegin[nZ-1]){
-- --nZ;
++}
++#endif
++
++/*
++** Grow the accumulation line buffer to accommodate ncNeed chars.
++** In/out parameters pz and pna reference the buffer and its size.
++** The buffer must eventually be sqlite3_free()'ed by the caller.
++*/
++static void grow_line_buffer(char **pz, i64 *pna, int ncNeed){
++
++ if( ncNeed > *pna ){
++ *pna += *pna + (*pna>>1) + 100;
++ *pz = sqlite3_realloc(*pz, *pna);
++ shell_check_oom(*pz);
}
-- shellState.wasm.zPos = z;
-- zLine = realloc(zPrior, nZ+1);
-- shell_check_oom(zLine);
-- memcpy(zLine, zBegin, nZ);
-- zLine[nZ] = 0;
-- return zLine;
}
--#endif /* SQLITE_SHELL_FIDDLE */
/*
--** Read input from *in and process it. If *in==0 then input
--** is interactive - the user is typing it it. Otherwise, input
--** is coming from a file or device. A prompt is issued and history
--** is saved only if input is interactive. An interrupt signal will
--** cause this routine to exit immediately, unless input is interactive.
++** Read input from designated source (p->pInSource) and process it.
++** If pInSource==0 then input is interactive - the user is typing it.
++** Otherwise, input is coming from a file, stream device or string.
++** Prompts issue and history is saved only for interactive input.
++** An interrupt signal will cause this routine to exit immediately,
++** with "exit demanded" code returned, unless input is interactive.
**
--** Return the number of errors.
--*/
--static int process_input(ShellState *p){
-- char *zLine = 0; /* A single input line */
-- char *zSql = 0; /* Accumulated SQL text */
-- i64 nLine; /* Length of current line */
-- i64 nSql = 0; /* Bytes of zSql[] used */
-- i64 nAlloc = 0; /* Allocated zSql[] space */
-- int rc; /* Error code */
-- int errCnt = 0; /* Number of errors seen */
-- i64 startline = 0; /* Line number for start of current input */
-- QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */
--
-- if( p->inputNesting==MAX_INPUT_NESTING ){
-- /* This will be more informative in a later version. */
-- utf8_printf(stderr,"Input nesting limit (%d) reached at line %d."
-- " Check recursion.\n", MAX_INPUT_NESTING, p->lineno);
-- return 1;
-- }
-- ++p->inputNesting;
-- p->lineno = 0;
-- CONTINUE_PROMPT_RESET;
-- while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){
-- fflush(p->out);
-- zLine = one_input_line(p->in, zLine, nSql>0);
-- if( zLine==0 ){
-- /* End of input */
-- if( p->in==0 && stdin_is_interactive ) printf("\n");
-- break;
-- }
-- if( seenInterrupt ){
-- if( p->in!=0 ) break;
-- seenInterrupt = 0;
-- }
-- p->lineno++;
-- if( QSS_INPLAIN(qss)
-- && line_is_command_terminator(zLine)
-- && line_is_complete(zSql, nSql) ){
-- memcpy(zLine,";",2);
-- }
-- qss = quickscan(zLine, qss, CONTINUE_PROMPT_PSTATE);
-- if( QSS_PLAINWHITE(qss) && nSql==0 ){
-- /* Just swallow single-line whitespace */
-- echo_group_input(p, zLine);
-- qss = QSS_Start;
-- continue;
-- }
-- if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){
-- CONTINUE_PROMPT_RESET;
-- echo_group_input(p, zLine);
-- if( zLine[0]=='.' ){
-- rc = do_meta_command(zLine, p);
-- if( rc==2 ){ /* exit requested */
++** Returns are the post-execute values of enum DotCmdRC:
++** DCR_Ok, DCR_Return, DCR_Exit, DCR_Abort
++** each of which may be bit-wise or'ed with DCR_Error.
++*/
++static DotCmdRC process_input(ShellInState *psi){
++ char *zLineInput = 0; /* a line-at-a-time input buffer or usable result */
++ char *zLineAccum = 0; /* accumulation buffer, used for multi-line input */
++ /* Above two pointers could be local to the group handling loop, but are
++ * not so that the number of memory allocations can be reduced. They are
++ * reused from one incoming group to another, realloc()'ed as needed. */
++ i64 naAccum = 0; /* tracking how big zLineAccum buffer has become */
++ /* Some flags for ending the overall group processing loop, always 1 or 0 */
++ u8 bInputEnd=0, bInterrupted=0;
++ /* Termination kind: DCR_Ok, DCR_Error, DCR_Return, DCR_Exit, DCR_Abort,
++ * the greatest of whichever is applicable */
++ u8 termKind = DCR_Ok;
++ /* Flag to affect prompting and interrupt action */
++ u8 bInteractive = INSOURCE_IS_INTERACTIVE(psi->pInSource);
++ int nErrors = 0; /* count of errors during execution or its prep */
++
++ /* Block overly-recursive or absurdly nested input redirects. */
++ if( psi->inputNesting>=MAX_INPUT_NESTING ){
++ InSource *pInSrc = psi->pInSource->pFrom;
++ const char *zLead = "Input nesting limit ("
++ SHELL_STRINGIFY(MAX_INPUT_NESTING)") reached,";
++ int i = 3;
++ assert(pInSrc!=0 && MAX_INPUT_NESTING>0);
++ while( i-->0 && pInSrc!=0 ){
++ utf8_printf(STD_ERR,
++ "%s from line %d of \"%s\"",
++ zLead, pInSrc->lineno, pInSrc->zSourceSay);
++ zLead = (i%2==0)? "\n" : "";
++ pInSrc=pInSrc->pFrom;
++ }
++ utf8_printf(STD_ERR, " ...\nError: Check recursion.\n");
++ return DCR_Error;
++ }
++ ++psi->inputNesting;
++
++ /* line-group processing loop (per SQL block, dot-command or comment) */
++ while( !bInputEnd && termKind==DCR_Ok && !bInterrupted ){
++#if SHELL_DYNAMIC_EXTENSION
++ ScriptSupport *pSS = psi->script;
++#endif
++ int nGroupLines = 0; /* count of lines belonging to this group */
++ i64 ncLineIn = 0; /* how many (non-zero) chars are in zLineInput */
++ i64 ncLineAcc = 0; /* how many (non-zero) chars are in zLineAccum */
++ i64 iLastLine = 0; /* index of last accumulated line start */
++ /* Initialize resumable scanner(s). */
++ SqlScanState sqScanState = SSS_Start; /* for SQL scan */
++#if SHELL_EXTENDED_PARSING
++ DCmd_ScanState dcScanState = DCSS_Start; /* for dot-command scan */
++ int nLeadWhite = 0; /* skips over initial whitespace to . or # */
++ char cLineEnd = '\n'; /* May be swallowed or replaced with space. */
++#else
++# define nLeadWhite 0 /* For legacy parsing, no white before . or # . */
++# define cLineEnd '\n' /* For legacy parsing, this always joins lines. */
++#endif
++ /* An ordered enum to record kind of incoming line group. Its ordering
++ * means than a value greater than Comment implies something runnable.
++ */
++ enum { Tbd = 0, Eof, Comment, Sql, Cmd
++#if SHELL_DYNAMIC_EXTENSION
++ , Script
++#endif
++ } inKind = Tbd;
++ /* An enum signifying the group disposition state */
++ enum {
++ Incoming, Runnable, Dumpable, Erroneous, Ignore
++ } disposition = Incoming;
++ char **pzLineUse = &zLineInput; /* ref line to be processed */
++ i64 *pncLineUse = &ncLineIn; /* ref that line's char count */
++ int iStartline = 0; /* starting line number of group */
++
+++ seenInterrupt = 0;
++ fflush(psi->out);
++ CONTINUE_PROMPT_RESET;
++ zLineInput = one_input_line(psi->pInSource, zLineInput,
++ nGroupLines>0, &shellPrompts);
++ if( zLineInput==0 ){
++ bInputEnd = 1;
++ inKind = Eof;
++ disposition = Ignore;
++ if( bInteractive ) printf("\n");
++ }else{
++ ++nGroupLines;
++ iStartline = psi->pInSource->lineno;
++ ncLineIn = strlen30(zLineInput);
++ if( seenInterrupt ){
++ if( psi->pInSource!=0 ) break;
++ bInterrupted = 1; /* This will be honored, or not, later. */
++ seenInterrupt = 0;
++ disposition = Dumpable;
++ }
++ /* Classify and check for single-line dispositions, prep for more. */
++#if SHELL_EXTENDED_PARSING
++ nLeadWhite = (SHEXT_PARSING(psi))
++ ? skipWhite(zLineInput)-zLineInput
++ : 0; /* Disallow leading whitespace for . or # in legacy mode. */
++#endif
++#if SHELL_DYNAMIC_EXTENSION
++ if( pSS && pSS->pMethods->isScriptLeader(pSS, zLineInput+nLeadWhite) ){
++ inKind = Script;
++ }else
++#endif
++ {
++ switch( zLineInput[nLeadWhite] ){
++ case '.':
++ inKind = Cmd;
++ dot_command_scan(zLineInput+nLeadWhite, &dcScanState,
++ CONTINUE_PROMPT_PSTATE);
++ break;
++ case '#':
++ inKind = Comment;
++ break;
++ default:
++ /* Might be SQL, or a swallowable whole SQL comment. */
++ sql_prescan(zLineInput, &sqScanState, CONTINUE_PROMPT_PSTATE);
++ if( SSS_PLAINWHITE(sqScanState) ){
++ /* It's either all blank or a whole SQL comment. Swallowable. */
++ inKind = Comment;
++ }else{
++ /* Something dark, not a # comment or dot-command. Must be SQL. */
++ inKind = Sql;
++ }
break;
-- }else if( rc ){
-- errCnt++;
}
}
-- qss = QSS_Start;
-- continue;
-- }
-- /* No single-line dispositions remain; accumulate line(s). */
-- nLine = strlen(zLine);
-- if( nSql+nLine+2>=nAlloc ){
-- /* Grow buffer by half-again increments when big. */
-- nAlloc = nSql+(nSql>>1)+nLine+100;
-- zSql = realloc(zSql, nAlloc);
-- shell_check_oom(zSql);
-- }
-- if( nSql==0 ){
-- i64 i;
-- for(i=0; zLine[i] && IsSpace(zLine[i]); i++){}
-- assert( nAlloc>0 && zSql!=0 );
-- memcpy(zSql, zLine+i, nLine+1-i);
-- startline = p->lineno;
-- nSql = nLine-i;
-- }else{
-- zSql[nSql++] = '\n';
-- memcpy(zSql+nSql, zLine, nLine+1);
-- nSql += nLine;
-- }
-- if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){
-- echo_group_input(p, zSql);
-- errCnt += runOneSqlLine(p, zSql, p->in, startline);
-- CONTINUE_PROMPT_RESET;
-- nSql = 0;
-- if( p->outCount ){
-- output_reset(p);
-- p->outCount = 0;
-- }else{
-- clearTempFile(p);
++ } /* end read/classify initial group input line */
++
++ /* Here, if not at end of input, the initial line of group is in, and
++ * it has been scanned and classified. Next, do the processing needed
++ * to recognize whether the initial line or accumulated group so far
++ * is complete such that it may be run, and perform joining of more
++ * lines into the group while it is not so complete. This loop ends
++ * with the input group line(s) ready to be run, or if the input ends
++ * before it is ready, with the group marked as erroneous.
++ */
++ while( disposition==Incoming ){
++ PROMPTS_UPDATE(inKind == Sql || inKind == Cmd);
++ /* Check whether more to accumulate, or ready for final disposition. */
++ switch( inKind ){
++ case Comment:
++ disposition = Dumpable;
++ case Cmd:
++#if SHELL_EXTENDED_PARSING
++ if( SHEXT_PARSING(psi) ){
++ if( line_join_done(dcScanState, *pzLineUse, pncLineUse, &cLineEnd) ){
++ disposition = Runnable;
++ }
++ }else
++#endif
++ disposition = Runnable; /* Legacy, any dot-command line is ready. */
++ break;
++#if SHELL_DYNAMIC_EXTENSION
++ case Script:
++ if( pSS==0
++ || pSS->pMethods->scriptIsComplete(pSS, *pzLineUse+nLeadWhite, 0) ){
++ disposition = Runnable;
++ }
++ break;
++#endif
++ case Sql:
++ /* Check to see if it is complete and ready to run. */
- if( SSS_SEMITERM(sqScanState) && sqlite3_complete(*pzLineUse)){
+++ if( SSS_SEMITERM(sqScanState) && 1==sqlite3_complete(*pzLineUse)){
++ disposition = Runnable;
++ }else if( SSS_PLAINWHITE(sqScanState) ){
++ /* It is a leading single-line or multi-line comment. */
++ disposition = Runnable;
++ inKind = Comment;
++ }else{
++ char *zT = line_is_command_terminator(zLineInput);
++ if( zT!=0 ){
++ /* Last line is a lone go or / -- prep for running it. */
++ if( nGroupLines>1 ){
++ disposition = Runnable;
++ memcpy(*pzLineUse+iLastLine,";\n",3);
++ *pncLineUse = iLastLine + 2;
++ }else{
++ /* Unless nothing preceded it, then dump it. */
++ disposition = Dumpable;
++ }
++ }
++ }
++ break;
++ case Tbd: case Eof: default: assert(0); /* Not reachable */
++ } /* end switch on inKind */
++ /* Collect and accumulate more input if group not yet complete. */
++ if( disposition==Incoming ){
++ if( nGroupLines==1 ){
++ grow_line_buffer(&zLineAccum, &naAccum, ncLineIn+2);
++ /* Copy line just input */
++ memcpy(zLineAccum, zLineInput, ncLineIn);
++ zLineAccum[ncLineIn] = 0;
++ ncLineAcc = ncLineIn;
++ pzLineUse = &zLineAccum;
++ pncLineUse = &ncLineAcc;
++ }
++ /* Read in next line of group, (if available.) */
++ zLineInput = one_input_line(psi->pInSource, zLineInput,
++ nGroupLines>0, &shellPrompts);
++ if( zLineInput==0 ){
++ bInputEnd = 1;
++ if( inKind==Sql && psi->pInSource==&cmdInSource ){
++ /* As a special dispensation, SQL arguments on the command line
++ ** do not need to end with ';' (or a lone go.) */
++ if( nGroupLines>1 ){
++ grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+2);
++ }
++ strcpy( zLineAccum+ncLineAcc, ";" );
++ if( 1==sqlite3_complete(*pzLineUse) ){
++ zLineAccum[ncLineAcc] = 0;
++ disposition = Runnable;
++ continue;
++ }
++ }
++ disposition = Erroneous;
++ inKind = Eof;
++ if( bInteractive ) printf("\n");
++ continue;
++ }
++ ++nGroupLines;
++ ncLineIn = strlen30(zLineInput);
++ /* Scan line just input (if needed) and append to accumulation. */
++ switch( inKind ){
++ case Cmd:
++ dot_command_scan(zLineInput, &dcScanState, CONTINUE_PROMPT_PSTATE);
++ break;
++ case Sql:
++ sql_prescan(zLineInput, &sqScanState, CONTINUE_PROMPT_PSTATE);
++ break;
++ default:
++ break;
++ }
++ grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+ncLineIn+2);
++ /* Join lines as setup by exam of previous line(s). */
++ if( cLineEnd!=0 ) zLineAccum[ncLineAcc++] = cLineEnd;
++#if SHELL_EXTENDED_PARSING
++ cLineEnd = '\n'; /* reset to default after use */
++#endif
++ memcpy(zLineAccum+ncLineAcc, zLineInput, ncLineIn);
++ iLastLine = ncLineAcc;
++ ncLineAcc += ncLineIn;
++ zLineAccum[ncLineAcc] = 0;
++ } /* end glom another line */
++ } /* end group collection loop */
++ /* Here, the group is fully collected or known to be incomplete forever. */
++ CONTINUE_PROMPT_RESET;
++ switch( disposition ){
++ case Dumpable:
++ echo_group_input(psi, *pzLineUse);
++#if SHELL_DYNAMIC_EXTENSION
++ if( inKind==Script && pSS!=0 ) pSS->pMethods->resetCompletionScan(pSS);
++#endif
++ break;
++ case Runnable:
++ switch( inKind ){
++ case Sql:
++ echo_group_input(psi, *pzLineUse);
++ nErrors += runOneSqlLine(XSS(psi), *pzLineUse,
++ INSOURCE_IS_INTERACTIVE(psi->pInSource),
++ iStartline);
++ break;
++ case Cmd: {
++ DotCmdRC dcr;
++ echo_group_input(psi, *pzLineUse);
++ dcr = do_dot_command(*pzLineUse+nLeadWhite, XSS(psi));
++ nErrors += (dcr & DCR_Error);
++ dcr &= ~DCR_Error;
++ if( dcr > termKind ) termKind = dcr;
++ break;
++ }
++#if SHELL_DYNAMIC_EXTENSION
++ case Script: {
++ char *zErr = 0;
++ DotCmdRC dcr;
++ assert(pSS!=0);
++ /* Consider: Should echo flag be honored here? */
++ pSS->pMethods->resetCompletionScan(pSS);
++ dcr = pSS->pMethods->runScript(pSS, *pzLineUse+nLeadWhite,
++ XSS(psi), &zErr);
++ if( dcr!=DCR_Ok || zErr!=0 ){
++ /* Future: Handle errors more informatively and like dot commands. */
++ nErrors += (dcr!=DCR_Ok);
++ if( zErr!=0 ){
++ utf8_printf(STD_ERR, "Error: %s\n", zErr);
++ sqlite3_free(zErr);
++ }
++ }
++ break;
++ }
++#endif
++ default:
++ assert(inKind!=Tbd);
++ break;
++ }
++ if( XSS(psi)->shellAbruptExit!=0 ){
++ termKind = DCR_Exit;
}
-- p->bSafeMode = p->bSafeModePersist;
-- qss = QSS_Start;
-- }else if( nSql && QSS_PLAINWHITE(qss) ){
-- echo_group_input(p, zSql);
-- nSql = 0;
-- qss = QSS_Start;
++ break;
++ case Erroneous:
++ utf8_printf(STD_ERR, "Error: Input incomplete at line %d of \"%s\"\n",
++ psi->pInSource->lineno, psi->pInSource->zSourceSay);
++#if SHELL_DYNAMIC_EXTENSION
++ if( inKind==Script && pSS!=0 ) pSS->pMethods->resetCompletionScan(pSS);
++#endif
++ ++nErrors;
++ break;
++ case Ignore:
++ break;
++ default: assert(0);
}
++ if( bail_on_error && nErrors>0 && termKind==DCR_Ok ) termKind = DCR_Error;
++ } /* end group consume/prep/(run, dump or complain) loop */
++
++ /* Cleanup and determine return value based on flags and error count. */
++ free(zLineInput); /* Allocated via malloc() by readline or equivalents. */
++ sqlite3_free(zLineAccum);
++
++ /* Translate DCR_Return because it has been done here, not to propagate
++ * unless input is from shell invocation argument. */
++ if( termKind==DCR_Return && psi->pInSource!=&cmdInSource ){
++ termKind = DCR_Ok;
}
-- if( nSql ){
-- /* This may be incomplete. Let the SQL parser deal with that. */
-- echo_group_input(p, zSql);
-- errCnt += runOneSqlLine(p, zSql, p->in, startline);
-- CONTINUE_PROMPT_RESET;
-- }
-- free(zSql);
-- free(zLine);
-- --p->inputNesting;
-- return errCnt>0;
++ return termKind|(nErrors>0);
}
/*
return argv[i];
}
+++static void zapGlobalDbLock(void){
+++ if( pGlobalDbLock ){
+++ sqlite3_mutex_free(pGlobalDbLock);
+++ pGlobalDbLock = 0;
+++ }
+++}
static void sayAbnormalExit(void){
if( seenInterrupt ) fprintf(stderr, "Program interrupted.\n");
}
exit(1);
}
#endif
-- main_init(&data);
++ main_init(&datai,&datax);
++#if SHELL_DATAIO_EXT
++ datai.pFreeformExporter = (ExportHandler*)&ffExporter;
++ datai.pColumnarExporter = (ExportHandler*)&cmExporter;
++ datai.pActiveExporter = (ExportHandler*)&ffExporter;
++#endif
-- /* On Windows, we must translate command-line arguments into UTF-8.
-- ** The SQLite memory allocator subsystem has to be enabled in order to
-- ** do this. But we want to run an sqlite3_shutdown() afterwards so that
-- ** subsequent sqlite3_config() calls will work. So copy all results into
-- ** memory that does not come from the SQLite memory allocator.
++ /* From here on, within the true clause of this next test, various
++ ** heap allocations are made which may fail, resulting in an abrupt
++ ** shell exit. Such an exit happens in 1 of 2 ways: A held resource
++ ** stack and the call stack are ripped back to this point; or just
++ ** the held resource stack is ripped back and a process exit occurs.
*/
++ register_exit_ripper(&exit_jb, entry_mark);
++ if( 0==RIP_TO_HERE(exit_jb) ){
++
++ /* On Windows, we must translate command-line arguments into UTF-8.
++ ** The SQLite memory allocator subsystem has to be enabled in order to
++ ** do this. But we want to run an sqlite3_shutdown() afterwards so that
++ ** subsequent sqlite3_config() calls will work. So copy all results into
++ ** memory that does not come from the SQLite memory allocator.
++ */
#if !SQLITE_SHELL_IS_UTF8
-- sqlite3_initialize();
-- argvToFree = malloc(sizeof(argv[0])*argc*2);
-- shell_check_oom(argvToFree);
-- argcToFree = argc;
-- argv = argvToFree + argc;
-- for(i=0; i<argc; i++){
-- char *z = sqlite3_win32_unicode_to_utf8(wargv[i]);
-- i64 n;
-- shell_check_oom(z);
-- n = strlen(z);
-- argv[i] = malloc( n+1 );
-- shell_check_oom(argv[i]);
-- memcpy(argv[i], z, n+1);
-- argvToFree[i] = argv[i];
-- sqlite3_free(z);
-- }
-- sqlite3_shutdown();
++ sqlite3_initialize();
++ argvToFree = malloc(sizeof(argv[0])*argc*2);
++ shell_check_oom(argvToFree);
++ argcToFree = argc;
++ argv = argvToFree + argc;
++ for(i=0; i<argc; i++){
++ char *z = sqlite3_win32_unicode_to_utf8(wargv[i]);
++ i64 n;
++ shell_check_oom(z);
++ n = strlen(z);
++ argv[i] = malloc( n+1 );
++ shell_check_oom(argv[i]);
++ memcpy(argv[i], z, n+1);
++ argvToFree[i] = argv[i];
++ sqlite3_free(z);
++ }
++ sqlite3_shutdown();
#endif
-- assert( argc>=1 && argv && argv[0] );
-- Argv0 = argv[0];
++ assert( argc>=1 && argv && argv[0] );
++ Argv0 = argv[0];
++#if SHELL_DYNAMIC_EXTENSION
++ initStartupDir();
++ if( isExtendedBasename(Argv0) ){
++ datai.bExtendedDotCmds = SHELL_ALL_EXTENSIONS;
++ }
++#endif
#ifdef SQLITE_SHELL_DBNAME_PROC
-- {
-- /* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name
-- ** of a C-function that will provide the name of the database file. Use
-- ** this compile-time option to embed this shell program in larger
-- ** applications. */
-- extern void SQLITE_SHELL_DBNAME_PROC(const char**);
-- SQLITE_SHELL_DBNAME_PROC(&data.pAuxDb->zDbFilename);
-- warnInmemoryDb = 0;
-- }
++ {
++ /* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name
++ ** of a C-function that will provide the name of the database file. Use
++ ** this compile-time option to embed this shell program in larger
++ ** applications. */
++ extern void SQLITE_SHELL_DBNAME_PROC(const char**);
++ SQLITE_SHELL_DBNAME_PROC(&datai.pAuxDb->zDbFilename);
++ warnInmemoryDb = 0;
++ }
#endif
-- /* Do an initial pass through the command-line argument to locate
-- ** the name of the database file, the name of the initialization file,
-- ** the size of the alternative malloc heap,
-- ** and the first command to execute.
-- */
++ /* Do an initial pass through the command-line argument to locate
++ ** the name of the database file, the name of the initialization file,
++ ** the size of the alternative malloc heap,
++ ** and the first command to execute.
++ */
#ifndef SQLITE_SHELL_FIDDLE
-- verify_uninitialized();
--#endif
-- for(i=1; i<argc; i++){
-- char *z;
-- z = argv[i];
-- if( z[0]!='-' || i>nOptsEnd ){
-- if( data.aAuxDb->zDbFilename==0 ){
-- data.aAuxDb->zDbFilename = z;
-- }else{
-- /* Excesss arguments are interpreted as SQL (or dot-commands) and
-- ** mean that nothing is read from stdin */
-- readStdin = 0;
-- nCmd++;
-- azCmd = realloc(azCmd, sizeof(azCmd[0])*nCmd);
-- shell_check_oom(azCmd);
-- azCmd[nCmd-1] = z;
-- }
-- continue;
-- }
-- if( z[1]=='-' ) z++;
-- if( cli_strcmp(z, "-")==0 ){
-- nOptsEnd = i;
-- continue;
-- }else if( cli_strcmp(z,"-separator")==0
-- || cli_strcmp(z,"-nullvalue")==0
-- || cli_strcmp(z,"-newline")==0
-- || cli_strcmp(z,"-cmd")==0
-- ){
-- (void)cmdline_option_value(argc, argv, ++i);
-- }else if( cli_strcmp(z,"-init")==0 ){
-- zInitFile = cmdline_option_value(argc, argv, ++i);
-- }else if( cli_strcmp(z,"-batch")==0 ){
-- /* Need to check for batch mode here to so we can avoid printing
-- ** informational messages (like from process_sqliterc) before
-- ** we do the actual processing of arguments later in a second pass.
-- */
-- stdin_is_interactive = 0;
-- }else if( cli_strcmp(z,"-heap")==0 ){
--#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
-- const char *zSize;
-- sqlite3_int64 szHeap;
--
-- zSize = cmdline_option_value(argc, argv, ++i);
-- szHeap = integerValue(zSize);
-- if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000;
-- verify_uninitialized();
-- sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64);
--#else
-- (void)cmdline_option_value(argc, argv, ++i);
--#endif
-- }else if( cli_strcmp(z,"-pagecache")==0 ){
-- sqlite3_int64 n, sz;
-- sz = integerValue(cmdline_option_value(argc,argv,++i));
-- if( sz>70000 ) sz = 70000;
-- if( sz<0 ) sz = 0;
-- n = integerValue(cmdline_option_value(argc,argv,++i));
-- if( sz>0 && n>0 && 0xffffffffffffLL/sz<n ){
-- n = 0xffffffffffffLL/sz;
-- }
-- verify_uninitialized();
-- sqlite3_config(SQLITE_CONFIG_PAGECACHE,
-- (n>0 && sz>0) ? malloc(n*sz) : 0, sz, n);
-- data.shellFlgs |= SHFLG_Pagecache;
-- }else if( cli_strcmp(z,"-lookaside")==0 ){
-- int n, sz;
-- sz = (int)integerValue(cmdline_option_value(argc,argv,++i));
-- if( sz<0 ) sz = 0;
-- n = (int)integerValue(cmdline_option_value(argc,argv,++i));
-- if( n<0 ) n = 0;
-- verify_uninitialized();
-- sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n);
-- if( sz*n==0 ) data.shellFlgs &= ~SHFLG_Lookaside;
-- }else if( cli_strcmp(z,"-threadsafe")==0 ){
-- int n;
-- n = (int)integerValue(cmdline_option_value(argc,argv,++i));
-- verify_uninitialized();
-- switch( n ){
-- case 0: sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); break;
-- case 2: sqlite3_config(SQLITE_CONFIG_MULTITHREAD); break;
-- default: sqlite3_config(SQLITE_CONFIG_SERIALIZED); break;
-- }
--#ifdef SQLITE_ENABLE_VFSTRACE
-- }else if( cli_strcmp(z,"-vfstrace")==0 ){
-- extern int vfstrace_register(
-- const char *zTraceName,
-- const char *zOldVfsName,
-- int (*xOut)(const char*,void*),
-- void *pOutArg,
-- int makeDefault
-- );
-- vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,stderr,1);
--#endif
--#ifdef SQLITE_ENABLE_MULTIPLEX
-- }else if( cli_strcmp(z,"-multiplex")==0 ){
-- extern int sqlite3_multiple_initialize(const char*,int);
-- sqlite3_multiplex_initialize(0, 1);
--#endif
-- }else if( cli_strcmp(z,"-mmap")==0 ){
-- sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
-- verify_uninitialized();
-- sqlite3_config(SQLITE_CONFIG_MMAP_SIZE, sz, sz);
--#if defined(SQLITE_ENABLE_SORTER_REFERENCES)
-- }else if( cli_strcmp(z,"-sorterref")==0 ){
-- sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
-- verify_uninitialized();
-- sqlite3_config(SQLITE_CONFIG_SORTERREF_SIZE, (int)sz);
--#endif
-- }else if( cli_strcmp(z,"-vfs")==0 ){
-- zVfs = cmdline_option_value(argc, argv, ++i);
--#ifdef SQLITE_HAVE_ZLIB
-- }else if( cli_strcmp(z,"-zip")==0 ){
-- data.openMode = SHELL_OPEN_ZIPFILE;
--#endif
-- }else if( cli_strcmp(z,"-append")==0 ){
-- data.openMode = SHELL_OPEN_APPENDVFS;
--#ifndef SQLITE_OMIT_DESERIALIZE
-- }else if( cli_strcmp(z,"-deserialize")==0 ){
-- data.openMode = SHELL_OPEN_DESERIALIZE;
-- }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
-- data.szMax = integerValue(argv[++i]);
--#endif
-- }else if( cli_strcmp(z,"-readonly")==0 ){
-- data.openMode = SHELL_OPEN_READONLY;
-- }else if( cli_strcmp(z,"-nofollow")==0 ){
-- data.openFlags = SQLITE_OPEN_NOFOLLOW;
--#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
-- }else if( cli_strncmp(z, "-A",2)==0 ){
-- /* All remaining command-line arguments are passed to the ".archive"
-- ** command, so ignore them */
-- break;
++ verify_uninitialized();
#endif
-- }else if( cli_strcmp(z, "-memtrace")==0 ){
-- sqlite3MemTraceActivate(stderr);
-- }else if( cli_strcmp(z,"-bail")==0 ){
-- bail_on_error = 1;
-- }else if( cli_strcmp(z,"-nonce")==0 ){
-- free(data.zNonce);
-- data.zNonce = strdup(argv[++i]);
-- }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
-- ShellSetFlag(&data,SHFLG_TestingMode);
-- }else if( cli_strcmp(z,"-safe")==0 ){
-- /* no-op - catch this on the second pass */
-- }
-- }
++ i = scanInvokeArgs(argc, argv, 1, &datai, &cmdArgs, &argsData);
#ifndef SQLITE_SHELL_FIDDLE
-- verify_uninitialized();
++ verify_uninitialized();
#endif
--
#ifdef SQLITE_SHELL_INIT_PROC
-- {
-- /* If the SQLITE_SHELL_INIT_PROC macro is defined, then it is the name
-- ** of a C-function that will perform initialization actions on SQLite that
-- ** occur just before or after sqlite3_initialize(). Use this compile-time
-- ** option to embed this shell program in larger applications. */
-- extern void SQLITE_SHELL_INIT_PROC(void);
-- SQLITE_SHELL_INIT_PROC();
-- }
++ {
++ /* If the SQLITE_SHELL_INIT_PROC macro is defined, then it is the name
++ ** of a C-function that will perform initialization actions on SQLite that
++ ** occur just before or after sqlite3_initialize(). Use this compile-time
++ ** option to embed this shell program in larger applications. */
++ extern void SQLITE_SHELL_INIT_PROC(void);
++ SQLITE_SHELL_INIT_PROC();
++ }
#else
-- /* All the sqlite3_config() calls have now been made. So it is safe
-- ** to call sqlite3_initialize() and process any command line -vfs option. */
-- sqlite3_initialize();
++ /* All the sqlite3_config() calls have now been made. So it is safe
++ ** to call sqlite3_initialize() and process any command line -vfs option. */
++ sqlite3_initialize();
#endif
-- if( zVfs ){
-- sqlite3_vfs *pVfs = sqlite3_vfs_find(zVfs);
-- if( pVfs ){
-- sqlite3_vfs_register(pVfs, 1);
-- }else{
-- utf8_printf(stderr, "no such VFS: \"%s\"\n", zVfs);
-- exit(1);
+++ /* Create a mutex for thread-safe query execution interruption. */
+++ pGlobalDbLock = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+++ atexit(zapGlobalDbLock);
++ /* Register the control-C (SIGINT) handler.
++ ** Make sure we have a valid signal handler early, before anything
++ ** is done that might take long. */
- pGlobalDbLock = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
++#ifdef SIGINT
++ signal(SIGINT, interrupt_handler);
++#elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE)
++ SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
++#endif
++
++ if( argsData.zVfs ){
++ sqlite3_vfs *pVfs = sqlite3_vfs_find(argsData.zVfs);
++ if( pVfs ){
++ sqlite3_vfs_register(pVfs, 1);
++ }else{
++ utf8_printf(STD_ERR, "no such VFS: \"%s\"\n", argsData.zVfs);
++ rc = 1;
++ goto shell_bail;
++ }
}
-- }
-- if( data.pAuxDb->zDbFilename==0 ){
++ if( datai.pAuxDb->zDbFilename==0 ){
#ifndef SQLITE_OMIT_MEMORYDB
-- data.pAuxDb->zDbFilename = ":memory:";
-- warnInmemoryDb = argc==1;
++ datai.pAuxDb->zDbFilename = ":memory:";
++ warnInmemoryDb = argc==1;
#else
-- utf8_printf(stderr,"%s: Error: no database filename specified\n", Argv0);
-- return 1;
++ utf8_printf(STD_ERR,"%s: Error: no database filename specified\n", Argv0);
++ rc = 1;
++ goto shell_bail;
#endif
-- }
-- data.out = stdout;
++ }
++ datai.out = STD_OUT;
#ifndef SQLITE_SHELL_FIDDLE
-- sqlite3_appendvfs_init(0,0,0);
++ sqlite3_appendvfs_init(0,0,0);
#endif
-- /* Go ahead and open the database file if it already exists. If the
-- ** file does not exist, delay opening it. This prevents empty database
-- ** files from being created if a user mistypes the database name argument
-- ** to the sqlite command-line tool.
-- */
-- if( access(data.pAuxDb->zDbFilename, 0)==0 ){
-- open_db(&data, 0);
-- }
++ /* Go ahead and open the database file if it already exists. If the
++ ** file does not exist, delay opening it. This prevents empty database
++ ** files from being created if a user mistypes the database name argument
++ ** to the sqlite command-line tool.
++ */
++ if( access(datai.pAuxDb->zDbFilename, 0)==0 ){
++ open_db(&datax, 0);
++ }
-- /* Process the initialization file if there is one. If no -init option
-- ** is given on the command line, look for a file named ~/.sqliterc and
-- ** try to process it.
-- */
-- process_sqliterc(&data,zInitFile);
++ /* Process the initialization file if there is one. If no -init option
++ ** is given on the command line, look for a file named ~/.sqliterc and
++ ** try to process it, without any quitting or bail-on-error.
++ */
++ process_sqliterc(&datai,argsData.zInitFile);
++
++ /* Make a second pass through the command-line argument and set
++ ** options. This second pass is delayed until after the initialization
++ ** file is processed so that the command-line arguments will override
++ ** settings in the initialization file.
++ */
++ rc = scanInvokeArgs(argc, argv, 2, &datai, &cmdArgs, &argsData);
++ if( rc>0 ){
++ goto shell_bail;
++ }
-- /* Make a second pass through the command-line argument and set
-- ** options. This second pass is delayed until after the initialization
-- ** file is processed so that the command-line arguments will override
-- ** settings in the initialization file.
-- */
-- for(i=1; i<argc; i++){
-- char *z = argv[i];
-- if( z[0]!='-' || i>=nOptsEnd ) continue;
-- if( z[1]=='-' ){ z++; }
-- if( cli_strcmp(z,"-init")==0 ){
-- i++;
-- }else if( cli_strcmp(z,"-html")==0 ){
-- data.mode = MODE_Html;
-- }else if( cli_strcmp(z,"-list")==0 ){
-- data.mode = MODE_List;
-- }else if( cli_strcmp(z,"-quote")==0 ){
-- data.mode = MODE_Quote;
-- sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Comma);
-- sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, SEP_Row);
-- }else if( cli_strcmp(z,"-line")==0 ){
-- data.mode = MODE_Line;
-- }else if( cli_strcmp(z,"-column")==0 ){
-- data.mode = MODE_Column;
-- }else if( cli_strcmp(z,"-json")==0 ){
-- data.mode = MODE_Json;
-- }else if( cli_strcmp(z,"-markdown")==0 ){
-- data.mode = MODE_Markdown;
-- }else if( cli_strcmp(z,"-table")==0 ){
-- data.mode = MODE_Table;
-- }else if( cli_strcmp(z,"-box")==0 ){
-- data.mode = MODE_Box;
-- }else if( cli_strcmp(z,"-csv")==0 ){
-- data.mode = MODE_Csv;
-- memcpy(data.colSeparator,",",2);
--#ifdef SQLITE_HAVE_ZLIB
-- }else if( cli_strcmp(z,"-zip")==0 ){
-- data.openMode = SHELL_OPEN_ZIPFILE;
--#endif
-- }else if( cli_strcmp(z,"-append")==0 ){
-- data.openMode = SHELL_OPEN_APPENDVFS;
--#ifndef SQLITE_OMIT_DESERIALIZE
-- }else if( cli_strcmp(z,"-deserialize")==0 ){
-- data.openMode = SHELL_OPEN_DESERIALIZE;
-- }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
-- data.szMax = integerValue(argv[++i]);
--#endif
-- }else if( cli_strcmp(z,"-readonly")==0 ){
-- data.openMode = SHELL_OPEN_READONLY;
-- }else if( cli_strcmp(z,"-nofollow")==0 ){
-- data.openFlags |= SQLITE_OPEN_NOFOLLOW;
-- }else if( cli_strcmp(z,"-ascii")==0 ){
-- data.mode = MODE_Ascii;
-- sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Unit);
-- sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Record);
-- }else if( cli_strcmp(z,"-tabs")==0 ){
-- data.mode = MODE_List;
-- sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Tab);
-- sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Row);
-- }else if( cli_strcmp(z,"-separator")==0 ){
-- sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,
-- "%s",cmdline_option_value(argc,argv,++i));
-- }else if( cli_strcmp(z,"-newline")==0 ){
-- sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,
-- "%s",cmdline_option_value(argc,argv,++i));
-- }else if( cli_strcmp(z,"-nullvalue")==0 ){
-- sqlite3_snprintf(sizeof(data.nullValue), data.nullValue,
-- "%s",cmdline_option_value(argc,argv,++i));
-- }else if( cli_strcmp(z,"-header")==0 ){
-- data.showHeader = 1;
-- ShellSetFlag(&data, SHFLG_HeaderSet);
-- }else if( cli_strcmp(z,"-noheader")==0 ){
-- data.showHeader = 0;
-- ShellSetFlag(&data, SHFLG_HeaderSet);
-- }else if( cli_strcmp(z,"-echo")==0 ){
-- ShellSetFlag(&data, SHFLG_Echo);
-- }else if( cli_strcmp(z,"-eqp")==0 ){
-- data.autoEQP = AUTOEQP_on;
-- }else if( cli_strcmp(z,"-eqpfull")==0 ){
-- data.autoEQP = AUTOEQP_full;
-- }else if( cli_strcmp(z,"-stats")==0 ){
-- data.statsOn = 1;
-- }else if( cli_strcmp(z,"-scanstats")==0 ){
-- data.scanstatsOn = 1;
-- }else if( cli_strcmp(z,"-backslash")==0 ){
-- /* Undocumented command-line option: -backslash
-- ** Causes C-style backslash escapes to be evaluated in SQL statements
-- ** prior to sending the SQL into SQLite. Useful for injecting
-- ** crazy bytes in the middle of SQL statements for testing and debugging.
-- */
-- ShellSetFlag(&data, SHFLG_Backslash);
-- }else if( cli_strcmp(z,"-bail")==0 ){
-- /* No-op. The bail_on_error flag should already be set. */
-- }else if( cli_strcmp(z,"-version")==0 ){
-- printf("%s %s\n", sqlite3_libversion(), sqlite3_sourceid());
-- return 0;
-- }else if( cli_strcmp(z,"-interactive")==0 ){
-- stdin_is_interactive = 1;
-- }else if( cli_strcmp(z,"-batch")==0 ){
-- stdin_is_interactive = 0;
-- }else if( cli_strcmp(z,"-utf8")==0 ){
#if SHELL_WIN_UTF8_OPT
-- console_utf8 = 1;
--#endif /* SHELL_WIN_UTF8_OPT */
-- }else if( cli_strcmp(z,"-heap")==0 ){
-- i++;
-- }else if( cli_strcmp(z,"-pagecache")==0 ){
-- i+=2;
-- }else if( cli_strcmp(z,"-lookaside")==0 ){
-- i+=2;
-- }else if( cli_strcmp(z,"-threadsafe")==0 ){
-- i+=2;
-- }else if( cli_strcmp(z,"-nonce")==0 ){
-- i += 2;
-- }else if( cli_strcmp(z,"-mmap")==0 ){
-- i++;
-- }else if( cli_strcmp(z,"-memtrace")==0 ){
-- i++;
--#ifdef SQLITE_ENABLE_SORTER_REFERENCES
-- }else if( cli_strcmp(z,"-sorterref")==0 ){
-- i++;
--#endif
-- }else if( cli_strcmp(z,"-vfs")==0 ){
-- i++;
--#ifdef SQLITE_ENABLE_VFSTRACE
-- }else if( cli_strcmp(z,"-vfstrace")==0 ){
-- i++;
--#endif
--#ifdef SQLITE_ENABLE_MULTIPLEX
-- }else if( cli_strcmp(z,"-multiplex")==0 ){
-- i++;
--#endif
-- }else if( cli_strcmp(z,"-help")==0 ){
-- usage(1);
-- }else if( cli_strcmp(z,"-cmd")==0 ){
-- /* Run commands that follow -cmd first and separately from commands
-- ** that simply appear on the command-line. This seems goofy. It would
-- ** be better if all commands ran in the order that they appear. But
-- ** we retain the goofy behavior for historical compatibility. */
-- if( i==argc-1 ) break;
-- z = cmdline_option_value(argc,argv,++i);
-- if( z[0]=='.' ){
-- rc = do_meta_command(z, &data);
-- if( rc && bail_on_error ) return rc==2 ? 0 : rc;
-- }else{
-- open_db(&data, 0);
-- rc = shell_exec(&data, z, &zErrMsg);
-- if( zErrMsg!=0 ){
-- utf8_printf(stderr,"Error: %s\n", zErrMsg);
-- if( bail_on_error ) return rc!=0 ? rc : 1;
-- }else if( rc!=0 ){
-- utf8_printf(stderr,"Error: unable to process SQL \"%s\"\n", z);
-- if( bail_on_error ) return rc;
-- }
-- }
--#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
-- }else if( cli_strncmp(z, "-A", 2)==0 ){
-- if( nCmd>0 ){
-- utf8_printf(stderr, "Error: cannot mix regular SQL or dot-commands"
-- " with \"%s\"\n", z);
-- return 1;
-- }
-- open_db(&data, OPEN_DB_ZIPFILE);
-- if( z[2] ){
-- argv[i] = &z[2];
-- arDotCommand(&data, 1, argv+(i-1), argc-(i-1));
-- }else{
-- arDotCommand(&data, 1, argv+i, argc-i);
-- }
-- readStdin = 0;
-- break;
--#endif
-- }else if( cli_strcmp(z,"-safe")==0 ){
-- data.bSafeMode = data.bSafeModePersist = 1;
-- }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
-- /* Acted upon in first pass. */
++ if( console_utf8 && stdin_is_interactive ){
++ console_prepare();
}else{
-- utf8_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z);
-- raw_printf(stderr,"Use -help for a list of options.\n");
-- return 1;
++ setBinaryMode(stdin, 0);
++ console_utf8 = 0;
}
-- data.cMode = data.mode;
-- }
--#if SHELL_WIN_UTF8_OPT
-- if( console_utf8 && stdin_is_interactive ){
-- console_prepare();
-- }else{
-- setBinaryMode(stdin, 0);
-- console_utf8 = 0;
-- }
#endif
-- if( !readStdin ){
-- /* Run all arguments that do not begin with '-' as if they were separate
-- ** command-line inputs, except for the argToSkip argument which contains
-- ** the database filename.
-- */
-- for(i=0; i<nCmd; i++){
-- if( azCmd[i][0]=='.' ){
-- rc = do_meta_command(azCmd[i], &data);
-- if( rc ){
-- free(azCmd);
-- return rc==2 ? 0 : rc;
-- }
-- }else{
-- open_db(&data, 0);
-- echo_group_input(&data, azCmd[i]);
-- rc = shell_exec(&data, azCmd[i], &zErrMsg);
-- if( zErrMsg || rc ){
-- if( zErrMsg!=0 ){
-- utf8_printf(stderr,"Error: %s\n", zErrMsg);
-- }else{
-- utf8_printf(stderr,"Error: unable to process SQL: %s\n", azCmd[i]);
-- }
-- sqlite3_free(zErrMsg);
-- free(azCmd);
-- return rc!=0 ? rc : 1;
-- }
-- }
-- }
-- }else{
-- /* Run commands received from standard input
-- */
-- if( stdin_is_interactive ){
-- char *zHome;
-- char *zHistory;
-- int nHistory;
-- printf(
-- "SQLite version %s %.19s\n" /*extra-version-info*/
-- "Enter \".help\" for usage hints.\n",
-- sqlite3_libversion(), sqlite3_sourceid()
-- );
-- if( warnInmemoryDb ){
-- printf("Connected to a ");
-- printBold("transient in-memory database");
-- printf(".\nUse \".open FILENAME\" to reopen on a "
-- "persistent database.\n");
-- }
-- zHistory = getenv("SQLITE_HISTORY");
-- if( zHistory ){
-- zHistory = strdup(zHistory);
-- }else if( (zHome = find_home_dir(0))!=0 ){
-- nHistory = strlen30(zHome) + 20;
-- if( (zHistory = malloc(nHistory))!=0 ){
-- sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome);
++ if( !argsData.readStdin ){
++ /* Run all arguments that are not the DB name or do not begin with '-'
++ ** as if they were separate command-line inputs. */
++ for(i=0; i<cmdArgs.nCmd && rc<2; i++){
++ set_invocation_cmd(cmdArgs.azCmd[i]);
++ drc = process_input(&datai);
++ rc = (drc>2)? 2 : drc;
++ if( rc>0 ){
++ goto shell_bail;
}
}
-- if( zHistory ){ shell_read_history(zHistory); }
++ }else{
++ /* Run commands received from standard input
++ */
++ if( stdin_is_interactive ){
++ char *zHome;
++ char *zHistory = 0;
++ if( argsData.bQuiet ){
++ /* bQuiet is almost like normal interactive, but quieter
++ ** and avoids history keeping and line editor completions. */
++ mainPrompt[0] = 0;
++ continuePrompt[0] = 0;
++ }else{
++ fprintf(STD_OUT,
++ "SQLite version %s %.19s\n" /*extra-version-info*/
++ "Enter \".help\" for usage hints.\n",
++ sqlite3_libversion(), sqlite3_sourceid()
++ );
++ if( warnInmemoryDb ){
++ fprintf(STD_OUT, "Connected to a ");
++ printBold("transient in-memory database");
++ fprintf(STD_OUT, ".\nUse \".open FILENAME\" to reopen on a "
++ "persistent database.\n");
++ }
++ zHistory = getenv("SQLITE_HISTORY");
++ if( zHistory ){
++ zHistory = strdup(zHistory);
++ }else if( (zHome = find_home_dir(0))!=0 ){
++ int nHistory = strlen30(zHome) + 20;
++ if( (zHistory = malloc(nHistory))!=0 ){
++ sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome);
++ }
++ }
++ if( zHistory ){ shell_read_history(zHistory); }
#if HAVE_READLINE || HAVE_EDITLINE
-- rl_attempted_completion_function = readline_completion;
++ rl_attempted_completion_function = readline_completion;
#elif HAVE_LINENOISE
-- linenoiseSetCompletionCallback(linenoise_completion);
++ linenoiseSetCompletionCallback(linenoise_completion);
#endif
-- data.in = 0;
-- rc = process_input(&data);
-- if( zHistory ){
-- shell_stifle_history(2000);
-- shell_write_history(zHistory);
-- free(zHistory);
++ }
++ datai.pInSource = &termInSource; /* read from stdin interactively */
++ drc = process_input(&datai);
++ rc = (drc>2)? 2 : drc;
++ if( !bQuiet ){
++ if( zHistory ){
++ shell_stifle_history(2000);
++ shell_write_history(zHistory);
++ free(zHistory);
++ }
++ }
++ }else{
++ datai.pInSource = &stdInSource; /* read from stdin without prompts */
++ drc = process_input(&datai);
++ rc = (drc>2)? 2 : drc;
}
-- }else{
-- data.in = stdin;
-- rc = process_input(&data);
}
++ }else{
++ /* An abrupt, stack-ripping exit arrives here. */
}
++ shell_bail:
++ holder_free(entry_mark);
#ifndef SQLITE_SHELL_FIDDLE
/* In WASM mode we have to leave the db state in place so that
-- ** client code can "push" SQL into it after this call returns. */
-- free(azCmd);
-- set_table_name(&data, 0);
-- if( data.db ){
-- session_close_all(&data, -1);
-- close_db(data.db);
++ ** client code can "push" SQL into it after this call returns.
++ ** For that build, just bypass freeing all acquired resources.
++ */
++ set_table_name(&datax, 0);
++ if( datax.dbUser ){
++ session_close_all(&datai, -1);
++# if SHELL_DYNAMIC_EXTENSION
++ notify_subscribers(&datai, NK_DbAboutToClose, datax.dbUser);
++# endif
++ close_db(datax.dbUser);
}
-- for(i=0; i<ArraySize(data.aAuxDb); i++){
-- sqlite3_free(data.aAuxDb[i].zFreeOnClose);
-- if( data.aAuxDb[i].db ){
-- session_close_all(&data, i);
-- close_db(data.aAuxDb[i].db);
+++# ifdef SQLITE_DEBUG
+++ /* Do this redundantly with atexit() to aid memory leak reporting. */
++ sqlite3_mutex_free(pGlobalDbLock);
++ pGlobalDbLock = 0;
+++# endif
++ for(i=0; i<ArraySize(datai.aAuxDb); i++){
++ sqlite3_free(datai.aAuxDb[i].zFreeOnClose);
++ if( datai.aAuxDb[i].db ){
++ session_close_all(&datai, i);
++ close_db(datai.aAuxDb[i].db);
}
}
find_home_dir(1);