Index: third_party/sqlite/src/src/shell.c |
diff --git a/third_party/sqlite/src/src/shell.c b/third_party/sqlite/src/src/shell.c |
index b1d5087106813b137bd235e891afcc7226849784..33377da521d3ca130e2745ab33675ab479a9b90b 100644 |
--- a/third_party/sqlite/src/src/shell.c |
+++ b/third_party/sqlite/src/src/shell.c |
@@ -90,7 +90,7 @@ |
#else |
-# define shell_read_history(X) |
+# define shell_read_history(X) |
# define shell_write_history(X) |
# define shell_stifle_history(X) |
@@ -136,6 +136,16 @@ |
#define IsDigit(X) isdigit((unsigned char)X) |
#define ToLower(X) (char)tolower((unsigned char)X) |
+#if defined(_WIN32) || defined(WIN32) |
+#include <windows.h> |
+ |
+/* string conversion routines only needed on Win32 */ |
+extern char *sqlite3_win32_unicode_to_utf8(LPCWSTR); |
+extern char *sqlite3_win32_mbcs_to_utf8_v2(const char *, int); |
+extern char *sqlite3_win32_utf8_to_mbcs_v2(const char *, int); |
+extern LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText); |
+#endif |
+ |
/* On Windows, we normally run with output mode of TEXT so that \n characters |
** are automatically translated into \r\n. However, this behavior needs |
** to be disabled in some cases (ex: when generating CSV output and when |
@@ -143,17 +153,17 @@ |
** routines take care of that. |
*/ |
#if defined(_WIN32) || defined(WIN32) |
-static void setBinaryMode(FILE *out){ |
- fflush(out); |
- _setmode(_fileno(out), _O_BINARY); |
+static void setBinaryMode(FILE *file, int isOutput){ |
+ if( isOutput ) fflush(file); |
+ _setmode(_fileno(file), _O_BINARY); |
} |
-static void setTextMode(FILE *out){ |
- fflush(out); |
- _setmode(_fileno(out), _O_TEXT); |
+static void setTextMode(FILE *file, int isOutput){ |
+ if( isOutput ) fflush(file); |
+ _setmode(_fileno(file), _O_TEXT); |
} |
#else |
-# define setBinaryMode(X) |
-# define setTextMode(X) |
+# define setBinaryMode(X,Y) |
+# define setTextMode(X,Y) |
#endif |
@@ -204,7 +214,7 @@ static void beginTimer(void){ |
/* Return the difference of two time_structs in seconds */ |
static double timeDiff(struct timeval *pStart, struct timeval *pEnd){ |
- return (pEnd->tv_usec - pStart->tv_usec)*0.000001 + |
+ return (pEnd->tv_usec - pStart->tv_usec)*0.000001 + |
(double)(pEnd->tv_sec - pStart->tv_sec); |
} |
@@ -229,8 +239,6 @@ static void endTimer(void){ |
#elif (defined(_WIN32) || defined(WIN32)) |
-#include <windows.h> |
- |
/* Saved resource information for the beginning of an operation */ |
static HANDLE hProcess; |
static FILETIME ftKernelBegin; |
@@ -261,7 +269,7 @@ static int hasTimer(void){ |
if( NULL != getProcessTimesAddr ){ |
return 1; |
} |
- FreeLibrary(hinstLib); |
+ FreeLibrary(hinstLib); |
} |
} |
} |
@@ -307,7 +315,7 @@ static void endTimer(void){ |
#define HAS_TIMER hasTimer() |
#else |
-#define BEGIN_TIMER |
+#define BEGIN_TIMER |
#define END_TIMER |
#define HAS_TIMER 0 |
#endif |
@@ -362,6 +370,38 @@ static char mainPrompt[20]; /* First line prompt. default: "sqlite> "*/ |
static char continuePrompt[20]; /* Continuation prompt. default: " ...> " */ |
/* |
+** Render output like fprintf(). Except, if the output is going to the |
+** console and if this is running on a Windows machine, translate the |
+** output from UTF-8 into MBCS. |
+*/ |
+#if defined(_WIN32) || defined(WIN32) |
+void utf8_printf(FILE *out, const char *zFormat, ...){ |
+ va_list ap; |
+ va_start(ap, zFormat); |
+ if( stdout_is_console && (out==stdout || out==stderr) ){ |
+ char *z1 = sqlite3_vmprintf(zFormat, ap); |
+ char *z2 = sqlite3_win32_utf8_to_mbcs_v2(z1, 0); |
+ sqlite3_free(z1); |
+ fputs(z2, out); |
+ sqlite3_free(z2); |
+ }else{ |
+ vfprintf(out, zFormat, ap); |
+ } |
+ va_end(ap); |
+} |
+#elif !defined(utf8_printf) |
+# define utf8_printf fprintf |
+#endif |
+ |
+/* |
+** Render output like fprintf(). This should not be used on anything that |
+** includes string formatting (e.g. "%s"). |
+*/ |
+#if !defined(raw_printf) |
+# define raw_printf fprintf |
+#endif |
+ |
+/* |
** Write I/O traces to the following stream. |
*/ |
#ifdef SQLITE_ENABLE_IOTRACE |
@@ -382,7 +422,7 @@ static void SQLITE_CDECL iotracePrintf(const char *zFormat, ...){ |
va_start(ap, zFormat); |
z = sqlite3_vmprintf(zFormat, ap); |
va_end(ap); |
- fprintf(iotrace, "%s", z); |
+ utf8_printf(iotrace, "%s", z); |
sqlite3_free(z); |
} |
#endif |
@@ -416,8 +456,8 @@ static int isNumber(const char *z, int *realnum){ |
} |
/* |
-** A global char* and an SQL function to access its current value |
-** from within an SQL statement. This program used to use the |
+** A global char* and an SQL function to access its current value |
+** from within an SQL statement. This program used to use the |
** sqlite_exec_printf() API to substitue a string into an SQL statement. |
** The correct way to do this with sqlite3 is to use the bind API, but |
** since the shell is built around the callback paradigm it would be a lot |
@@ -483,11 +523,10 @@ static char *local_getline(char *zLine, FILE *in){ |
} |
} |
#if defined(_WIN32) || defined(WIN32) |
- /* For interactive input on Windows systems, translate the |
+ /* For interactive input on Windows systems, translate the |
** multi-byte characterset characters into UTF-8. */ |
- if( stdin_is_interactive ){ |
- extern char *sqlite3_win32_mbcs_to_utf8(const char*); |
- char *zTrans = sqlite3_win32_mbcs_to_utf8(zLine); |
+ if( stdin_is_interactive && in==stdin ){ |
+ char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0); |
if( zTrans ){ |
int nTrans = strlen30(zTrans)+1; |
if( nTrans>nLine ){ |
@@ -539,41 +578,21 @@ static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ |
return zResult; |
} |
+#if defined(SQLITE_ENABLE_SESSION) |
/* |
-** Render output like fprintf(). Except, if the output is going to the |
-** console and if this is running on a Windows machine, translate the |
-** output from UTF-8 into MBCS. |
+** State information for a single open session |
*/ |
-#if defined(_WIN32) || defined(WIN32) |
-void utf8_printf(FILE *out, const char *zFormat, ...){ |
- va_list ap; |
- va_start(ap, zFormat); |
- if( stdout_is_console && (out==stdout || out==stderr) ){ |
- extern char *sqlite3_win32_utf8_to_mbcs(const char*); |
- char *z1 = sqlite3_vmprintf(zFormat, ap); |
- char *z2 = sqlite3_win32_utf8_to_mbcs(z1); |
- sqlite3_free(z1); |
- fputs(z2, out); |
- sqlite3_free(z2); |
- }else{ |
- vfprintf(out, zFormat, ap); |
- } |
- va_end(ap); |
-} |
-#elif !defined(utf8_printf) |
-# define utf8_printf fprintf |
-#endif |
- |
-/* |
-** Render output like fprintf(). This should not be used on anything that |
-** includes string formatting (e.g. "%s"). |
-*/ |
-#if !defined(raw_printf) |
-# define raw_printf fprintf |
+typedef struct OpenSession OpenSession; |
+struct OpenSession { |
+ char *zName; /* Symbolic name for this session */ |
+ int nFilter; /* Number of xFilter rejection GLOB patterns */ |
+ char **azFilter; /* Array of xFilter rejection GLOB patterns */ |
+ sqlite3_session *p; /* The open session */ |
+}; |
#endif |
/* |
-** Shell output mode information from before ".explain on", |
+** Shell output mode information from before ".explain on", |
** saved so that it can be restored by ".explain off" |
*/ |
typedef struct SavedModeInfo SavedModeInfo; |
@@ -592,6 +611,7 @@ typedef struct ShellState ShellState; |
struct ShellState { |
sqlite3 *db; /* The database */ |
int echoOn; /* True to echo input commands */ |
+ int autoExplain; /* Automatically turn on .explain mode */ |
int autoEQP; /* Run EXPLAIN QUERY PLAN prior to seach SQL stmt */ |
int statsOn; /* True to display memory stats before each finalize */ |
int scanstatsOn; /* True to display scan stats before each finalize */ |
@@ -603,17 +623,20 @@ struct ShellState { |
FILE *traceOut; /* Output for sqlite3_trace() */ |
int nErr; /* Number of errors seen */ |
int mode; /* An output mode setting */ |
+ int cMode; /* temporary output mode for the current query */ |
+ int normalMode; /* Output mode before ".explain on" */ |
int writableSchema; /* True if PRAGMA writable_schema=ON */ |
int showHeader; /* True to show column names in List or Column mode */ |
+ int nCheck; /* Number of ".check" commands run */ |
unsigned shellFlgs; /* Various flags */ |
char *zDestTable; /* Name of destination table when MODE_Insert */ |
+ char zTestcase[30]; /* Name of current test case */ |
char colSeparator[20]; /* Column separator character for several modes */ |
char rowSeparator[20]; /* Row separator character for MODE_Ascii */ |
int colWidth[100]; /* Requested width of each column when in column mode*/ |
int actualWidth[100]; /* Actual width of each column */ |
char nullValue[20]; /* The text to print when a NULL comes back from |
** the database */ |
- SavedModeInfo normalMode;/* Holds the mode just before .explain ON */ |
char outfile[FILENAME_MAX]; /* Filename for *out */ |
const char *zDbFilename; /* name of the database file */ |
char *zFreeOnClose; /* Filename to free when closing */ |
@@ -623,6 +646,10 @@ struct ShellState { |
int *aiIndent; /* Array of indents used in MODE_Explain */ |
int nIndent; /* Size of array aiIndent[] */ |
int iIndent; /* Index of current op in aiIndent[] */ |
+#if defined(SQLITE_ENABLE_SESSION) |
+ int nSession; /* Number of active sessions */ |
+ OpenSession aSession[4]; /* Array of sessions. [0] is in focus. */ |
+#endif |
}; |
/* |
@@ -641,10 +668,12 @@ struct ShellState { |
#define MODE_Semi 3 /* Same as MODE_List but append ";" to each line */ |
#define MODE_Html 4 /* Generate an XHTML table */ |
#define MODE_Insert 5 /* Generate SQL "insert" statements */ |
-#define MODE_Tcl 6 /* Generate ANSI-C or TCL quoted elements */ |
-#define MODE_Csv 7 /* Quote strings, numbers are plain */ |
-#define MODE_Explain 8 /* Like MODE_Column, but do not truncate data */ |
-#define MODE_Ascii 9 /* Use ASCII unit and record separators (0x1F/0x1E) */ |
+#define MODE_Quote 6 /* Quote values as for SQL */ |
+#define MODE_Tcl 7 /* Generate ANSI-C or TCL quoted elements */ |
+#define MODE_Csv 8 /* Quote strings, numbers are plain */ |
+#define MODE_Explain 9 /* Like MODE_Column, but do not truncate data */ |
+#define MODE_Ascii 10 /* Use ASCII unit and record separators (0x1F/0x1E) */ |
+#define MODE_Pretty 11 /* Pretty-print schemas */ |
static const char *modeDescr[] = { |
"line", |
@@ -653,10 +682,12 @@ static const char *modeDescr[] = { |
"semi", |
"html", |
"insert", |
+ "quote", |
"tcl", |
"csv", |
"explain", |
"ascii", |
+ "prettyprint", |
}; |
/* |
@@ -704,7 +735,7 @@ static void output_hex_blob(FILE *out, const void *pBlob, int nBlob){ |
static void output_quoted_string(FILE *out, const char *z){ |
int i; |
int nSingle = 0; |
- setBinaryMode(out); |
+ setBinaryMode(out, 1); |
for(i=0; z[i]; i++){ |
if( z[i]=='\'' ) nSingle++; |
} |
@@ -727,7 +758,7 @@ static void output_quoted_string(FILE *out, const char *z){ |
} |
raw_printf(out,"'"); |
} |
- setTextMode(out); |
+ setTextMode(out, 1); |
} |
/* |
@@ -769,11 +800,11 @@ static void output_html_string(FILE *out, const char *z){ |
int i; |
if( z==0 ) z = ""; |
while( *z ){ |
- for(i=0; z[i] |
- && z[i]!='<' |
- && z[i]!='&' |
- && z[i]!='>' |
- && z[i]!='\"' |
+ for(i=0; z[i] |
+ && z[i]!='<' |
+ && z[i]!='&' |
+ && z[i]!='>' |
+ && z[i]!='\"' |
&& z[i]!='\''; |
i++){} |
if( i>0 ){ |
@@ -801,22 +832,22 @@ static void output_html_string(FILE *out, const char *z){ |
** array, then the string must be quoted for CSV. |
*/ |
static const char needCsvQuote[] = { |
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
- 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, |
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, |
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
+ 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, |
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, |
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
}; |
/* |
@@ -833,8 +864,8 @@ static void output_csv(ShellState *p, const char *z, int bSep){ |
int i; |
int nSep = strlen30(p->colSeparator); |
for(i=0; z[i]; i++){ |
- if( needCsvQuote[((unsigned char*)z)[i]] |
- || (z[i]==p->colSeparator[0] && |
+ if( needCsvQuote[((unsigned char*)z)[i]] |
+ || (z[i]==p->colSeparator[0] && |
(nSep==1 || memcmp(z, p->colSeparator, nSep)==0)) ){ |
i = 0; |
break; |
@@ -868,6 +899,73 @@ static void interrupt_handler(int NotUsed){ |
} |
#endif |
+#ifndef SQLITE_OMIT_AUTHORIZATION |
+/* |
+** When the ".auth ON" is set, the following authorizer callback is |
+** invoked. It always returns SQLITE_OK. |
+*/ |
+static int shellAuth( |
+ void *pClientData, |
+ int op, |
+ const char *zA1, |
+ const char *zA2, |
+ const char *zA3, |
+ const char *zA4 |
+){ |
+ ShellState *p = (ShellState*)pClientData; |
+ static const char *azAction[] = { 0, |
+ "CREATE_INDEX", "CREATE_TABLE", "CREATE_TEMP_INDEX", |
+ "CREATE_TEMP_TABLE", "CREATE_TEMP_TRIGGER", "CREATE_TEMP_VIEW", |
+ "CREATE_TRIGGER", "CREATE_VIEW", "DELETE", |
+ "DROP_INDEX", "DROP_TABLE", "DROP_TEMP_INDEX", |
+ "DROP_TEMP_TABLE", "DROP_TEMP_TRIGGER", "DROP_TEMP_VIEW", |
+ "DROP_TRIGGER", "DROP_VIEW", "INSERT", |
+ "PRAGMA", "READ", "SELECT", |
+ "TRANSACTION", "UPDATE", "ATTACH", |
+ "DETACH", "ALTER_TABLE", "REINDEX", |
+ "ANALYZE", "CREATE_VTABLE", "DROP_VTABLE", |
+ "FUNCTION", "SAVEPOINT", "RECURSIVE" |
+ }; |
+ int i; |
+ const char *az[4]; |
+ az[0] = zA1; |
+ az[1] = zA2; |
+ az[2] = zA3; |
+ az[3] = zA4; |
+ utf8_printf(p->out, "authorizer: %s", azAction[op]); |
+ for(i=0; i<4; i++){ |
+ raw_printf(p->out, " "); |
+ if( az[i] ){ |
+ output_c_string(p->out, az[i]); |
+ }else{ |
+ raw_printf(p->out, "NULL"); |
+ } |
+ } |
+ raw_printf(p->out, "\n"); |
+ return SQLITE_OK; |
+} |
+#endif |
+ |
+/* |
+** Print a schema statement. Part of MODE_Semi and MODE_Pretty output. |
+** |
+** This routine converts some CREATE TABLE statements for shadow tables |
+** in FTS3/4/5 into CREATE TABLE IF NOT EXISTS statements. |
+*/ |
+static void printSchemaLine(FILE *out, const char *z, const char *zTail){ |
+ if( sqlite3_strglob("CREATE TABLE ['\"]*", z)==0 ){ |
+ utf8_printf(out, "CREATE TABLE IF NOT EXISTS %s%s", z+13, zTail); |
+ }else{ |
+ utf8_printf(out, "%s%s", z, zTail); |
+ } |
+} |
+static void printSchemaLineN(FILE *out, char *z, int n, const char *zTail){ |
+ char c = z[n]; |
+ z[n] = 0; |
+ printSchemaLine(out, z, zTail); |
+ z[n] = c; |
+} |
+ |
/* |
** This is the callback routine that the shell |
** invokes for each row of a query result. |
@@ -882,7 +980,7 @@ static int shell_callback( |
int i; |
ShellState *p = (ShellState*)pArg; |
- switch( p->mode ){ |
+ switch( p->cMode ){ |
case MODE_Line: { |
int w = 5; |
if( azArg==0 ) break; |
@@ -899,11 +997,24 @@ static int shell_callback( |
} |
case MODE_Explain: |
case MODE_Column: { |
+ static const int aExplainWidths[] = {4, 13, 4, 4, 4, 13, 2, 13}; |
+ const int *colWidth; |
+ int showHdr; |
+ char *rowSep; |
+ if( p->cMode==MODE_Column ){ |
+ colWidth = p->colWidth; |
+ showHdr = p->showHeader; |
+ rowSep = p->rowSeparator; |
+ }else{ |
+ colWidth = aExplainWidths; |
+ showHdr = 1; |
+ rowSep = SEP_Row; |
+ } |
if( p->cnt++==0 ){ |
for(i=0; i<nArg; i++){ |
int w, n; |
if( i<ArraySize(p->colWidth) ){ |
- w = p->colWidth[i]; |
+ w = colWidth[i]; |
}else{ |
w = 0; |
} |
@@ -916,17 +1027,17 @@ static int shell_callback( |
if( i<ArraySize(p->actualWidth) ){ |
p->actualWidth[i] = w; |
} |
- if( p->showHeader ){ |
+ if( showHdr ){ |
if( w<0 ){ |
utf8_printf(p->out,"%*.*s%s",-w,-w,azCol[i], |
- i==nArg-1 ? p->rowSeparator : " "); |
+ i==nArg-1 ? rowSep : " "); |
}else{ |
utf8_printf(p->out,"%-*.*s%s",w,w,azCol[i], |
- i==nArg-1 ? p->rowSeparator : " "); |
+ i==nArg-1 ? rowSep : " "); |
} |
} |
} |
- if( p->showHeader ){ |
+ if( showHdr ){ |
for(i=0; i<nArg; i++){ |
int w; |
if( i<ArraySize(p->actualWidth) ){ |
@@ -938,7 +1049,7 @@ static int shell_callback( |
utf8_printf(p->out,"%-*.*s%s",w,w, |
"----------------------------------------------------------" |
"----------------------------------------------------------", |
- i==nArg-1 ? p->rowSeparator : " "); |
+ i==nArg-1 ? rowSep : " "); |
} |
} |
} |
@@ -950,7 +1061,7 @@ static int shell_callback( |
}else{ |
w = 10; |
} |
- if( p->mode==MODE_Explain && azArg[i] && strlen30(azArg[i])>w ){ |
+ if( p->cMode==MODE_Explain && azArg[i] && strlen30(azArg[i])>w ){ |
w = strlen30(azArg[i]); |
} |
if( i==1 && p->aiIndent && p->pStmt ){ |
@@ -962,16 +1073,79 @@ static int shell_callback( |
if( w<0 ){ |
utf8_printf(p->out,"%*.*s%s",-w,-w, |
azArg[i] ? azArg[i] : p->nullValue, |
- i==nArg-1 ? p->rowSeparator : " "); |
+ i==nArg-1 ? rowSep : " "); |
}else{ |
utf8_printf(p->out,"%-*.*s%s",w,w, |
azArg[i] ? azArg[i] : p->nullValue, |
- i==nArg-1 ? p->rowSeparator : " "); |
+ i==nArg-1 ? rowSep : " "); |
} |
} |
break; |
} |
- case MODE_Semi: |
+ case MODE_Semi: { /* .schema and .fullschema output */ |
+ printSchemaLine(p->out, azArg[0], ";\n"); |
+ break; |
+ } |
+ case MODE_Pretty: { /* .schema and .fullschema with --indent */ |
+ char *z; |
+ int j; |
+ int nParen = 0; |
+ char cEnd = 0; |
+ char c; |
+ int nLine = 0; |
+ assert( nArg==1 ); |
+ if( azArg[0]==0 ) break; |
+ if( sqlite3_strlike("CREATE VIEW%", azArg[0], 0)==0 |
+ || sqlite3_strlike("CREATE TRIG%", azArg[0], 0)==0 |
+ ){ |
+ utf8_printf(p->out, "%s;\n", azArg[0]); |
+ break; |
+ } |
+ z = sqlite3_mprintf("%s", azArg[0]); |
+ j = 0; |
+ for(i=0; IsSpace(z[i]); i++){} |
+ for(; (c = z[i])!=0; i++){ |
+ if( IsSpace(c) ){ |
+ if( IsSpace(z[j-1]) || z[j-1]=='(' ) continue; |
+ }else if( (c=='(' || c==')') && j>0 && IsSpace(z[j-1]) ){ |
+ j--; |
+ } |
+ z[j++] = c; |
+ } |
+ while( j>0 && IsSpace(z[j-1]) ){ j--; } |
+ z[j] = 0; |
+ if( strlen30(z)>=79 ){ |
+ for(i=j=0; (c = z[i])!=0; i++){ |
+ if( c==cEnd ){ |
+ cEnd = 0; |
+ }else if( c=='"' || c=='\'' || c=='`' ){ |
+ cEnd = c; |
+ }else if( c=='[' ){ |
+ cEnd = ']'; |
+ }else if( c=='(' ){ |
+ nParen++; |
+ }else if( c==')' ){ |
+ nParen--; |
+ if( nLine>0 && nParen==0 && j>0 ){ |
+ printSchemaLineN(p->out, z, j, "\n"); |
+ j = 0; |
+ } |
+ } |
+ z[j++] = c; |
+ if( nParen==1 && (c=='(' || c==',' || c=='\n') ){ |
+ if( c=='\n' ) j--; |
+ printSchemaLineN(p->out, z, j, "\n "); |
+ j = 0; |
+ nLine++; |
+ while( IsSpace(z[i+1]) ){ i++; } |
+ } |
+ } |
+ z[j] = 0; |
+ } |
+ printSchemaLine(p->out, z, ";\n"); |
+ sqlite3_free(z); |
+ break; |
+ } |
case MODE_List: { |
if( p->cnt++==0 && p->showHeader ){ |
for(i=0; i<nArg; i++){ |
@@ -986,8 +1160,6 @@ static int shell_callback( |
utf8_printf(p->out, "%s", z); |
if( i<nArg-1 ){ |
utf8_printf(p->out, "%s", p->colSeparator); |
- }else if( p->mode==MODE_Semi ){ |
- utf8_printf(p->out, ";%s", p->rowSeparator); |
}else{ |
utf8_printf(p->out, "%s", p->rowSeparator); |
} |
@@ -1031,7 +1203,7 @@ static int shell_callback( |
break; |
} |
case MODE_Csv: { |
- setBinaryMode(p->out); |
+ setBinaryMode(p->out, 1); |
if( p->cnt++==0 && p->showHeader ){ |
for(i=0; i<nArg; i++){ |
output_csv(p, azCol[i] ? azCol[i] : "", i<nArg-1); |
@@ -1044,22 +1216,31 @@ static int shell_callback( |
} |
utf8_printf(p->out, "%s", p->rowSeparator); |
} |
- setTextMode(p->out); |
+ setTextMode(p->out, 1); |
break; |
} |
+ case MODE_Quote: |
case MODE_Insert: { |
- p->cnt++; |
if( azArg==0 ) break; |
- utf8_printf(p->out,"INSERT INTO %s",p->zDestTable); |
- if( p->showHeader ){ |
- raw_printf(p->out,"("); |
+ if( p->cMode==MODE_Insert ){ |
+ utf8_printf(p->out,"INSERT INTO %s",p->zDestTable); |
+ if( p->showHeader ){ |
+ raw_printf(p->out,"("); |
+ for(i=0; i<nArg; i++){ |
+ char *zSep = i>0 ? ",": ""; |
+ utf8_printf(p->out, "%s%s", zSep, azCol[i]); |
+ } |
+ raw_printf(p->out,")"); |
+ } |
+ raw_printf(p->out," VALUES("); |
+ }else if( p->cnt==0 && p->showHeader ){ |
for(i=0; i<nArg; i++){ |
- char *zSep = i>0 ? ",": ""; |
- utf8_printf(p->out, "%s%s", zSep, azCol[i]); |
+ if( i>0 ) raw_printf(p->out, ","); |
+ output_quoted_string(p->out, azCol[i]); |
} |
- raw_printf(p->out,")"); |
+ raw_printf(p->out,"\n"); |
} |
- raw_printf(p->out," VALUES("); |
+ p->cnt++; |
for(i=0; i<nArg; i++){ |
char *zSep = i>0 ? ",": ""; |
if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ |
@@ -1082,7 +1263,7 @@ static int shell_callback( |
output_quoted_string(p->out, azArg[i]); |
} |
} |
- raw_printf(p->out,");\n"); |
+ raw_printf(p->out,p->cMode==MODE_Quote?"\n":");\n"); |
break; |
} |
case MODE_Ascii: { |
@@ -1157,7 +1338,7 @@ static void set_table_name(ShellState *p, const char *zName){ |
** added to zIn, and the result returned in memory obtained from malloc(). |
** zIn, if it was not NULL, is freed. |
** |
-** If the third argument, quote, is not '\0', then it is used as a |
+** If the third argument, quote, is not '\0', then it is used as a |
** quote character for zAppend. |
*/ |
static char *appendText(char *zIn, char const *zAppend, char quote){ |
@@ -1204,7 +1385,7 @@ static char *appendText(char *zIn, char const *zAppend, char quote){ |
** semicolon terminator to the end of that line. |
** |
** If the number of columns is 1 and that column contains text "--" |
-** then write the semicolon on a separate line. That way, if a |
+** then write the semicolon on a separate line. That way, if a |
** "--" comment occurs at the end of the statement, the comment |
** won't consume the semicolon terminator. |
*/ |
@@ -1234,7 +1415,7 @@ static int run_table_dump_query( |
} |
z = (const char*)sqlite3_column_text(pSelect, 0); |
utf8_printf(p->out, "%s", z); |
- for(i=1; i<nResult; i++){ |
+ for(i=1; i<nResult; i++){ |
utf8_printf(p->out, ",%s", sqlite3_column_text(pSelect, i)); |
} |
if( z==0 ) z = ""; |
@@ -1243,7 +1424,7 @@ static int run_table_dump_query( |
raw_printf(p->out, "\n;\n"); |
}else{ |
raw_printf(p->out, ";\n"); |
- } |
+ } |
rc = sqlite3_step(pSelect); |
} |
rc = sqlite3_finalize(pSelect); |
@@ -1269,6 +1450,43 @@ static char *save_err_msg( |
return zErrMsg; |
} |
+#ifdef __linux__ |
+/* |
+** Attempt to display I/O stats on Linux using /proc/PID/io |
+*/ |
+static void displayLinuxIoStats(FILE *out){ |
+ FILE *in; |
+ char z[200]; |
+ sqlite3_snprintf(sizeof(z), z, "/proc/%d/io", getpid()); |
+ in = fopen(z, "rb"); |
+ if( in==0 ) return; |
+ while( fgets(z, sizeof(z), in)!=0 ){ |
+ static const struct { |
+ const char *zPattern; |
+ const char *zDesc; |
+ } aTrans[] = { |
+ { "rchar: ", "Bytes received by read():" }, |
+ { "wchar: ", "Bytes sent to write():" }, |
+ { "syscr: ", "Read() system calls:" }, |
+ { "syscw: ", "Write() system calls:" }, |
+ { "read_bytes: ", "Bytes read from storage:" }, |
+ { "write_bytes: ", "Bytes written to storage:" }, |
+ { "cancelled_write_bytes: ", "Cancelled write bytes:" }, |
+ }; |
+ int i; |
+ for(i=0; i<ArraySize(aTrans); i++){ |
+ int n = (int)strlen(aTrans[i].zPattern); |
+ if( strncmp(aTrans[i].zPattern, z, n)==0 ){ |
+ utf8_printf(out, "%-36s %s", aTrans[i].zDesc, &z[n]); |
+ break; |
+ } |
+ } |
+ } |
+ fclose(in); |
+} |
+#endif |
+ |
+ |
/* |
** Display memory stats. |
*/ |
@@ -1281,7 +1499,7 @@ static int display_stats( |
int iHiwtr; |
if( pArg && pArg->out ){ |
- |
+ |
iHiwtr = iCur = -1; |
sqlite3_status(SQLITE_STATUS_MEMORY_USED, &iCur, &iHiwtr, bReset); |
raw_printf(pArg->out, |
@@ -1365,18 +1583,18 @@ static int display_stats( |
raw_printf(pArg->out, "Page cache hits: %d\n", iCur); |
iHiwtr = iCur = -1; |
sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1); |
- raw_printf(pArg->out, "Page cache misses: %d\n", iCur); |
+ raw_printf(pArg->out, "Page cache misses: %d\n", iCur); |
iHiwtr = iCur = -1; |
sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1); |
- raw_printf(pArg->out, "Page cache writes: %d\n", iCur); |
+ raw_printf(pArg->out, "Page cache writes: %d\n", iCur); |
iHiwtr = iCur = -1; |
sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset); |
raw_printf(pArg->out, "Schema Heap Usage: %d bytes\n", |
- iCur); |
+ iCur); |
iHiwtr = iCur = -1; |
sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHiwtr, bReset); |
raw_printf(pArg->out, "Statement Heap/Lookaside Usage: %d bytes\n", |
- iCur); |
+ iCur); |
} |
if( pArg && pArg->out && db && pArg->pStmt ){ |
@@ -1391,6 +1609,10 @@ static int display_stats( |
raw_printf(pArg->out, "Virtual Machine Steps: %d\n", iCur); |
} |
+#ifdef __linux__ |
+ displayLinuxIoStats(pArg->out); |
+#endif |
+ |
/* Do not remove this machine readable comment: extra-stats-output-here */ |
return 0; |
@@ -1434,7 +1656,7 @@ static void display_scanstats( |
sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_EXPLAIN, (void*)&zExplain); |
utf8_printf(pArg->out, "Loop %2d: %s\n", n, zExplain); |
rEstLoop *= rEst; |
- raw_printf(pArg->out, |
+ raw_printf(pArg->out, |
" nLoop=%-8lld nRow=%-8lld estRow=%-8lld estRow/Loop=%-8g\n", |
nLoop, nVisit, (sqlite3_int64)(rEstLoop+0.5), rEst |
); |
@@ -1461,7 +1683,7 @@ static int str_in_array(const char *zStr, const char **azArray){ |
/* |
** If compiled statement pSql appears to be an EXPLAIN statement, allocate |
** and populate the ShellState.aiIndent[] array with the number of |
-** spaces each opcode should be indented before it is output. |
+** spaces each opcode should be indented before it is output. |
** |
** The indenting rules are: |
** |
@@ -1491,10 +1713,17 @@ static void explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){ |
/* Try to figure out if this is really an EXPLAIN statement. If this |
** cannot be verified, return early. */ |
+ if( sqlite3_column_count(pSql)!=8 ){ |
+ p->cMode = p->mode; |
+ return; |
+ } |
zSql = sqlite3_sql(pSql); |
if( zSql==0 ) return; |
for(z=zSql; *z==' ' || *z=='\t' || *z=='\n' || *z=='\f' || *z=='\r'; z++); |
- if( sqlite3_strnicmp(z, "explain", 7) ) return; |
+ if( sqlite3_strnicmp(z, "explain", 7) ){ |
+ p->cMode = p->mode; |
+ return; |
+ } |
for(iOp=0; SQLITE_ROW==sqlite3_step(pSql); iOp++){ |
int i; |
@@ -1511,6 +1740,20 @@ static void explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){ |
/* Grow the p->aiIndent array as required */ |
if( iOp>=nAlloc ){ |
+ if( iOp==0 ){ |
+ /* Do further verfication that this is explain output. Abort if |
+ ** it is not */ |
+ static const char *explainCols[] = { |
+ "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment" }; |
+ int jj; |
+ for(jj=0; jj<ArraySize(explainCols); jj++){ |
+ if( strcmp(sqlite3_column_name(pSql,jj),explainCols[jj])!=0 ){ |
+ p->cMode = p->mode; |
+ sqlite3_reset(pSql); |
+ return; |
+ } |
+ } |
+ } |
nAlloc += 100; |
p->aiIndent = (int*)sqlite3_realloc64(p->aiIndent, nAlloc*sizeof(int)); |
abYield = (int*)sqlite3_realloc64(abYield, nAlloc*sizeof(int)); |
@@ -1525,7 +1768,7 @@ static void explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){ |
if( str_in_array(zOp, azGoto) && p2op<p->nIndent |
&& (abYield[p2op] || sqlite3_column_int(pSql, 2)) |
){ |
- for(i=p2op+1; i<iOp; i++) p->aiIndent[i] += 2; |
+ for(i=p2op; i<iOp; i++) p->aiIndent[i] += 2; |
} |
} |
@@ -1545,12 +1788,110 @@ static void explain_data_delete(ShellState *p){ |
} |
/* |
-** Execute a statement or set of statements. Print |
-** any result rows/columns depending on the current mode |
+** Disable and restore .wheretrace and .selecttrace settings. |
+*/ |
+#if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_SELECTTRACE) |
+extern int sqlite3SelectTrace; |
+static int savedSelectTrace; |
+#endif |
+#if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_WHERETRACE) |
+extern int sqlite3WhereTrace; |
+static int savedWhereTrace; |
+#endif |
+static void disable_debug_trace_modes(void){ |
+#if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_SELECTTRACE) |
+ savedSelectTrace = sqlite3SelectTrace; |
+ sqlite3SelectTrace = 0; |
+#endif |
+#if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_WHERETRACE) |
+ savedWhereTrace = sqlite3WhereTrace; |
+ sqlite3WhereTrace = 0; |
+#endif |
+} |
+static void restore_debug_trace_modes(void){ |
+#if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_SELECTTRACE) |
+ sqlite3SelectTrace = savedSelectTrace; |
+#endif |
+#if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_WHERETRACE) |
+ sqlite3WhereTrace = savedWhereTrace; |
+#endif |
+} |
+ |
+/* |
+** Run a prepared statement |
+*/ |
+static void exec_prepared_stmt( |
+ ShellState *pArg, /* Pointer to ShellState */ |
+ sqlite3_stmt *pStmt, /* Statment to run */ |
+ int (*xCallback)(void*,int,char**,char**,int*) /* Callback function */ |
+){ |
+ int rc; |
+ |
+ /* perform the first step. this will tell us if we |
+ ** have a result set or not and how wide it is. |
+ */ |
+ rc = sqlite3_step(pStmt); |
+ /* if we have a result set... */ |
+ if( SQLITE_ROW == rc ){ |
+ /* if we have a callback... */ |
+ if( xCallback ){ |
+ /* allocate space for col name ptr, value ptr, and type */ |
+ int nCol = sqlite3_column_count(pStmt); |
+ void *pData = sqlite3_malloc64(3*nCol*sizeof(const char*) + 1); |
+ if( !pData ){ |
+ rc = SQLITE_NOMEM; |
+ }else{ |
+ char **azCols = (char **)pData; /* Names of result columns */ |
+ char **azVals = &azCols[nCol]; /* Results */ |
+ int *aiTypes = (int *)&azVals[nCol]; /* Result types */ |
+ int i, x; |
+ assert(sizeof(int) <= sizeof(char *)); |
+ /* save off ptrs to column names */ |
+ for(i=0; i<nCol; i++){ |
+ azCols[i] = (char *)sqlite3_column_name(pStmt, i); |
+ } |
+ do{ |
+ /* extract the data and data types */ |
+ for(i=0; i<nCol; i++){ |
+ aiTypes[i] = x = sqlite3_column_type(pStmt, i); |
+ if( x==SQLITE_BLOB && pArg && pArg->cMode==MODE_Insert ){ |
+ azVals[i] = ""; |
+ }else{ |
+ azVals[i] = (char*)sqlite3_column_text(pStmt, i); |
+ } |
+ if( !azVals[i] && (aiTypes[i]!=SQLITE_NULL) ){ |
+ rc = SQLITE_NOMEM; |
+ break; /* from for */ |
+ } |
+ } /* end for */ |
+ |
+ /* if data and types extracted successfully... */ |
+ if( SQLITE_ROW == rc ){ |
+ /* call the supplied callback with the result row data */ |
+ if( xCallback(pArg, nCol, azVals, azCols, aiTypes) ){ |
+ rc = SQLITE_ABORT; |
+ }else{ |
+ rc = sqlite3_step(pStmt); |
+ } |
+ } |
+ } while( SQLITE_ROW == rc ); |
+ sqlite3_free(pData); |
+ } |
+ }else{ |
+ do{ |
+ rc = sqlite3_step(pStmt); |
+ } while( rc == SQLITE_ROW ); |
+ } |
+ } |
+} |
+ |
+/* |
+** Execute a statement or set of statements. Print |
+** any result rows/columns depending on the current mode |
** set via the supplied callback. |
** |
-** This is very similar to SQLite's built-in sqlite3_exec() |
-** function except it takes a slightly different callback |
+** This is very similar to SQLite's built-in sqlite3_exec() |
+** function except it takes a slightly different callback |
** and callback data argument. |
*/ |
static int shell_exec( |
@@ -1571,6 +1912,7 @@ static int shell_exec( |
} |
while( zSql[0] && (SQLITE_OK == rc) ){ |
+ static const char *zStmtSql; |
rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover); |
if( SQLITE_OK != rc ){ |
if( pzErrMsg ){ |
@@ -1583,6 +1925,9 @@ static int shell_exec( |
while( IsSpace(zSql[0]) ) zSql++; |
continue; |
} |
+ zStmtSql = sqlite3_sql(pStmt); |
+ if( zStmtSql==0 ) zStmtSql = ""; |
+ while( IsSpace(zStmtSql[0]) ) zStmtSql++; |
/* save off the prepared statment handle and reset row count */ |
if( pArg ){ |
@@ -1592,15 +1937,15 @@ static int shell_exec( |
/* echo the sql statement if echo on */ |
if( pArg && pArg->echoOn ){ |
- const char *zStmtSql = sqlite3_sql(pStmt); |
utf8_printf(pArg->out, "%s\n", zStmtSql ? zStmtSql : zSql); |
} |
/* Show the EXPLAIN QUERY PLAN if .eqp is on */ |
- if( pArg && pArg->autoEQP ){ |
+ if( pArg && pArg->autoEQP && sqlite3_strlike("EXPLAIN%",zStmtSql,0)!=0 ){ |
sqlite3_stmt *pExplain; |
- char *zEQP = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", |
- sqlite3_sql(pStmt)); |
+ char *zEQP; |
+ disable_debug_trace_modes(); |
+ zEQP = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", zStmtSql); |
rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0); |
if( rc==SQLITE_OK ){ |
while( sqlite3_step(pExplain)==SQLITE_ROW ){ |
@@ -1612,71 +1957,39 @@ static int shell_exec( |
} |
sqlite3_finalize(pExplain); |
sqlite3_free(zEQP); |
+ if( pArg->autoEQP>=2 ){ |
+ /* Also do an EXPLAIN for ".eqp full" mode */ |
+ zEQP = sqlite3_mprintf("EXPLAIN %s", zStmtSql); |
+ 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, xCallback); |
+ explain_data_delete(pArg); |
+ } |
+ sqlite3_finalize(pExplain); |
+ sqlite3_free(zEQP); |
+ } |
+ restore_debug_trace_modes(); |
} |
- /* If the shell is currently in ".explain" mode, gather the extra |
- ** data required to add indents to the output.*/ |
- if( pArg && pArg->mode==MODE_Explain ){ |
- explain_data_prepare(pArg, pStmt); |
- } |
+ if( pArg ){ |
+ pArg->cMode = pArg->mode; |
+ if( pArg->autoExplain |
+ && sqlite3_column_count(pStmt)==8 |
+ && sqlite3_strlike("EXPLAIN%", zStmtSql,0)==0 |
+ ){ |
+ pArg->cMode = MODE_Explain; |
+ } |
- /* perform the first step. this will tell us if we |
- ** have a result set or not and how wide it is. |
- */ |
- rc = sqlite3_step(pStmt); |
- /* if we have a result set... */ |
- if( SQLITE_ROW == rc ){ |
- /* if we have a callback... */ |
- if( xCallback ){ |
- /* allocate space for col name ptr, value ptr, and type */ |
- int nCol = sqlite3_column_count(pStmt); |
- void *pData = sqlite3_malloc64(3*nCol*sizeof(const char*) + 1); |
- if( !pData ){ |
- rc = SQLITE_NOMEM; |
- }else{ |
- char **azCols = (char **)pData; /* Names of result columns */ |
- char **azVals = &azCols[nCol]; /* Results */ |
- int *aiTypes = (int *)&azVals[nCol]; /* Result types */ |
- int i, x; |
- assert(sizeof(int) <= sizeof(char *)); |
- /* save off ptrs to column names */ |
- for(i=0; i<nCol; i++){ |
- azCols[i] = (char *)sqlite3_column_name(pStmt, i); |
- } |
- do{ |
- /* extract the data and data types */ |
- for(i=0; i<nCol; i++){ |
- aiTypes[i] = x = sqlite3_column_type(pStmt, i); |
- if( x==SQLITE_BLOB && pArg && pArg->mode==MODE_Insert ){ |
- azVals[i] = ""; |
- }else{ |
- azVals[i] = (char*)sqlite3_column_text(pStmt, i); |
- } |
- if( !azVals[i] && (aiTypes[i]!=SQLITE_NULL) ){ |
- rc = SQLITE_NOMEM; |
- break; /* from for */ |
- } |
- } /* end for */ |
- |
- /* if data and types extracted successfully... */ |
- if( SQLITE_ROW == rc ){ |
- /* call the supplied callback with the result row data */ |
- if( xCallback(pArg, nCol, azVals, azCols, aiTypes) ){ |
- rc = SQLITE_ABORT; |
- }else{ |
- rc = sqlite3_step(pStmt); |
- } |
- } |
- } while( SQLITE_ROW == rc ); |
- sqlite3_free(pData); |
- } |
- }else{ |
- do{ |
- rc = sqlite3_step(pStmt); |
- } while( rc == SQLITE_ROW ); |
+ /* If the shell is currently in ".explain" mode, gather the extra |
+ ** data required to add indents to the output.*/ |
+ if( pArg->cMode==MODE_Explain ){ |
+ explain_data_prepare(pArg, pStmt); |
} |
} |
+ exec_prepared_stmt(pArg, pStmt, xCallback); |
explain_data_delete(pArg); |
/* print usage stats if stats on */ |
@@ -1689,7 +2002,7 @@ static int shell_exec( |
display_scanstats(db, pArg); |
} |
- /* Finalize the statement just executed. If this fails, save a |
+ /* Finalize the statement just executed. If this fails, save a |
** copy of the error message. Otherwise, set zSql to point to the |
** next statement to execute. */ |
rc2 = sqlite3_finalize(pStmt); |
@@ -1731,7 +2044,7 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){ |
zTable = azArg[0]; |
zType = azArg[1]; |
zSql = azArg[2]; |
- |
+ |
if( strcmp(zTable, "sqlite_sequence")==0 ){ |
zPrepStmt = "DELETE FROM sqlite_sequence;\n"; |
}else if( sqlite3_strglob("sqlite_stat?", zTable)==0 ){ |
@@ -1752,7 +2065,7 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){ |
sqlite3_free(zIns); |
return 0; |
}else{ |
- utf8_printf(p->out, "%s;\n", zSql); |
+ printSchemaLine(p->out, zSql, ";\n"); |
} |
if( strcmp(zType, "table")==0 ){ |
@@ -1761,7 +2074,7 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){ |
char *zTableInfo = 0; |
char *zTmp = 0; |
int nRow = 0; |
- |
+ |
zTableInfo = appendText(zTableInfo, "PRAGMA table_info(", 0); |
zTableInfo = appendText(zTableInfo, zTable, '"'); |
zTableInfo = appendText(zTableInfo, ");", 0); |
@@ -1820,7 +2133,7 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){ |
** "ORDER BY rowid DESC" to the end. |
*/ |
static int run_schema_dump_query( |
- ShellState *p, |
+ ShellState *p, |
const char *zQuery |
){ |
int rc; |
@@ -1854,10 +2167,14 @@ static int run_schema_dump_query( |
** Text of a help message |
*/ |
static char zHelp[] = |
+#ifndef SQLITE_OMIT_AUTHORIZATION |
+ ".auth ON|OFF Show authorizer callbacks\n" |
+#endif |
".backup ?DB? FILE Backup DB (default \"main\") to FILE\n" |
".bail on|off Stop after hitting an error. Default OFF\n" |
".binary on|off Turn binary output on or off. Default OFF\n" |
".changes on|off Show number of rows changed by SQL\n" |
+ ".check GLOB Fail if output since .testcase does not match\n" |
".clone NEWDB Clone data into NEWDB from the existing database\n" |
".databases List names and files of attached databases\n" |
".dbinfo ?DB? Show status information about the database\n" |
@@ -1865,14 +2182,16 @@ static char zHelp[] = |
" If TABLE specified, only dump tables matching\n" |
" LIKE pattern TABLE.\n" |
".echo on|off Turn command echo on or off\n" |
- ".eqp on|off Enable or disable automatic EXPLAIN QUERY PLAN\n" |
+ ".eqp on|off|full Enable or disable automatic EXPLAIN QUERY PLAN\n" |
".exit Exit this program\n" |
- ".explain ?on|off? Turn output mode suitable for EXPLAIN on or off.\n" |
- " With no args, it turns EXPLAIN on.\n" |
- ".fullschema Show schema and the content of sqlite_stat tables\n" |
+ ".explain ?on|off|auto? Turn EXPLAIN output mode on or off or to automatic\n" |
+ ".fullschema ?--indent? Show schema and the content of sqlite_stat tables\n" |
".headers on|off Turn display of headers on or off\n" |
".help Show this message\n" |
".import FILE TABLE Import data from FILE into TABLE\n" |
+#ifndef SQLITE_OMIT_TEST_CONTROL |
+ ".imposter INDEX TABLE Create imposter table TABLE on index INDEX\n" |
+#endif |
".indexes ?TABLE? Show names of all indexes\n" |
" If TABLE specified, only show indexes for tables\n" |
" matching LIKE pattern TABLE.\n" |
@@ -1880,6 +2199,8 @@ static char zHelp[] = |
".iotrace FILE Enable I/O diagnostic logging to FILE\n" |
#endif |
".limit ?LIMIT? ?VAL? Display or change the value of an SQLITE_LIMIT\n" |
+ ".lint OPTIONS Report potential schema issues. Options:\n" |
+ " fkey-indexes Find missing foreign key indexes\n" |
#ifndef SQLITE_OMIT_LOAD_EXTENSION |
".load FILE ?ENTRY? Load an extension library\n" |
#endif |
@@ -1892,11 +2213,13 @@ static char zHelp[] = |
" insert SQL insert statements for TABLE\n" |
" line One value per line\n" |
" list Values delimited by .separator strings\n" |
+ " quote Escape answers as for SQL\n" |
" tabs Tab-separated values\n" |
" tcl TCL list elements\n" |
".nullvalue STRING Use STRING in place of NULL values\n" |
".once FILENAME Output for the next SQL command only to FILENAME\n" |
- ".open ?FILENAME? Close existing database and reopen FILENAME\n" |
+ ".open ?--new? ?FILE? Close existing database and reopen FILE\n" |
+ " The --new starts with an empty file\n" |
".output ?FILENAME? Send output to FILENAME or stdout\n" |
".print STRING... Print literal STRING\n" |
".prompt MAIN CONTINUE Replace the standard prompts\n" |
@@ -1905,29 +2228,95 @@ static char zHelp[] = |
".restore ?DB? FILE Restore content of DB (default \"main\") from FILE\n" |
".save FILE Write in-memory database into FILE\n" |
".scanstats on|off Turn sqlite3_stmt_scanstatus() metrics on or off\n" |
- ".schema ?TABLE? Show the CREATE statements\n" |
- " If TABLE specified, only show tables matching\n" |
- " LIKE pattern TABLE.\n" |
+ ".schema ?PATTERN? Show the CREATE statements matching PATTERN\n" |
+ " Add --indent for pretty-printing\n" |
".separator COL ?ROW? Change the column separator and optionally the row\n" |
" separator for both the output mode and .import\n" |
+#if defined(SQLITE_ENABLE_SESSION) |
+ ".session CMD ... Create or control sessions\n" |
+#endif |
".shell CMD ARGS... Run CMD ARGS... in a system shell\n" |
".show Show the current values for various settings\n" |
- ".stats on|off Turn stats on or off\n" |
+ ".stats ?on|off? Show stats or turn stats on or off\n" |
".system CMD ARGS... Run CMD ARGS... in a system shell\n" |
".tables ?TABLE? List names of tables\n" |
" If TABLE specified, only list tables matching\n" |
" LIKE pattern TABLE.\n" |
+ ".testcase NAME Begin redirecting output to 'testcase-out.txt'\n" |
".timeout MS Try opening locked tables for MS milliseconds\n" |
".timer on|off Turn SQL timer on or off\n" |
".trace FILE|off Output each SQL statement as it is run\n" |
".vfsinfo ?AUX? Information about the top-level VFS\n" |
+ ".vfslist List all available VFSes\n" |
".vfsname ?AUX? Print the name of the VFS stack\n" |
".width NUM1 NUM2 ... Set column widths for \"column\" mode\n" |
" Negative values right-justify\n" |
; |
+#if defined(SQLITE_ENABLE_SESSION) |
+/* |
+** Print help information for the ".sessions" command |
+*/ |
+void session_help(ShellState *p){ |
+ raw_printf(p->out, |
+ ".session ?NAME? SUBCOMMAND ?ARGS...?\n" |
+ "If ?NAME? is omitted, the first defined session is used.\n" |
+ "Subcommands:\n" |
+ " attach TABLE Attach TABLE\n" |
+ " changeset FILE Write a changeset into FILE\n" |
+ " close Close one session\n" |
+ " enable ?BOOLEAN? Set or query the enable bit\n" |
+ " filter GLOB... Reject tables matching GLOBs\n" |
+ " indirect ?BOOLEAN? Mark or query the indirect status\n" |
+ " isempty Query whether the session is empty\n" |
+ " list List currently open session names\n" |
+ " open DB NAME Open a new session on DB\n" |
+ " patchset FILE Write a patchset into FILE\n" |
+ ); |
+} |
+#endif |
+ |
+ |
/* Forward reference */ |
static int process_input(ShellState *p, FILE *in); |
+ |
+/* |
+** 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. |
+*/ |
+static char *readFile(const char *zName, int *pnByte){ |
+ FILE *in = fopen(zName, "rb"); |
+ long nIn; |
+ size_t nRead; |
+ char *pBuf; |
+ if( in==0 ) return 0; |
+ fseek(in, 0, SEEK_END); |
+ nIn = ftell(in); |
+ rewind(in); |
+ pBuf = sqlite3_malloc64( nIn+1 ); |
+ if( pBuf==0 ) return 0; |
+ nRead = fread(pBuf, nIn, 1, in); |
+ fclose(in); |
+ if( nRead!=1 ){ |
+ sqlite3_free(pBuf); |
+ return 0; |
+ } |
+ pBuf[nIn] = 0; |
+ if( pnByte ) *pnByte = nIn; |
+ return pBuf; |
+} |
+ |
/* |
** Implementation of the "readfile(X)" SQL function. The entire content |
** of the file named X is read and returned as a BLOB. NULL is returned |
@@ -1939,25 +2328,14 @@ static void readfileFunc( |
sqlite3_value **argv |
){ |
const char *zName; |
- FILE *in; |
- long nIn; |
void *pBuf; |
+ int nBuf; |
UNUSED_PARAMETER(argc); |
zName = (const char*)sqlite3_value_text(argv[0]); |
if( zName==0 ) return; |
- in = fopen(zName, "rb"); |
- if( in==0 ) return; |
- fseek(in, 0, SEEK_END); |
- nIn = ftell(in); |
- rewind(in); |
- pBuf = sqlite3_malloc64( nIn ); |
- if( pBuf && 1==fread(pBuf, nIn, 1, in) ){ |
- sqlite3_result_blob(context, pBuf, nIn, sqlite3_free); |
- }else{ |
- sqlite3_free(pBuf); |
- } |
- fclose(in); |
+ pBuf = readFile(zName, &nBuf); |
+ if( pBuf ) sqlite3_result_blob(context, pBuf, nBuf, sqlite3_free); |
} |
/* |
@@ -1991,6 +2369,53 @@ static void writefileFunc( |
sqlite3_result_int64(context, rc); |
} |
+#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 |
+ |
+/* |
+** Close all OpenSession objects and release all associated resources. |
+*/ |
+#if defined(SQLITE_ENABLE_SESSION) |
+static void session_close_all(ShellState *p){ |
+ int i; |
+ for(i=0; i<p->nSession; i++){ |
+ session_close(&p->aSession[i]); |
+ } |
+ p->nSession = 0; |
+} |
+#else |
+# define session_close_all(X) |
+#endif |
+ |
+/* |
+** 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 |
+ |
/* |
** 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. |
@@ -2005,7 +2430,7 @@ static void open_db(ShellState *p, int keepAlive){ |
shellstaticFunc, 0, 0); |
} |
if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ |
- utf8_printf(stderr,"Error: unable to open database \"%s\": %s\n", |
+ utf8_printf(stderr,"Error: unable to open database \"%s\": %s\n", |
p->zDbFilename, sqlite3_errmsg(p->db)); |
if( keepAlive ) return; |
exit(1); |
@@ -2169,7 +2594,7 @@ static void output_file_close(FILE *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 |
+** recognized and do the right thing. NULL is returned if the output |
** filename is "off". |
*/ |
static FILE *output_file_open(const char *zFile){ |
@@ -2189,17 +2614,30 @@ static FILE *output_file_open(const char *zFile){ |
return f; |
} |
+#if !defined(SQLITE_UNTESTABLE) |
+#if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT) |
/* |
** A routine for handling output from sqlite3_trace(). |
*/ |
-static void sql_trace_callback(void *pArg, const char *z){ |
+static int sql_trace_callback( |
+ unsigned mType, |
+ void *pArg, |
+ void *pP, |
+ void *pX |
+){ |
FILE *f = (FILE*)pArg; |
+ UNUSED_PARAMETER(mType); |
+ UNUSED_PARAMETER(pP); |
if( f ){ |
+ const char *z = (const char*)pX; |
int i = (int)strlen(z); |
while( i>0 && z[i-1]==';' ){ i--; } |
utf8_printf(f, "%.*s;\n", i, z); |
} |
+ return 0; |
} |
+#endif |
+#endif |
/* |
** A no-op routine that runs with the ".breakpoint" doc-command. This is |
@@ -2358,7 +2796,7 @@ static void tryToCloneData( |
sqlite3 *newDb, |
const char *zTable |
){ |
- sqlite3_stmt *pQuery = 0; |
+ sqlite3_stmt *pQuery = 0; |
sqlite3_stmt *pInsert = 0; |
char *zQuery = 0; |
char *zInsert = 0; |
@@ -2595,10 +3033,10 @@ static int db_int(ShellState *p, const char *zSql){ |
/* |
** Convert a 2-byte or 4-byte big-endian integer into a native integer |
*/ |
-unsigned int get2byteInt(unsigned char *a){ |
+static unsigned int get2byteInt(unsigned char *a){ |
return (a[0]<<8) + a[1]; |
} |
-unsigned int get4byteInt(unsigned char *a){ |
+static unsigned int get4byteInt(unsigned char *a){ |
return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3]; |
} |
@@ -2662,9 +3100,9 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ |
utf8_printf(p->out, "%-20s %u", aField[i].zName, val); |
switch( ofst ){ |
case 56: { |
- if( val==1 ) raw_printf(p->out, " (utf8)"); |
- if( val==2 ) raw_printf(p->out, " (utf16le)"); |
- if( val==3 ) raw_printf(p->out, " (utf16be)"); |
+ if( val==1 ) raw_printf(p->out, " (utf8)"); |
+ if( val==2 ) raw_printf(p->out, " (utf16le)"); |
+ if( val==3 ) raw_printf(p->out, " (utf16be)"); |
} |
} |
raw_printf(p->out, "\n"); |
@@ -2682,27 +3120,398 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ |
sqlite3_free(zSql); |
utf8_printf(p->out, "%-20s %d\n", aQuery[i].zName, val); |
} |
- sqlite3_free(zSchemaTab); |
- return 0; |
-} |
+ sqlite3_free(zSchemaTab); |
+ return 0; |
+} |
+ |
+/* |
+** Print the current sqlite3_errmsg() value to stderr and return 1. |
+*/ |
+static int shellDatabaseError(sqlite3 *db){ |
+ const char *zErr = sqlite3_errmsg(db); |
+ utf8_printf(stderr, "Error: %s\n", zErr); |
+ return 1; |
+} |
+ |
+/* |
+** Print an out-of-memory message to stderr and return 1. |
+*/ |
+static int shellNomemError(void){ |
+ raw_printf(stderr, "Error: out of memory\n"); |
+ return 1; |
+} |
+ |
+/* |
+** Compare the pattern in zGlob[] against the text in z[]. Return TRUE |
+** if they match and FALSE (0) if they do not match. |
+** |
+** Globbing rules: |
+** |
+** '*' Matches any sequence of zero or more characters. |
+** |
+** '?' Matches exactly one character. |
+** |
+** [...] Matches one character from the enclosed list of |
+** characters. |
+** |
+** [^...] Matches one character not in the enclosed list. |
+** |
+** '#' Matches any sequence of one or more digits with an |
+** optional + or - sign in front |
+** |
+** ' ' Any span of whitespace matches any other span of |
+** whitespace. |
+** |
+** Extra whitespace at the end of z[] is ignored. |
+*/ |
+static int testcase_glob(const char *zGlob, const char *z){ |
+ int c, c2; |
+ int invert; |
+ int seen; |
+ |
+ while( (c = (*(zGlob++)))!=0 ){ |
+ if( IsSpace(c) ){ |
+ if( !IsSpace(*z) ) return 0; |
+ while( IsSpace(*zGlob) ) zGlob++; |
+ while( IsSpace(*z) ) z++; |
+ }else if( c=='*' ){ |
+ while( (c=(*(zGlob++))) == '*' || c=='?' ){ |
+ if( c=='?' && (*(z++))==0 ) return 0; |
+ } |
+ if( c==0 ){ |
+ return 1; |
+ }else if( c=='[' ){ |
+ while( *z && testcase_glob(zGlob-1,z)==0 ){ |
+ z++; |
+ } |
+ return (*z)!=0; |
+ } |
+ while( (c2 = (*(z++)))!=0 ){ |
+ while( c2!=c ){ |
+ c2 = *(z++); |
+ if( c2==0 ) return 0; |
+ } |
+ if( testcase_glob(zGlob,z) ) return 1; |
+ } |
+ return 0; |
+ }else if( c=='?' ){ |
+ if( (*(z++))==0 ) return 0; |
+ }else if( c=='[' ){ |
+ int prior_c = 0; |
+ seen = 0; |
+ invert = 0; |
+ c = *(z++); |
+ if( c==0 ) return 0; |
+ c2 = *(zGlob++); |
+ if( c2=='^' ){ |
+ invert = 1; |
+ c2 = *(zGlob++); |
+ } |
+ if( c2==']' ){ |
+ if( c==']' ) seen = 1; |
+ c2 = *(zGlob++); |
+ } |
+ while( c2 && c2!=']' ){ |
+ if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){ |
+ c2 = *(zGlob++); |
+ if( c>=prior_c && c<=c2 ) seen = 1; |
+ prior_c = 0; |
+ }else{ |
+ if( c==c2 ){ |
+ seen = 1; |
+ } |
+ prior_c = c2; |
+ } |
+ c2 = *(zGlob++); |
+ } |
+ if( c2==0 || (seen ^ invert)==0 ) return 0; |
+ }else if( c=='#' ){ |
+ if( (z[0]=='-' || z[0]=='+') && IsDigit(z[1]) ) z++; |
+ if( !IsDigit(z[0]) ) return 0; |
+ z++; |
+ while( IsDigit(z[0]) ){ z++; } |
+ }else{ |
+ if( c!=(*(z++)) ) return 0; |
+ } |
+ } |
+ while( IsSpace(*z) ){ z++; } |
+ return *z==0; |
+} |
+ |
+ |
+/* |
+** Compare the string as a command-line option with either one or two |
+** initial "-" characters. |
+*/ |
+static int optionMatch(const char *zStr, const char *zOpt){ |
+ if( zStr[0]!='-' ) return 0; |
+ zStr++; |
+ if( zStr[0]=='-' ) zStr++; |
+ return strcmp(zStr, zOpt)==0; |
+} |
+ |
+/* |
+** Delete a file. |
+*/ |
+int shellDeleteFile(const char *zFilename){ |
+ int rc; |
+#ifdef _WIN32 |
+ wchar_t *z = sqlite3_win32_utf8_to_unicode(zFilename); |
+ rc = _wunlink(z); |
+ sqlite3_free(z); |
+#else |
+ rc = unlink(zFilename); |
+#endif |
+ return rc; |
+} |
+ |
+ |
+/* |
+** The implementation of SQL scalar function fkey_collate_clause(), used |
+** by the ".lint fkey-indexes" command. This scalar function is always |
+** called with four arguments - the parent table name, the parent column name, |
+** the child table name and the child column name. |
+** |
+** fkey_collate_clause('parent-tab', 'parent-col', 'child-tab', 'child-col') |
+** |
+** If either of the named tables or columns do not exist, this function |
+** returns an empty string. An empty string is also returned if both tables |
+** and columns exist but have the same default collation sequence. Or, |
+** if both exist but the default collation sequences are different, this |
+** function returns the string " COLLATE <parent-collation>", where |
+** <parent-collation> is the default collation sequence of the parent column. |
+*/ |
+static void shellFkeyCollateClause( |
+ sqlite3_context *pCtx, |
+ int nVal, |
+ sqlite3_value **apVal |
+){ |
+ sqlite3 *db = sqlite3_context_db_handle(pCtx); |
+ const char *zParent; |
+ const char *zParentCol; |
+ const char *zParentSeq; |
+ const char *zChild; |
+ const char *zChildCol; |
+ const char *zChildSeq = 0; /* Initialize to avoid false-positive warning */ |
+ int rc; |
+ |
+ assert( nVal==4 ); |
+ zParent = (const char*)sqlite3_value_text(apVal[0]); |
+ zParentCol = (const char*)sqlite3_value_text(apVal[1]); |
+ zChild = (const char*)sqlite3_value_text(apVal[2]); |
+ zChildCol = (const char*)sqlite3_value_text(apVal[3]); |
+ |
+ sqlite3_result_text(pCtx, "", -1, SQLITE_STATIC); |
+ rc = sqlite3_table_column_metadata( |
+ db, "main", zParent, zParentCol, 0, &zParentSeq, 0, 0, 0 |
+ ); |
+ if( rc==SQLITE_OK ){ |
+ rc = sqlite3_table_column_metadata( |
+ db, "main", zChild, zChildCol, 0, &zChildSeq, 0, 0, 0 |
+ ); |
+ } |
+ |
+ if( rc==SQLITE_OK && sqlite3_stricmp(zParentSeq, zChildSeq) ){ |
+ char *z = sqlite3_mprintf(" COLLATE %s", zParentSeq); |
+ sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT); |
+ sqlite3_free(z); |
+ } |
+} |
+ |
+ |
+/* |
+** 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 rowid FROM child_table WHERE child_key=?" |
+ ** |
+ ** This is the same SELECT 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 rowid FROM ' || quote(s.name) || ' WHERE '" |
+ " || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' " |
+ " || fkey_collate_clause(f.[table], f.[to], s.name, f.[from]),' AND ')" |
+ ", " |
+ " 'SEARCH TABLE ' || s.name || ' USING COVERING INDEX*('" |
+ " || group_concat('*=?', ' AND ') || ')'" |
+ ", " |
+ " s.name || '(' || group_concat(f.[from], ', ') || ')'" |
+ ", " |
+ " f.[table] || '(' || group_concat(COALESCE(f.[to], " |
+ " (SELECT name FROM pragma_table_info(f.[table]) WHERE pk=seq+1)" |
+ " )) || ')'" |
+ ", " |
+ " 'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))" |
+ " || ' ON ' || quote(s.name) || '('" |
+ " || group_concat(quote(f.[from]) ||" |
+ " fkey_collate_clause(f.[table], f.[to], s.name, f.[from]), ', ')" |
+ " || ');'" |
+ ", " |
+ " f.[table] " |
+ |
+ "FROM sqlite_master AS s, pragma_foreign_key_list(s.name) AS f " |
+ "GROUP BY s.name, f.id " |
+ "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)" |
+ ; |
+ |
+ for(i=2; i<nArg; i++){ |
+ int n = (int)strlen(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); |
+ |
+ 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 = (0==sqlite3_strglob(zGlob, 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)); |
+ } |
-/* |
-** Print the current sqlite3_errmsg() value to stderr and return 1. |
-*/ |
-static int shellDatabaseError(sqlite3 *db){ |
- const char *zErr = sqlite3_errmsg(db); |
- utf8_printf(stderr, "Error: %s\n", zErr); |
- return 1; |
+ return rc; |
} |
/* |
-** Print an out-of-memory message to stderr and return 1. |
+** Implementation of ".lint" dot command. |
*/ |
-static int shellNomemError(void){ |
- raw_printf(stderr, "Error: out of memory\n"); |
- return 1; |
+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 ? (int)strlen(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 an input line begins with "." then invoke this routine to |
** process that line. |
@@ -2724,9 +3533,9 @@ static int do_meta_command(char *zLine, ShellState *p){ |
if( zLine[h]=='\'' || zLine[h]=='"' ){ |
int delim = zLine[h++]; |
azArg[nArg++] = &zLine[h]; |
- while( zLine[h] && zLine[h]!=delim ){ |
+ while( zLine[h] && zLine[h]!=delim ){ |
if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++; |
- h++; |
+ h++; |
} |
if( zLine[h]==delim ){ |
zLine[h++] = 0; |
@@ -2745,6 +3554,23 @@ static int do_meta_command(char *zLine, ShellState *p){ |
if( nArg==0 ) return 0; /* no tokens, no error */ |
n = strlen30(azArg[0]); |
c = azArg[0][0]; |
+ |
+#ifndef SQLITE_OMIT_AUTHORIZATION |
+ if( c=='a' && strncmp(azArg[0], "auth", n)==0 ){ |
+ if( nArg!=2 ){ |
+ raw_printf(stderr, "Usage: .auth ON|OFF\n"); |
+ rc = 1; |
+ goto meta_command_exit; |
+ } |
+ open_db(p, 0); |
+ if( booleanValue(azArg[1]) ){ |
+ sqlite3_set_authorizer(p->db, shellAuth, p); |
+ }else{ |
+ sqlite3_set_authorizer(p->db, 0, 0); |
+ } |
+ }else |
+#endif |
+ |
if( (c=='b' && n>=3 && strncmp(azArg[0], "backup", n)==0) |
|| (c=='s' && n>=3 && strncmp(azArg[0], "save", n)==0) |
){ |
@@ -2813,9 +3639,9 @@ static int do_meta_command(char *zLine, ShellState *p){ |
if( c=='b' && n>=3 && strncmp(azArg[0], "binary", n)==0 ){ |
if( nArg==2 ){ |
if( booleanValue(azArg[1]) ){ |
- setBinaryMode(p->out); |
+ setBinaryMode(p->out, 1); |
}else{ |
- setTextMode(p->out); |
+ setTextMode(p->out, 1); |
} |
}else{ |
raw_printf(stderr, "Usage: .binary on|off\n"); |
@@ -2839,6 +3665,31 @@ static int do_meta_command(char *zLine, ShellState *p){ |
} |
}else |
+ /* Cancel output redirection, if it is currently set (by .testcase) |
+ ** Then read the content of the testcase-out.txt file and compare against |
+ ** azArg[1]. If there are differences, report an error and exit. |
+ */ |
+ if( c=='c' && n>=3 && strncmp(azArg[0], "check", n)==0 ){ |
+ char *zRes = 0; |
+ output_reset(p); |
+ if( nArg!=2 ){ |
+ raw_printf(stderr, "Usage: .check GLOB-PATTERN\n"); |
+ rc = 2; |
+ }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){ |
+ raw_printf(stderr, "Error: cannot read 'testcase-out.txt'\n"); |
+ rc = 2; |
+ }else if( testcase_glob(azArg[1],zRes)==0 ){ |
+ utf8_printf(stderr, |
+ "testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n", |
+ p->zTestcase, azArg[1], zRes); |
+ rc = 2; |
+ }else{ |
+ utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase); |
+ p->nCheck++; |
+ } |
+ sqlite3_free(zRes); |
+ }else |
+ |
if( c=='c' && strncmp(azArg[0], "clone", n)==0 ){ |
if( nArg==2 ){ |
tryToClone(p, azArg[1]); |
@@ -2853,13 +3704,12 @@ static int do_meta_command(char *zLine, ShellState *p){ |
char *zErrMsg = 0; |
open_db(p, 0); |
memcpy(&data, p, sizeof(data)); |
- data.showHeader = 1; |
- data.mode = MODE_Column; |
- data.colWidth[0] = 3; |
- data.colWidth[1] = 15; |
- data.colWidth[2] = 58; |
+ data.showHeader = 0; |
+ data.cMode = data.mode = MODE_List; |
+ sqlite3_snprintf(sizeof(data.colSeparator),data.colSeparator,": "); |
data.cnt = 0; |
- sqlite3_exec(p->db, "PRAGMA database_list; ", callback, &data, &zErrMsg); |
+ sqlite3_exec(p->db, "SELECT name, file FROM pragma_database_list", |
+ callback, &data, &zErrMsg); |
if( zErrMsg ){ |
utf8_printf(stderr,"Error: %s\n", zErrMsg); |
sqlite3_free(zErrMsg); |
@@ -2887,11 +3737,11 @@ static int do_meta_command(char *zLine, ShellState *p){ |
sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0); |
p->nErr = 0; |
if( nArg==1 ){ |
- run_schema_dump_query(p, |
+ run_schema_dump_query(p, |
"SELECT name, type, sql FROM sqlite_master " |
"WHERE sql NOT NULL AND type=='table' AND name!='sqlite_sequence'" |
); |
- run_schema_dump_query(p, |
+ run_schema_dump_query(p, |
"SELECT name, type, sql FROM sqlite_master " |
"WHERE name=='sqlite_sequence'" |
); |
@@ -2936,11 +3786,15 @@ static int do_meta_command(char *zLine, ShellState *p){ |
if( c=='e' && strncmp(azArg[0], "eqp", n)==0 ){ |
if( nArg==2 ){ |
- p->autoEQP = booleanValue(azArg[1]); |
+ if( strcmp(azArg[1],"full")==0 ){ |
+ p->autoEQP = 2; |
+ }else{ |
+ p->autoEQP = booleanValue(azArg[1]); |
+ } |
}else{ |
- raw_printf(stderr, "Usage: .eqp on|off\n"); |
+ raw_printf(stderr, "Usage: .eqp on|off|full\n"); |
rc = 1; |
- } |
+ } |
}else |
if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){ |
@@ -2949,37 +3803,24 @@ static int do_meta_command(char *zLine, ShellState *p){ |
}else |
if( c=='e' && strncmp(azArg[0], "explain", n)==0 ){ |
- int val = nArg>=2 ? booleanValue(azArg[1]) : 1; |
- if(val == 1) { |
- if(!p->normalMode.valid) { |
- p->normalMode.valid = 1; |
- p->normalMode.mode = p->mode; |
- p->normalMode.showHeader = p->showHeader; |
- memcpy(p->normalMode.colWidth,p->colWidth,sizeof(p->colWidth)); |
- } |
- /* We could put this code under the !p->explainValid |
- ** condition so that it does not execute if we are already in |
- ** explain mode. However, always executing it allows us an easy |
- ** was to reset to explain mode in case the user previously |
- ** did an .explain followed by a .width, .mode or .header |
- ** command. |
- */ |
+ int val = 1; |
+ if( nArg>=2 ){ |
+ if( strcmp(azArg[1],"auto")==0 ){ |
+ val = 99; |
+ }else{ |
+ val = booleanValue(azArg[1]); |
+ } |
+ } |
+ if( val==1 && p->mode!=MODE_Explain ){ |
+ p->normalMode = p->mode; |
p->mode = MODE_Explain; |
- p->showHeader = 1; |
- memset(p->colWidth,0,sizeof(p->colWidth)); |
- p->colWidth[0] = 4; /* addr */ |
- p->colWidth[1] = 13; /* opcode */ |
- p->colWidth[2] = 4; /* P1 */ |
- p->colWidth[3] = 4; /* P2 */ |
- p->colWidth[4] = 4; /* P3 */ |
- p->colWidth[5] = 13; /* P4 */ |
- p->colWidth[6] = 2; /* P5 */ |
- p->colWidth[7] = 13; /* Comment */ |
- }else if (p->normalMode.valid) { |
- p->normalMode.valid = 0; |
- p->mode = p->normalMode.mode; |
- p->showHeader = p->normalMode.showHeader; |
- memcpy(p->colWidth,p->normalMode.colWidth,sizeof(p->colWidth)); |
+ p->autoExplain = 0; |
+ }else if( val==0 ){ |
+ if( p->mode==MODE_Explain ) p->mode = p->normalMode; |
+ p->autoExplain = 0; |
+ }else if( val==99 ){ |
+ if( p->mode==MODE_Explain ) p->mode = p->normalMode; |
+ p->autoExplain = 1; |
} |
}else |
@@ -2987,15 +3828,19 @@ static int do_meta_command(char *zLine, ShellState *p){ |
ShellState data; |
char *zErrMsg = 0; |
int doStats = 0; |
+ memcpy(&data, p, sizeof(data)); |
+ data.showHeader = 0; |
+ data.cMode = data.mode = MODE_Semi; |
+ if( nArg==2 && optionMatch(azArg[1], "indent") ){ |
+ data.cMode = data.mode = MODE_Pretty; |
+ nArg = 1; |
+ } |
if( nArg!=1 ){ |
- raw_printf(stderr, "Usage: .fullschema\n"); |
+ raw_printf(stderr, "Usage: .fullschema ?--indent?\n"); |
rc = 1; |
goto meta_command_exit; |
} |
open_db(p, 0); |
- memcpy(&data, p, sizeof(data)); |
- data.showHeader = 0; |
- data.mode = MODE_Semi; |
rc = sqlite3_exec(p->db, |
"SELECT sql FROM" |
" (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x" |
@@ -3020,7 +3865,7 @@ static int do_meta_command(char *zLine, ShellState *p){ |
raw_printf(p->out, "ANALYZE sqlite_master;\n"); |
sqlite3_exec(p->db, "SELECT 'ANALYZE sqlite_master'", |
callback, &data, &zErrMsg); |
- data.mode = MODE_Insert; |
+ data.cMode = data.mode = MODE_Insert; |
data.zDestTable = "sqlite_stat1"; |
shell_exec(p->db, "SELECT * FROM sqlite_stat1", |
shell_callback, &data,&zErrMsg); |
@@ -3138,7 +3983,7 @@ static int do_meta_command(char *zLine, ShellState *p){ |
char *zCreate = sqlite3_mprintf("CREATE TABLE %s", zTable); |
char cSep = '('; |
while( xRead(&sCtx) ){ |
- zCreate = sqlite3_mprintf("%z%c\n \"%s\" TEXT", zCreate, cSep, sCtx.z); |
+ zCreate = sqlite3_mprintf("%z%c\n \"%w\" TEXT", zCreate, cSep, sCtx.z); |
cSep = ','; |
if( sCtx.cTerm!=sCtx.cColSep ) break; |
} |
@@ -3245,51 +4090,78 @@ static int do_meta_command(char *zLine, ShellState *p){ |
if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0); |
}else |
- if( c=='i' && (strncmp(azArg[0], "indices", n)==0 |
- || strncmp(azArg[0], "indexes", n)==0) ){ |
- ShellState data; |
- char *zErrMsg = 0; |
- open_db(p, 0); |
- memcpy(&data, p, sizeof(data)); |
- data.showHeader = 0; |
- data.mode = MODE_List; |
- if( nArg==1 ){ |
- rc = sqlite3_exec(p->db, |
- "SELECT name FROM sqlite_master " |
- "WHERE type='index' AND name NOT LIKE 'sqlite_%' " |
- "UNION ALL " |
- "SELECT name FROM sqlite_temp_master " |
- "WHERE type='index' " |
- "ORDER BY 1", |
- callback, &data, &zErrMsg |
- ); |
- }else if( nArg==2 ){ |
- zShellStatic = azArg[1]; |
- rc = sqlite3_exec(p->db, |
- "SELECT name FROM sqlite_master " |
- "WHERE type='index' AND tbl_name LIKE shellstatic() " |
- "UNION ALL " |
- "SELECT name FROM sqlite_temp_master " |
- "WHERE type='index' AND tbl_name LIKE shellstatic() " |
- "ORDER BY 1", |
- callback, &data, &zErrMsg |
- ); |
- zShellStatic = 0; |
- }else{ |
- raw_printf(stderr, "Usage: .indexes ?LIKE-PATTERN?\n"); |
+#ifndef SQLITE_UNTESTABLE |
+ if( c=='i' && strncmp(azArg[0], "imposter", n)==0 ){ |
+ char *zSql; |
+ char *zCollist = 0; |
+ sqlite3_stmt *pStmt; |
+ int tnum = 0; |
+ int i; |
+ if( nArg!=3 ){ |
+ utf8_printf(stderr, "Usage: .imposter INDEX IMPOSTER\n"); |
rc = 1; |
goto meta_command_exit; |
} |
- if( zErrMsg ){ |
- utf8_printf(stderr,"Error: %s\n", zErrMsg); |
- sqlite3_free(zErrMsg); |
+ open_db(p, 0); |
+ zSql = sqlite3_mprintf("SELECT rootpage FROM sqlite_master" |
+ " WHERE name='%q' AND type='index'", azArg[1]); |
+ sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); |
+ sqlite3_free(zSql); |
+ if( sqlite3_step(pStmt)==SQLITE_ROW ){ |
+ tnum = sqlite3_column_int(pStmt, 0); |
+ } |
+ sqlite3_finalize(pStmt); |
+ if( tnum==0 ){ |
+ utf8_printf(stderr, "no such index: \"%s\"\n", azArg[1]); |
rc = 1; |
- }else if( rc != SQLITE_OK ){ |
- raw_printf(stderr, |
- "Error: querying sqlite_master and sqlite_temp_master\n"); |
+ goto meta_command_exit; |
+ } |
+ zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]); |
+ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); |
+ sqlite3_free(zSql); |
+ i = 0; |
+ while( sqlite3_step(pStmt)==SQLITE_ROW ){ |
+ char zLabel[20]; |
+ const char *zCol = (const char*)sqlite3_column_text(pStmt,2); |
+ i++; |
+ if( zCol==0 ){ |
+ if( sqlite3_column_int(pStmt,1)==-1 ){ |
+ zCol = "_ROWID_"; |
+ }else{ |
+ sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i); |
+ zCol = zLabel; |
+ } |
+ } |
+ if( zCollist==0 ){ |
+ zCollist = sqlite3_mprintf("\"%w\"", zCol); |
+ }else{ |
+ zCollist = sqlite3_mprintf("%z,\"%w\"", zCollist, zCol); |
+ } |
+ } |
+ sqlite3_finalize(pStmt); |
+ zSql = sqlite3_mprintf( |
+ "CREATE TABLE \"%w\"(%s,PRIMARY KEY(%s))WITHOUT ROWID", |
+ azArg[2], zCollist, zCollist); |
+ sqlite3_free(zCollist); |
+ rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 1, tnum); |
+ if( rc==SQLITE_OK ){ |
+ rc = sqlite3_exec(p->db, zSql, 0, 0, 0); |
+ sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0); |
+ if( rc ){ |
+ utf8_printf(stderr, "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db)); |
+ }else{ |
+ utf8_printf(stdout, "%s;\n", zSql); |
+ raw_printf(stdout, |
+ "WARNING: writing to an imposter table will corrupt the index!\n" |
+ ); |
+ } |
+ }else{ |
+ raw_printf(stderr, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); |
rc = 1; |
} |
+ sqlite3_free(zSql); |
}else |
+#endif /* !defined(SQLITE_OMIT_TEST_CONTROL) */ |
#ifdef SQLITE_ENABLE_IOTRACE |
if( c=='i' && strncmp(azArg[0], "iotrace", n)==0 ){ |
@@ -3313,6 +4185,7 @@ static int do_meta_command(char *zLine, ShellState *p){ |
} |
}else |
#endif |
+ |
if( c=='l' && n>=5 && strncmp(azArg[0], "limits", n)==0 ){ |
static const struct { |
const char *zLimitName; /* Name of a limit */ |
@@ -3335,7 +4208,7 @@ static int do_meta_command(char *zLine, ShellState *p){ |
open_db(p, 0); |
if( nArg==1 ){ |
for(i=0; i<ArraySize(aLimit); i++){ |
- printf("%20s %d\n", aLimit[i].zLimitName, |
+ printf("%20s %d\n", aLimit[i].zLimitName, |
sqlite3_limit(p->db, aLimit[i].limitCode, -1)); |
} |
}else if( nArg>3 ){ |
@@ -3372,6 +4245,11 @@ static int do_meta_command(char *zLine, ShellState *p){ |
} |
}else |
+ if( c=='l' && n>2 && strncmp(azArg[0], "lint", n)==0 ){ |
+ open_db(p, 0); |
+ lintDotCommand(p, azArg, nArg); |
+ }else |
+ |
#ifndef SQLITE_OMIT_LOAD_EXTENSION |
if( c=='l' && strncmp(azArg[0], "load", n)==0 ){ |
const char *zFile, *zProc; |
@@ -3410,15 +4288,20 @@ static int do_meta_command(char *zLine, ShellState *p){ |
int c2 = zMode[0]; |
if( c2=='l' && n2>2 && strncmp(azArg[1],"lines",n2)==0 ){ |
p->mode = MODE_Line; |
+ sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); |
}else if( c2=='c' && strncmp(azArg[1],"columns",n2)==0 ){ |
p->mode = MODE_Column; |
+ sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); |
}else if( c2=='l' && n2>2 && strncmp(azArg[1],"list",n2)==0 ){ |
p->mode = MODE_List; |
+ sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Column); |
+ sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); |
}else if( c2=='h' && strncmp(azArg[1],"html",n2)==0 ){ |
p->mode = MODE_Html; |
}else if( c2=='t' && strncmp(azArg[1],"tcl",n2)==0 ){ |
p->mode = MODE_Tcl; |
sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Space); |
+ sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); |
}else if( c2=='c' && strncmp(azArg[1],"csv",n2)==0 ){ |
p->mode = MODE_Csv; |
sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); |
@@ -3429,15 +4312,18 @@ static int do_meta_command(char *zLine, ShellState *p){ |
}else if( c2=='i' && strncmp(azArg[1],"insert",n2)==0 ){ |
p->mode = MODE_Insert; |
set_table_name(p, nArg>=3 ? azArg[2] : "table"); |
+ }else if( c2=='q' && strncmp(azArg[1],"quote",n2)==0 ){ |
+ p->mode = MODE_Quote; |
}else if( c2=='a' && strncmp(azArg[1],"ascii",n2)==0 ){ |
p->mode = MODE_Ascii; |
sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Unit); |
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Record); |
}else { |
raw_printf(stderr, "Error: mode should be one of: " |
- "ascii column csv html insert line list tabs tcl\n"); |
+ "ascii column csv html insert line list quote tabs tcl\n"); |
rc = 1; |
} |
+ p->cMode = p->mode; |
}else |
if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 ){ |
@@ -3451,21 +4337,43 @@ static int do_meta_command(char *zLine, ShellState *p){ |
}else |
if( c=='o' && strncmp(azArg[0], "open", n)==0 && n>=2 ){ |
- sqlite3 *savedDb = p->db; |
- const char *zSavedFilename = p->zDbFilename; |
- char *zNewFilename = 0; |
+ char *zNewFilename; /* Name of the database file to open */ |
+ int iName = 1; /* Index in azArg[] of the filename */ |
+ int newFlag = 0; /* True to delete file before opening */ |
+ /* Close the existing database */ |
+ session_close_all(p); |
+ sqlite3_close(p->db); |
p->db = 0; |
- if( nArg>=2 ) zNewFilename = sqlite3_mprintf("%s", azArg[1]); |
- p->zDbFilename = zNewFilename; |
- open_db(p, 1); |
- if( p->db!=0 ){ |
- sqlite3_close(savedDb); |
- sqlite3_free(p->zFreeOnClose); |
- p->zFreeOnClose = zNewFilename; |
- }else{ |
- sqlite3_free(zNewFilename); |
- p->db = savedDb; |
- p->zDbFilename = zSavedFilename; |
+ sqlite3_free(p->zFreeOnClose); |
+ p->zFreeOnClose = 0; |
+ /* Check for command-line arguments */ |
+ for(iName=1; iName<nArg && azArg[iName][0]=='-'; iName++){ |
+ const char *z = azArg[iName]; |
+ if( optionMatch(z,"new") ){ |
+ newFlag = 1; |
+ }else if( z[0]=='-' ){ |
+ utf8_printf(stderr, "unknown option: %s\n", z); |
+ rc = 1; |
+ goto meta_command_exit; |
+ } |
+ } |
+ /* If a filename is specified, try to open it first */ |
+ zNewFilename = nArg>iName ? sqlite3_mprintf("%s", azArg[iName]) : 0; |
+ if( zNewFilename ){ |
+ if( newFlag ) shellDeleteFile(zNewFilename); |
+ p->zDbFilename = zNewFilename; |
+ open_db(p, 1); |
+ if( p->db==0 ){ |
+ utf8_printf(stderr, "Error: cannot open '%s'\n", zNewFilename); |
+ sqlite3_free(zNewFilename); |
+ }else{ |
+ p->zFreeOnClose = zNewFilename; |
+ } |
+ } |
+ if( p->db==0 ){ |
+ /* As a fall-back open a TEMP database */ |
+ p->zDbFilename = 0; |
+ open_db(p, 0); |
} |
}else |
@@ -3627,8 +4535,13 @@ static int do_meta_command(char *zLine, ShellState *p){ |
open_db(p, 0); |
memcpy(&data, p, sizeof(data)); |
data.showHeader = 0; |
- data.mode = MODE_Semi; |
- if( nArg==2 ){ |
+ data.cMode = data.mode = MODE_Semi; |
+ if( nArg>=2 && optionMatch(azArg[1], "indent") ){ |
+ data.cMode = data.mode = MODE_Pretty; |
+ nArg--; |
+ if( nArg==2 ) azArg[1] = azArg[2]; |
+ } |
+ if( nArg==2 && azArg[1][0]!='-' ){ |
int i; |
for(i=0; azArg[1][i]; i++) azArg[1][i] = ToLower(azArg[1][i]); |
if( strcmp(azArg[1],"sqlite_master")==0 ){ |
@@ -3683,7 +4596,7 @@ static int do_meta_command(char *zLine, ShellState *p){ |
callback, &data, &zErrMsg |
); |
}else{ |
- raw_printf(stderr, "Usage: .schema ?LIKE-PATTERN?\n"); |
+ raw_printf(stderr, "Usage: .schema ?--indent? ?LIKE-PATTERN?\n"); |
rc = 1; |
goto meta_command_exit; |
} |
@@ -3699,14 +4612,207 @@ static int do_meta_command(char *zLine, ShellState *p){ |
} |
}else |
- |
#if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_SELECTTRACE) |
if( c=='s' && n==11 && strncmp(azArg[0], "selecttrace", n)==0 ){ |
- extern int sqlite3SelectTrace; |
sqlite3SelectTrace = integerValue(azArg[1]); |
}else |
#endif |
+#if defined(SQLITE_ENABLE_SESSION) |
+ if( c=='s' && strncmp(azArg[0],"session",n)==0 && n>=3 ){ |
+ OpenSession *pSession = &p->aSession[0]; |
+ char **azCmd = &azArg[1]; |
+ int iSes = 0; |
+ int nCmd = nArg - 1; |
+ int i; |
+ if( nArg<=1 ) goto session_syntax_error; |
+ open_db(p, 0); |
+ if( nArg>=3 ){ |
+ for(iSes=0; iSes<p->nSession; iSes++){ |
+ if( strcmp(p->aSession[iSes].zName, azArg[1])==0 ) break; |
+ } |
+ if( iSes<p->nSession ){ |
+ pSession = &p->aSession[iSes]; |
+ azCmd++; |
+ nCmd--; |
+ }else{ |
+ pSession = &p->aSession[0]; |
+ iSes = 0; |
+ } |
+ } |
+ |
+ /* .session attach TABLE |
+ ** Invoke the sqlite3session_attach() interface to attach a particular |
+ ** table so that it is never filtered. |
+ */ |
+ if( strcmp(azCmd[0],"attach")==0 ){ |
+ if( nCmd!=2 ) goto session_syntax_error; |
+ if( pSession->p==0 ){ |
+ session_not_open: |
+ raw_printf(stderr, "ERROR: No sessions are open\n"); |
+ }else{ |
+ rc = sqlite3session_attach(pSession->p, azCmd[1]); |
+ if( rc ){ |
+ raw_printf(stderr, "ERROR: sqlite3session_attach() returns %d\n", rc); |
+ rc = 0; |
+ } |
+ } |
+ }else |
+ |
+ /* .session changeset FILE |
+ ** .session patchset FILE |
+ ** Write a changeset or patchset into a file. The file is overwritten. |
+ */ |
+ if( strcmp(azCmd[0],"changeset")==0 || strcmp(azCmd[0],"patchset")==0 ){ |
+ FILE *out = 0; |
+ if( nCmd!=2 ) goto session_syntax_error; |
+ if( pSession->p==0 ) goto session_not_open; |
+ out = fopen(azCmd[1], "wb"); |
+ if( out==0 ){ |
+ utf8_printf(stderr, "ERROR: cannot open \"%s\" for writing\n", azCmd[1]); |
+ }else{ |
+ int szChng; |
+ void *pChng; |
+ if( azCmd[0][0]=='c' ){ |
+ rc = sqlite3session_changeset(pSession->p, &szChng, &pChng); |
+ }else{ |
+ rc = sqlite3session_patchset(pSession->p, &szChng, &pChng); |
+ } |
+ if( rc ){ |
+ printf("Error: error code %d\n", rc); |
+ rc = 0; |
+ } |
+ if( pChng |
+ && fwrite(pChng, szChng, 1, out)!=1 ){ |
+ raw_printf(stderr, "ERROR: Failed to write entire %d-byte output\n", |
+ szChng); |
+ } |
+ sqlite3_free(pChng); |
+ fclose(out); |
+ } |
+ }else |
+ |
+ /* .session close |
+ ** Close the identified session |
+ */ |
+ if( strcmp(azCmd[0], "close")==0 ){ |
+ if( nCmd!=1 ) goto session_syntax_error; |
+ if( p->nSession ){ |
+ session_close(pSession); |
+ p->aSession[iSes] = p->aSession[--p->nSession]; |
+ } |
+ }else |
+ |
+ /* .session enable ?BOOLEAN? |
+ ** Query or set the enable flag |
+ */ |
+ if( strcmp(azCmd[0], "enable")==0 ){ |
+ int ii; |
+ if( nCmd>2 ) goto session_syntax_error; |
+ ii = nCmd==1 ? -1 : booleanValue(azCmd[1]); |
+ if( p->nSession ){ |
+ ii = sqlite3session_enable(pSession->p, ii); |
+ utf8_printf(p->out, "session %s enable flag = %d\n", |
+ pSession->zName, ii); |
+ } |
+ }else |
+ |
+ /* .session filter GLOB .... |
+ ** Set a list of GLOB patterns of table names to be excluded. |
+ */ |
+ if( strcmp(azCmd[0], "filter")==0 ){ |
+ int ii, nByte; |
+ if( nCmd<2 ) goto session_syntax_error; |
+ if( p->nSession ){ |
+ for(ii=0; ii<pSession->nFilter; ii++){ |
+ sqlite3_free(pSession->azFilter[ii]); |
+ } |
+ sqlite3_free(pSession->azFilter); |
+ nByte = sizeof(pSession->azFilter[0])*(nCmd-1); |
+ pSession->azFilter = sqlite3_malloc( nByte ); |
+ if( pSession->azFilter==0 ){ |
+ raw_printf(stderr, "Error: out or memory\n"); |
+ exit(1); |
+ } |
+ for(ii=1; ii<nCmd; ii++){ |
+ pSession->azFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]); |
+ } |
+ pSession->nFilter = ii-1; |
+ } |
+ }else |
+ |
+ /* .session indirect ?BOOLEAN? |
+ ** Query or set the indirect flag |
+ */ |
+ if( strcmp(azCmd[0], "indirect")==0 ){ |
+ int ii; |
+ if( nCmd>2 ) goto session_syntax_error; |
+ ii = nCmd==1 ? -1 : booleanValue(azCmd[1]); |
+ if( p->nSession ){ |
+ ii = sqlite3session_indirect(pSession->p, ii); |
+ utf8_printf(p->out, "session %s indirect flag = %d\n", |
+ pSession->zName, ii); |
+ } |
+ }else |
+ |
+ /* .session isempty |
+ ** Determine if the session is empty |
+ */ |
+ if( strcmp(azCmd[0], "isempty")==0 ){ |
+ int ii; |
+ if( nCmd!=1 ) goto session_syntax_error; |
+ if( p->nSession ){ |
+ ii = sqlite3session_isempty(pSession->p); |
+ utf8_printf(p->out, "session %s isempty flag = %d\n", |
+ pSession->zName, ii); |
+ } |
+ }else |
+ |
+ /* .session list |
+ ** List all currently open sessions |
+ */ |
+ if( strcmp(azCmd[0],"list")==0 ){ |
+ for(i=0; i<p->nSession; i++){ |
+ utf8_printf(p->out, "%d %s\n", i, p->aSession[i].zName); |
+ } |
+ }else |
+ |
+ /* .session open DB NAME |
+ ** Open a new session called NAME on the attached database DB. |
+ ** DB is normally "main". |
+ */ |
+ if( strcmp(azCmd[0],"open")==0 ){ |
+ char *zName; |
+ if( nCmd!=3 ) goto session_syntax_error; |
+ zName = azCmd[2]; |
+ if( zName[0]==0 ) goto session_syntax_error; |
+ for(i=0; i<p->nSession; i++){ |
+ if( strcmp(p->aSession[i].zName,zName)==0 ){ |
+ utf8_printf(stderr, "Session \"%s\" already exists\n", zName); |
+ goto meta_command_exit; |
+ } |
+ } |
+ if( p->nSession>=ArraySize(p->aSession) ){ |
+ raw_printf(stderr, "Maximum of %d sessions\n", ArraySize(p->aSession)); |
+ goto meta_command_exit; |
+ } |
+ pSession = &p->aSession[p->nSession]; |
+ rc = sqlite3session_create(p->db, azCmd[1], &pSession->p); |
+ if( rc ){ |
+ raw_printf(stderr, "Cannot open session: error code=%d\n", rc); |
+ rc = 0; |
+ goto meta_command_exit; |
+ } |
+ pSession->nFilter = 0; |
+ sqlite3session_table_filter(pSession->p, session_filter, pSession); |
+ p->nSession++; |
+ pSession->zName = sqlite3_mprintf("%s", zName); |
+ }else |
+ /* If no command name matches, show a syntax error */ |
+ session_syntax_error: |
+ session_help(p); |
+ }else |
+#endif |
#ifdef SQLITE_DEBUG |
/* Undocumented commands for internal testing. Subject to change |
@@ -3767,16 +4873,18 @@ static int do_meta_command(char *zLine, ShellState *p){ |
}else |
if( c=='s' && strncmp(azArg[0], "show", n)==0 ){ |
+ static const char *azBool[] = { "off", "on", "full", "unk" }; |
int i; |
if( nArg!=1 ){ |
raw_printf(stderr, "Usage: .show\n"); |
rc = 1; |
goto meta_command_exit; |
} |
- utf8_printf(p->out, "%12.12s: %s\n","echo", p->echoOn ? "on" : "off"); |
- utf8_printf(p->out, "%12.12s: %s\n","eqp", p->autoEQP ? "on" : "off"); |
- utf8_printf(p->out,"%9.9s: %s\n","explain",p->normalMode.valid?"on":"off"); |
- utf8_printf(p->out,"%12.12s: %s\n","headers", p->showHeader ? "on" : "off"); |
+ utf8_printf(p->out, "%12.12s: %s\n","echo", azBool[p->echoOn!=0]); |
+ utf8_printf(p->out, "%12.12s: %s\n","eqp", azBool[p->autoEQP&3]); |
+ utf8_printf(p->out, "%12.12s: %s\n","explain", |
+ p->mode==MODE_Explain ? "on" : p->autoExplain ? "auto" : "off"); |
+ utf8_printf(p->out,"%12.12s: %s\n","headers", azBool[p->showHeader!=0]); |
utf8_printf(p->out, "%12.12s: %s\n","mode", modeDescr[p->mode]); |
utf8_printf(p->out, "%12.12s: ", "nullvalue"); |
output_c_string(p->out, p->nullValue); |
@@ -3789,24 +4897,31 @@ static int do_meta_command(char *zLine, ShellState *p){ |
utf8_printf(p->out,"%12.12s: ", "rowseparator"); |
output_c_string(p->out, p->rowSeparator); |
raw_printf(p->out, "\n"); |
- utf8_printf(p->out, "%12.12s: %s\n","stats", p->statsOn ? "on" : "off"); |
+ utf8_printf(p->out, "%12.12s: %s\n","stats", azBool[p->statsOn!=0]); |
utf8_printf(p->out, "%12.12s: ", "width"); |
for (i=0;i<(int)ArraySize(p->colWidth) && p->colWidth[i] != 0;i++) { |
raw_printf(p->out, "%d ", p->colWidth[i]); |
} |
raw_printf(p->out, "\n"); |
+ utf8_printf(p->out, "%12.12s: %s\n", "filename", |
+ p->zDbFilename ? p->zDbFilename : ""); |
}else |
if( c=='s' && strncmp(azArg[0], "stats", n)==0 ){ |
if( nArg==2 ){ |
p->statsOn = booleanValue(azArg[1]); |
+ }else if( nArg==1 ){ |
+ display_stats(p->db, p, 0); |
}else{ |
- raw_printf(stderr, "Usage: .stats on|off\n"); |
+ raw_printf(stderr, "Usage: .stats ?on|off?\n"); |
rc = 1; |
} |
}else |
- if( c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0 ){ |
+ if( (c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0) |
+ || (c=='i' && (strncmp(azArg[0], "indices", n)==0 |
+ || strncmp(azArg[0], "indexes", n)==0) ) |
+ ){ |
sqlite3_stmt *pStmt; |
char **azResult; |
int nRow, nAlloc; |
@@ -3819,28 +4934,41 @@ static int do_meta_command(char *zLine, ShellState *p){ |
/* Create an SQL statement to query for the list of tables in the |
** main and all attached databases where the table name matches the |
** LIKE pattern bound to variable "?1". */ |
- zSql = sqlite3_mprintf( |
- "SELECT name FROM sqlite_master" |
- " WHERE type IN ('table','view')" |
- " AND name NOT LIKE 'sqlite_%%'" |
- " AND name LIKE ?1"); |
- while( zSql && sqlite3_step(pStmt)==SQLITE_ROW ){ |
+ if( c=='t' ){ |
+ zSql = sqlite3_mprintf( |
+ "SELECT name FROM sqlite_master" |
+ " WHERE type IN ('table','view')" |
+ " AND name NOT LIKE 'sqlite_%%'" |
+ " AND name LIKE ?1"); |
+ }else if( nArg>2 ){ |
+ /* It is an historical accident that the .indexes command shows an error |
+ ** when called with the wrong number of arguments whereas the .tables |
+ ** command does not. */ |
+ raw_printf(stderr, "Usage: .indexes ?LIKE-PATTERN?\n"); |
+ rc = 1; |
+ goto meta_command_exit; |
+ }else{ |
+ zSql = sqlite3_mprintf( |
+ "SELECT name FROM sqlite_master" |
+ " WHERE type='index'" |
+ " AND tbl_name LIKE ?1"); |
+ } |
+ for(ii=0; zSql && sqlite3_step(pStmt)==SQLITE_ROW; ii++){ |
const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1); |
- if( zDbName==0 || strcmp(zDbName,"main")==0 ) continue; |
- if( strcmp(zDbName,"temp")==0 ){ |
+ if( zDbName==0 || ii==0 ) continue; |
+ if( c=='t' ){ |
zSql = sqlite3_mprintf( |
"%z UNION ALL " |
- "SELECT 'temp.' || name FROM sqlite_temp_master" |
+ "SELECT '%q.' || name FROM \"%w\".sqlite_master" |
" WHERE type IN ('table','view')" |
" AND name NOT LIKE 'sqlite_%%'" |
- " AND name LIKE ?1", zSql); |
+ " AND name LIKE ?1", zSql, zDbName, zDbName); |
}else{ |
zSql = sqlite3_mprintf( |
"%z UNION ALL " |
"SELECT '%q.' || name FROM \"%w\".sqlite_master" |
- " WHERE type IN ('table','view')" |
- " AND name NOT LIKE 'sqlite_%%'" |
- " AND name LIKE ?1", zSql, zDbName, zDbName); |
+ " WHERE type='index'" |
+ " AND tbl_name LIKE ?1", zSql, zDbName, zDbName); |
} |
} |
rc = sqlite3_finalize(pStmt); |
@@ -3910,6 +5038,21 @@ static int do_meta_command(char *zLine, ShellState *p){ |
sqlite3_free(azResult); |
}else |
+ /* Begin redirecting output to the file "testcase-out.txt" */ |
+ if( c=='t' && strcmp(azArg[0],"testcase")==0 ){ |
+ output_reset(p); |
+ p->out = output_file_open("testcase-out.txt"); |
+ if( p->out==0 ){ |
+ raw_printf(stderr, "Error: cannot open 'testcase-out.txt'\n"); |
+ } |
+ if( nArg>=2 ){ |
+ sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", azArg[1]); |
+ }else{ |
+ sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?"); |
+ } |
+ }else |
+ |
+#ifndef SQLITE_UNTESTABLE |
if( c=='t' && n>=8 && strncmp(azArg[0], "testctrl", n)==0 && nArg>=2 ){ |
static const struct { |
const char *zCtrlName; /* Name of a test-control option */ |
@@ -3959,9 +5102,9 @@ static int do_meta_command(char *zLine, ShellState *p){ |
/* sqlite3_test_control(int, db, int) */ |
case SQLITE_TESTCTRL_OPTIMIZATIONS: |
- case SQLITE_TESTCTRL_RESERVE: |
+ case SQLITE_TESTCTRL_RESERVE: |
if( nArg==3 ){ |
- int opt = (int)strtol(azArg[2], 0, 0); |
+ int opt = (int)strtol(azArg[2], 0, 0); |
rc2 = sqlite3_test_control(testctrl, p->db, opt); |
raw_printf(p->out, "%d (0x%08x)\n", rc2, rc2); |
} else { |
@@ -3985,7 +5128,7 @@ static int do_meta_command(char *zLine, ShellState *p){ |
break; |
/* sqlite3_test_control(int, uint) */ |
- case SQLITE_TESTCTRL_PENDING_BYTE: |
+ case SQLITE_TESTCTRL_PENDING_BYTE: |
if( nArg==3 ){ |
unsigned int opt = (unsigned int)integerValue(azArg[2]); |
rc2 = sqlite3_test_control(testctrl, opt); |
@@ -3995,13 +5138,13 @@ static int do_meta_command(char *zLine, ShellState *p){ |
" int option\n", azArg[1]); |
} |
break; |
- |
+ |
/* sqlite3_test_control(int, int) */ |
- case SQLITE_TESTCTRL_ASSERT: |
- case SQLITE_TESTCTRL_ALWAYS: |
- case SQLITE_TESTCTRL_NEVER_CORRUPT: |
+ case SQLITE_TESTCTRL_ASSERT: |
+ case SQLITE_TESTCTRL_ALWAYS: |
+ case SQLITE_TESTCTRL_NEVER_CORRUPT: |
if( nArg==3 ){ |
- int opt = booleanValue(azArg[2]); |
+ int opt = booleanValue(azArg[2]); |
rc2 = sqlite3_test_control(testctrl, opt); |
raw_printf(p->out, "%d (0x%08x)\n", rc2, rc2); |
} else { |
@@ -4012,9 +5155,9 @@ static int do_meta_command(char *zLine, ShellState *p){ |
/* sqlite3_test_control(int, char *) */ |
#ifdef SQLITE_N_KEYWORD |
- case SQLITE_TESTCTRL_ISKEYWORD: |
+ case SQLITE_TESTCTRL_ISKEYWORD: |
if( nArg==3 ){ |
- const char *opt = azArg[2]; |
+ const char *opt = azArg[2]; |
rc2 = sqlite3_test_control(testctrl, opt); |
raw_printf(p->out, "%d (0x%08x)\n", rc2, rc2); |
} else { |
@@ -4027,7 +5170,7 @@ static int do_meta_command(char *zLine, ShellState *p){ |
case SQLITE_TESTCTRL_IMPOSTER: |
if( nArg==5 ){ |
- rc2 = sqlite3_test_control(testctrl, p->db, |
+ rc2 = sqlite3_test_control(testctrl, p->db, |
azArg[2], |
integerValue(azArg[3]), |
integerValue(azArg[4])); |
@@ -4037,10 +5180,10 @@ static int do_meta_command(char *zLine, ShellState *p){ |
} |
break; |
- case SQLITE_TESTCTRL_BITVEC_TEST: |
- case SQLITE_TESTCTRL_FAULT_INSTALL: |
- case SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS: |
- case SQLITE_TESTCTRL_SCRATCHMALLOC: |
+ case SQLITE_TESTCTRL_BITVEC_TEST: |
+ case SQLITE_TESTCTRL_FAULT_INSTALL: |
+ case SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS: |
+ case SQLITE_TESTCTRL_SCRATCHMALLOC: |
default: |
utf8_printf(stderr, |
"Error: CLI support for testctrl %s not implemented\n", |
@@ -4054,7 +5197,7 @@ static int do_meta_command(char *zLine, ShellState *p){ |
open_db(p, 0); |
sqlite3_busy_timeout(p->db, nArg>=2 ? (int)integerValue(azArg[1]) : 0); |
}else |
- |
+ |
if( c=='t' && n>=5 && strncmp(azArg[0], "timer", n)==0 ){ |
if( nArg==2 ){ |
enableTimer = booleanValue(azArg[1]); |
@@ -4067,7 +5210,7 @@ static int do_meta_command(char *zLine, ShellState *p){ |
rc = 1; |
} |
}else |
- |
+ |
if( c=='t' && strncmp(azArg[0], "trace", n)==0 ){ |
open_db(p, 0); |
if( nArg!=2 ){ |
@@ -4079,12 +5222,13 @@ static int do_meta_command(char *zLine, ShellState *p){ |
p->traceOut = output_file_open(azArg[1]); |
#if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT) |
if( p->traceOut==0 ){ |
- sqlite3_trace(p->db, 0, 0); |
+ sqlite3_trace_v2(p->db, 0, 0, 0); |
}else{ |
- sqlite3_trace(p->db, sql_trace_callback, p->traceOut); |
+ sqlite3_trace_v2(p->db, SQLITE_TRACE_STMT, sql_trace_callback,p->traceOut); |
} |
#endif |
}else |
+#endif /* !defined(SQLITE_UNTESTABLE) */ |
#if SQLITE_USER_AUTHENTICATION |
if( c=='u' && strncmp(azArg[0], "user", n)==0 ){ |
@@ -4147,7 +5291,7 @@ static int do_meta_command(char *zLine, ShellState *p){ |
raw_printf(stderr, "Usage: .user login|add|edit|delete ...\n"); |
rc = 1; |
goto meta_command_exit; |
- } |
+ } |
}else |
#endif /* SQLITE_USER_AUTHENTICATION */ |
@@ -4158,7 +5302,7 @@ static int do_meta_command(char *zLine, ShellState *p){ |
if( c=='v' && strncmp(azArg[0], "vfsinfo", n)==0 ){ |
const char *zDbName = nArg==2 ? azArg[1] : "main"; |
- sqlite3_vfs *pVfs; |
+ sqlite3_vfs *pVfs = 0; |
if( p->db ){ |
sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs); |
if( pVfs ){ |
@@ -4170,6 +5314,24 @@ static int do_meta_command(char *zLine, ShellState *p){ |
} |
}else |
+ if( c=='v' && strncmp(azArg[0], "vfslist", n)==0 ){ |
+ sqlite3_vfs *pVfs; |
+ sqlite3_vfs *pCurrent = 0; |
+ if( p->db ){ |
+ sqlite3_file_control(p->db, "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent); |
+ } |
+ for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){ |
+ utf8_printf(p->out, "vfs.zName = \"%s\"%s\n", pVfs->zName, |
+ pVfs==pCurrent ? " <--- CURRENT" : ""); |
+ raw_printf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion); |
+ raw_printf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile); |
+ raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname); |
+ if( pVfs->pNext ){ |
+ raw_printf(p->out, "-----------------------------------\n"); |
+ } |
+ } |
+ }else |
+ |
if( c=='v' && strncmp(azArg[0], "vfsname", n)==0 ){ |
const char *zDbName = nArg==2 ? azArg[1] : "main"; |
char *zVfsName = 0; |
@@ -4184,7 +5346,6 @@ static int do_meta_command(char *zLine, ShellState *p){ |
#if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_WHERETRACE) |
if( c=='w' && strncmp(azArg[0], "wheretrace", n)==0 ){ |
- extern int sqlite3WhereTrace; |
sqlite3WhereTrace = nArg>=2 ? booleanValue(azArg[1]) : 0xff; |
}else |
#endif |
@@ -4277,6 +5438,42 @@ static int line_is_complete(char *zSql, int nSql){ |
} |
/* |
+** Run a single line of SQL |
+*/ |
+static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ |
+ int rc; |
+ char *zErrMsg = 0; |
+ |
+ open_db(p, 0); |
+ if( p->backslashOn ) resolve_backslashes(zSql); |
+ BEGIN_TIMER; |
+ rc = shell_exec(p->db, zSql, shell_callback, p, &zErrMsg); |
+ END_TIMER; |
+ if( rc || zErrMsg ){ |
+ char zPrefix[100]; |
+ if( in!=0 || !stdin_is_interactive ){ |
+ sqlite3_snprintf(sizeof(zPrefix), zPrefix, |
+ "Error: near line %d:", startline); |
+ }else{ |
+ sqlite3_snprintf(sizeof(zPrefix), zPrefix, "Error:"); |
+ } |
+ if( zErrMsg!=0 ){ |
+ utf8_printf(stderr, "%s %s\n", zPrefix, zErrMsg); |
+ sqlite3_free(zErrMsg); |
+ zErrMsg = 0; |
+ }else{ |
+ utf8_printf(stderr, "%s %s\n", zPrefix, sqlite3_errmsg(p->db)); |
+ } |
+ return 1; |
+ }else if( p->countChanges ){ |
+ raw_printf(p->out, "changes: %3d total_changes: %d\n", |
+ sqlite3_changes(p->db), sqlite3_total_changes(p->db)); |
+ } |
+ return 0; |
+} |
+ |
+ |
+/* |
** 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 |
@@ -4292,7 +5489,6 @@ static int process_input(ShellState *p, FILE *in){ |
int nSql = 0; /* Bytes of zSql[] used */ |
int nAlloc = 0; /* Allocated zSql[] space */ |
int nSqlPrior = 0; /* Bytes of zSql[] used by prior line */ |
- char *zErrMsg; /* Error message returned */ |
int rc; /* Error code */ |
int errCnt = 0; /* Number of errors seen */ |
int lineno = 0; /* Current line number */ |
@@ -4303,7 +5499,7 @@ static int process_input(ShellState *p, FILE *in){ |
zLine = one_input_line(in, zLine, nSql>0); |
if( zLine==0 ){ |
/* End of input */ |
- if( stdin_is_interactive ) printf("\n"); |
+ if( in==0 && stdin_is_interactive ) printf("\n"); |
break; |
} |
if( seenInterrupt ){ |
@@ -4352,32 +5548,7 @@ static int process_input(ShellState *p, FILE *in){ |
} |
if( nSql && line_contains_semicolon(&zSql[nSqlPrior], nSql-nSqlPrior) |
&& sqlite3_complete(zSql) ){ |
- p->cnt = 0; |
- open_db(p, 0); |
- if( p->backslashOn ) resolve_backslashes(zSql); |
- BEGIN_TIMER; |
- rc = shell_exec(p->db, zSql, shell_callback, p, &zErrMsg); |
- END_TIMER; |
- if( rc || zErrMsg ){ |
- char zPrefix[100]; |
- if( in!=0 || !stdin_is_interactive ){ |
- sqlite3_snprintf(sizeof(zPrefix), zPrefix, |
- "Error: near line %d:", startline); |
- }else{ |
- sqlite3_snprintf(sizeof(zPrefix), zPrefix, "Error:"); |
- } |
- if( zErrMsg!=0 ){ |
- utf8_printf(stderr, "%s %s\n", zPrefix, zErrMsg); |
- sqlite3_free(zErrMsg); |
- zErrMsg = 0; |
- }else{ |
- utf8_printf(stderr, "%s %s\n", zPrefix, sqlite3_errmsg(p->db)); |
- } |
- errCnt++; |
- }else if( p->countChanges ){ |
- raw_printf(p->out, "changes: %3d total_changes: %d\n", |
- sqlite3_changes(p->db), sqlite3_total_changes(p->db)); |
- } |
+ errCnt += runOneSqlLine(p, zSql, in, startline); |
nSql = 0; |
if( p->outCount ){ |
output_reset(p); |
@@ -4388,11 +5559,8 @@ static int process_input(ShellState *p, FILE *in){ |
nSql = 0; |
} |
} |
- if( nSql ){ |
- if( !_all_whitespace(zSql) ){ |
- utf8_printf(stderr, "Error: incomplete SQL: %s\n", zSql); |
- errCnt++; |
- } |
+ if( nSql && !_all_whitespace(zSql) ){ |
+ runOneSqlLine(p, zSql, in, startline); |
} |
free(zSql); |
free(zLine); |
@@ -4403,8 +5571,13 @@ static int process_input(ShellState *p, FILE *in){ |
** Return a pathname which is the user's home directory. A |
** 0 return indicates an error of some kind. |
*/ |
-static char *find_home_dir(void){ |
+static char *find_home_dir(int clearFlag){ |
static char *home_dir = NULL; |
+ if( clearFlag ){ |
+ free(home_dir); |
+ home_dir = 0; |
+ return 0; |
+ } |
if( home_dir ) return home_dir; |
#if !defined(_WIN32) && !defined(WIN32) && !defined(_WIN32_WCE) \ |
@@ -4479,7 +5652,7 @@ static void process_sqliterc( |
FILE *in = NULL; |
if (sqliterc == NULL) { |
- home_dir = find_home_dir(); |
+ home_dir = find_home_dir(0); |
if( home_dir==0 ){ |
raw_printf(stderr, "-- warning: cannot find home directory;" |
" cannot read ~/.sqliterc\n"); |
@@ -4503,7 +5676,7 @@ static void process_sqliterc( |
/* |
** Show available command line options |
*/ |
-static const char zOptions[] = |
+static const char zOptions[] = |
" -ascii set output mode to 'ascii'\n" |
" -bail stop after hitting an error\n" |
" -batch force batch I/O\n" |
@@ -4540,7 +5713,7 @@ static const char zOptions[] = |
; |
static void usage(int showDetail){ |
utf8_printf(stderr, |
- "Usage: %s [OPTIONS] FILENAME [SQL]\n" |
+ "Usage: %s [OPTIONS] FILENAME [SQL]\n" |
"FILENAME is the name of an SQLite database. A new database is created\n" |
"if the file does not previously exist.\n", Argv0); |
if( showDetail ){ |
@@ -4556,7 +5729,8 @@ static void usage(int showDetail){ |
*/ |
static void main_init(ShellState *data) { |
memset(data, 0, sizeof(*data)); |
- data->mode = MODE_List; |
+ data->normalMode = data->cMode = data->mode = MODE_List; |
+ data->autoExplain = 1; |
memcpy(data->colSeparator,SEP_Column, 2); |
memcpy(data->rowSeparator,SEP_Row, 2); |
data->showHeader = 0; |
@@ -4601,7 +5775,20 @@ static char *cmdline_option_value(int argc, char **argv, int i){ |
return argv[i]; |
} |
+#ifndef SQLITE_SHELL_IS_UTF8 |
+# if (defined(_WIN32) || defined(WIN32)) && defined(_MSC_VER) |
+# define SQLITE_SHELL_IS_UTF8 (0) |
+# else |
+# define SQLITE_SHELL_IS_UTF8 (1) |
+# endif |
+#endif |
+ |
+#if SQLITE_SHELL_IS_UTF8 |
int SQLITE_CDECL main(int argc, char **argv){ |
+#else |
+int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ |
+ char **argv; |
+#endif |
char *zErrMsg = 0; |
ShellState data; |
const char *zInitFile = 0; |
@@ -4612,6 +5799,11 @@ int SQLITE_CDECL main(int argc, char **argv){ |
int nCmd = 0; |
char **azCmd = 0; |
+ setBinaryMode(stdin, 0); |
+ setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */ |
+ stdin_is_interactive = isatty(0); |
+ stdout_is_console = isatty(1); |
+ |
#if USE_SYSTEM_SQLITE+0!=1 |
if( strcmp(sqlite3_sourceid(),SQLITE_SOURCE_ID)!=0 ){ |
utf8_printf(stderr, "SQLite header and source version mismatch\n%s\n%s\n", |
@@ -4619,12 +5811,24 @@ int SQLITE_CDECL main(int argc, char **argv){ |
exit(1); |
} |
#endif |
- setBinaryMode(stdin); |
- setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */ |
- Argv0 = argv[0]; |
main_init(&data); |
- stdin_is_interactive = isatty(0); |
- stdout_is_console = isatty(1); |
+#if !SQLITE_SHELL_IS_UTF8 |
+ sqlite3_initialize(); |
+ argv = sqlite3_malloc64(sizeof(argv[0])*argc); |
+ if( argv==0 ){ |
+ raw_printf(stderr, "out of memory\n"); |
+ exit(1); |
+ } |
+ for(i=0; i<argc; i++){ |
+ argv[i] = sqlite3_win32_unicode_to_utf8(wargv[i]); |
+ if( argv[i]==0 ){ |
+ raw_printf(stderr, "out of memory\n"); |
+ exit(1); |
+ } |
+ } |
+#endif |
+ assert( argc>=1 && argv && argv[0] ); |
+ Argv0 = argv[0]; |
/* Make sure we have a valid signal handler early, before anything |
** else is done. |
@@ -4690,7 +5894,7 @@ int SQLITE_CDECL main(int argc, char **argv){ |
zInitFile = cmdline_option_value(argc, argv, ++i); |
}else if( strcmp(z,"-batch")==0 ){ |
/* Need to check for batch mode here to so we can avoid printing |
- ** informational messages (like from process_sqliterc) before |
+ ** informational messages (like from process_sqliterc) before |
** we do the actual processing of arguments later in a second pass. |
*/ |
stdin_is_interactive = 0; |
@@ -4703,6 +5907,8 @@ int SQLITE_CDECL main(int argc, char **argv){ |
szHeap = integerValue(zSize); |
if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000; |
sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64); |
+#else |
+ (void)cmdline_option_value(argc, argv, ++i); |
#endif |
}else if( strcmp(z,"-scratch")==0 ){ |
int n, sz; |
@@ -4831,6 +6037,8 @@ int SQLITE_CDECL main(int argc, char **argv){ |
data.echoOn = 1; |
}else if( strcmp(z,"-eqp")==0 ){ |
data.autoEQP = 1; |
+ }else if( strcmp(z,"-eqpfull")==0 ){ |
+ data.autoEQP = 2; |
}else if( strcmp(z,"-stats")==0 ){ |
data.statsOn = 1; |
}else if( strcmp(z,"-scanstats")==0 ){ |
@@ -4899,6 +6107,7 @@ int SQLITE_CDECL main(int argc, char **argv){ |
raw_printf(stderr,"Use -help for a list of options.\n"); |
return 1; |
} |
+ data.cMode = data.mode; |
} |
if( !readStdin ){ |
@@ -4941,7 +6150,7 @@ int SQLITE_CDECL main(int argc, char **argv){ |
printf(".\nUse \".open FILENAME\" to reopen on a " |
"persistent database.\n"); |
} |
- zHome = find_home_dir(); |
+ zHome = find_home_dir(0); |
if( zHome ){ |
nHistory = strlen30(zHome) + 20; |
if( (zHistory = malloc(nHistory))!=0 ){ |
@@ -4961,8 +6170,14 @@ int SQLITE_CDECL main(int argc, char **argv){ |
} |
set_table_name(&data, 0); |
if( data.db ){ |
+ session_close_all(&data); |
sqlite3_close(data.db); |
} |
- sqlite3_free(data.zFreeOnClose); |
+ sqlite3_free(data.zFreeOnClose); |
+ find_home_dir(1); |
+#if !SQLITE_SHELL_IS_UTF8 |
+ for(i=0; i<argc; i++) sqlite3_free(argv[i]); |
+ sqlite3_free(argv); |
+#endif |
return rc; |
} |