blob: 4949115c00aa831c318dea59137ff8a2620cafc3 [file] [log] [blame] [edit]
/*
** 2001 September 15
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file contains code to implement the "sqlite3" or "sqlite3x"
** command line utilities for accessing SQLite databases.
*/
#if (defined(_WIN32) || defined(WIN32)) && !defined(_CRT_SECURE_NO_WARNINGS)
/* This needs to come before any includes for MSVC compiler */
# define _CRT_SECURE_NO_WARNINGS
#endif
/*
** Optionally #include a user-defined header, whereby compilation options
** may be set prior to where they take effect, but after platform setup.
** If SQLITE_CUSTOM_INCLUDE=? is defined, its value names the #include
** file. Note that this macro has a like effect on sqlite3.c compilation.
*/
#define SHELL_STRINGIFY_(f) #f
#define SHELL_STRINGIFY(f) SHELL_STRINGIFY_(f)
#ifdef SQLITE_CUSTOM_INCLUDE
# include SHELL_STRINGIFY(SQLITE_CUSTOM_INCLUDE)
#endif
/*
** Determine if we are dealing with WinRT, which provides only a subset of
** the full Win32 API.
*/
#if !defined(SQLITE_OS_WINRT)
# define SQLITE_OS_WINRT 0
#endif
/*
** If SQLITE_SHELL_FIDDLE is defined then the shell is modified
** somewhat for use as a WASM module in a web browser. This flag
** should only be used when building the "fiddle" web application, as
** the browser-mode build has much different user input requirements
** and this build mode rewires the user input subsystem to account for
** that.
*/
/*
** Warning pragmas copied from msvc.h in the core.
*/
#if defined(_MSC_VER)
#pragma warning(disable : 4054)
#pragma warning(disable : 4055)
#pragma warning(disable : 4100)
#pragma warning(disable : 4127)
#pragma warning(disable : 4130)
#pragma warning(disable : 4152)
#pragma warning(disable : 4189)
#pragma warning(disable : 4206)
#pragma warning(disable : 4210)
#pragma warning(disable : 4232)
#pragma warning(disable : 4244)
#pragma warning(disable : 4305)
#pragma warning(disable : 4306)
#pragma warning(disable : 4324)
#pragma warning(disable : 4702)
#pragma warning(disable : 4706)
#endif /* defined(_MSC_VER) */
/*
** No support for loadable extensions in VxWorks.
*/
#if (defined(__RTP__) || defined(_WRS_KERNEL)) && !SQLITE_OMIT_LOAD_EXTENSION
# define SQLITE_OMIT_LOAD_EXTENSION 1
#endif
/*
** Enable large-file support for fopen() and friends on unix.
*/
#ifndef SQLITE_DISABLE_LFS
# define _LARGE_FILE 1
# ifndef _FILE_OFFSET_BITS
# define _FILE_OFFSET_BITS 64
# endif
# define _LARGEFILE_SOURCE 1
#endif
#if defined(SQLITE_SHELL_FIDDLE) && !defined(_POSIX_SOURCE)
/*
** emcc requires _POSIX_SOURCE (or one of several similar defines)
** to expose strdup().
*/
# define _POSIX_SOURCE
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <math.h>
#include <stdint.h>
#include "sqlite3.h"
typedef sqlite3_int64 i64;
typedef sqlite3_uint64 u64;
typedef uint32_t u32;
typedef unsigned char u8;
typedef unsigned short u16;
#if SQLITE_USER_AUTHENTICATION
# include "sqlite3userauth.h"
#endif
#include <ctype.h>
#include <stdarg.h>
#if !defined(_WIN32) && !defined(WIN32)
# include <signal.h>
# if !defined(__RTP__) && !defined(_WRS_KERNEL) && !defined(SQLITE_WASI)
# include <pwd.h>
# endif
#endif
#if (!defined(_WIN32) && !defined(WIN32)) || defined(__MINGW32__)
# include <unistd.h>
# include <dirent.h>
# define GETPID getpid
# if defined(__MINGW32__)
# define DIRENT dirent
# ifndef S_ISLNK
# define S_ISLNK(mode) (0)
# endif
# endif
#else
# define GETPID (int)GetCurrentProcessId
#endif
#include <sys/types.h>
#include <sys/stat.h>
#if HAVE_READLINE
# include <readline/readline.h>
# include <readline/history.h>
#endif
#if HAVE_EDITLINE
# include <editline/readline.h>
#endif
#if HAVE_EDITLINE || HAVE_READLINE
# define shell_add_history(X) add_history(X)
# define shell_read_history(X) read_history(X)
# define shell_write_history(X) write_history(X)
# define shell_stifle_history(X) stifle_history(X)
# define shell_readline(X) readline(X)
#elif HAVE_LINENOISE
# include "linenoise.h"
# define shell_add_history(X) linenoiseHistoryAdd(X)
# define shell_read_history(X) linenoiseHistoryLoad(X)
# define shell_write_history(X) linenoiseHistorySave(X)
# define shell_stifle_history(X) linenoiseHistorySetMaxLen(X)
# define shell_readline(X) linenoise(X)
#else
# define shell_read_history(X)
# define shell_write_history(X)
# define shell_stifle_history(X)
# define SHELL_USE_LOCAL_GETLINE 1
#endif
#ifndef deliberate_fall_through
/* Quiet some compilers about some of our intentional code. */
# if defined(GCC_VERSION) && GCC_VERSION>=7000000
# define deliberate_fall_through __attribute__((fallthrough));
# else
# define deliberate_fall_through
# endif
#endif
#if defined(_WIN32) || defined(WIN32)
# if SQLITE_OS_WINRT
# define SQLITE_OMIT_POPEN 1
# else
# include <io.h>
# include <fcntl.h>
# define isatty(h) _isatty(h)
# ifndef access
# define access(f,m) _access((f),(m))
# endif
# ifndef unlink
# define unlink _unlink
# endif
# ifndef strdup
# define strdup _strdup
# endif
# undef popen
# define popen _popen
# undef pclose
# define pclose _pclose
# endif
#else
/* Make sure isatty() has a prototype. */
extern int isatty(int);
# if !defined(__RTP__) && !defined(_WRS_KERNEL) && !defined(SQLITE_WASI)
/* popen and pclose are not C89 functions and so are
** sometimes omitted from the <stdio.h> header */
extern FILE *popen(const char*,const char*);
extern int pclose(FILE*);
# else
# define SQLITE_OMIT_POPEN 1
# endif
#endif
#if defined(_WIN32_WCE)
/* Windows CE (arm-wince-mingw32ce-gcc) does not provide isatty()
* thus we always assume that we have a console. That can be
* overridden with the -batch command line option.
*/
#define isatty(x) 1
#endif
/* ctype macros that work with signed characters */
#define IsSpace(X) isspace((unsigned char)(X))
#define IsDigit(X) isdigit((unsigned char)(X))
#define ToLower(X) (char)tolower((unsigned char)(X))
#if defined(_WIN32) || defined(WIN32)
#if SQLITE_OS_WINRT
#include <intrin.h>
#endif
#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#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
/* Get the shell extension interfaces and structs. (ShellExState ...) */
INCLUDE shext_linkage.h
/* Forward declarations for use by resmanage.c package. */
typedef struct ShellText ShellText;
static void freeText(ShellText *p);
#define SHELL_MANAGE_TEXT /* Cause text_holder() to be available. */
/* Get resource management package, supporting OOM and safe mode exits. */
INCLUDE resmanage.c
/* For an embedded shell, allow the 3 standard streams to be specified.
** If used, these names will have to refer to something globally reachable
** from the same thread which called the shell's main().
**/
#ifndef STD_IN
# define STD_IN stdin
#endif
#ifndef STD_OUT
# define STD_OUT stdout
#endif
#ifndef STD_ERR
# define STD_ERR stderr
#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
** rendering quoted strings that contain \n characters). The following
** routines take care of that.
*/
#if (defined(_WIN32) || defined(WIN32)) && !SQLITE_OS_WINRT
static void setBinaryMode(FILE *file, int isOutput){
if( isOutput ) fflush(file);
_setmode(_fileno(file), _O_BINARY);
}
static void setTextMode(FILE *file, int isOutput){
if( isOutput ) fflush(file);
_setmode(_fileno(file), _O_TEXT);
}
#else
# define setBinaryMode(X,Y)
# define setTextMode(X,Y)
#endif
/* Use a shorter form of this ubiquitously used, (varargs) API: */
#define smprintf sqlite3_mprintf
/* Forward declaration of the number of built-in dot commands (as compiled) */
static unsigned numCommands;
/* True if the timer is enabled */
static int enableTimer = 0;
/* A version of strcmp() that works with NULL values */
static int cli_strcmp(const char *a, const char *b){
if( a==0 ) a = "";
if( b==0 ) b = "";
return strcmp(a,b);
}
static int cli_strncmp(const char *a, const char *b, size_t n){
if( a==0 ) a = "";
if( b==0 ) b = "";
return strncmp(a,b,n);
}
/* Return the current wall-clock time */
static sqlite3_int64 timeOfDay(void){
static sqlite3_vfs *clockVfs = 0;
sqlite3_int64 t;
if( clockVfs==0 ) clockVfs = sqlite3_vfs_find(0);
if( clockVfs==0 ) return 0; /* Never actually happens */
if( clockVfs->iVersion>=2 && clockVfs->xCurrentTimeInt64!=0 ){
clockVfs->xCurrentTimeInt64(clockVfs, &t);
}else{
double r;
clockVfs->xCurrentTime(clockVfs, &r);
t = (sqlite3_int64)(r*86400000.0);
}
return t;
}
#if !defined(_WIN32) && !defined(WIN32) && !defined(__minux)
#include <sys/time.h>
#include <sys/resource.h>
/* VxWorks does not support getrusage() as far as we can determine */
#if defined(_WRS_KERNEL) || defined(__RTP__)
struct rusage {
struct timeval ru_utime; /* user CPU time used */
struct timeval ru_stime; /* system CPU time used */
};
#define getrusage(A,B) memset(B,0,sizeof(*B))
#endif
/* Saved resource information for the beginning of an operation */
static struct rusage sBegin; /* CPU time at start */
static sqlite3_int64 iBegin; /* Wall-clock time at start */
/*
** Begin timing an operation
*/
static void beginTimer(void){
if( enableTimer ){
getrusage(RUSAGE_SELF, &sBegin);
iBegin = timeOfDay();
}
}
/* 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 +
(double)(pEnd->tv_sec - pStart->tv_sec);
}
/*
** Print the timing results.
*/
static void endTimer(void){
if( enableTimer ){
sqlite3_int64 iEnd = timeOfDay();
struct rusage sEnd;
getrusage(RUSAGE_SELF, &sEnd);
fprintf(STD_OUT, "Run Time: real %.3f user %f sys %f\n",
(iEnd - iBegin)*0.001,
timeDiff(&sBegin.ru_utime, &sEnd.ru_utime),
timeDiff(&sBegin.ru_stime, &sEnd.ru_stime));
}
}
#define BEGIN_TIMER beginTimer()
#define END_TIMER endTimer()
#define HAS_TIMER 1
#elif (defined(_WIN32) || defined(WIN32))
/* Saved resource information for the beginning of an operation */
static HANDLE hProcess;
static FILETIME ftKernelBegin;
static FILETIME ftUserBegin;
static sqlite3_int64 ftWallBegin;
typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME,
LPFILETIME, LPFILETIME);
static GETPROCTIMES getProcessTimesAddr = NULL;
/*
** Check to see if we have timer support. Return 1 if necessary
** support found (or found previously).
*/
static int hasTimer(void){
if( getProcessTimesAddr ){
return 1;
} else {
#if !SQLITE_OS_WINRT
/* GetProcessTimes() isn't supported in WIN95 and some other Windows
** versions. See if the version we are running on has it, and if it
** does, save off a pointer to it and the current process handle.
*/
hProcess = GetCurrentProcess();
if( hProcess ){
HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll"));
if( NULL != hinstLib ){
getProcessTimesAddr =
(GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes");
if( NULL != getProcessTimesAddr ){
return 1;
}
FreeLibrary(hinstLib);
}
}
#endif
}
return 0;
}
/*
** Begin timing an operation
*/
static void beginTimer(void){
if( enableTimer && getProcessTimesAddr ){
FILETIME ftCreation, ftExit;
getProcessTimesAddr(hProcess,&ftCreation,&ftExit,
&ftKernelBegin,&ftUserBegin);
ftWallBegin = timeOfDay();
}
}
/* Return the difference of two FILETIME structs in seconds */
static double timeDiff(FILETIME *pStart, FILETIME *pEnd){
sqlite_int64 i64Start = *((sqlite_int64 *) pStart);
sqlite_int64 i64End = *((sqlite_int64 *) pEnd);
return (double) ((i64End - i64Start) / 10000000.0);
}
/*
** Print the timing results.
*/
static void endTimer(void){
if( enableTimer && getProcessTimesAddr){
FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd;
sqlite3_int64 ftWallEnd = timeOfDay();
getProcessTimesAddr(hProcess,&ftCreation,&ftExit,&ftKernelEnd,&ftUserEnd);
fprintf(STD_OUT, "Run Time: real %.3f user %f sys %f\n",
(ftWallEnd - ftWallBegin)*0.001,
timeDiff(&ftUserBegin, &ftUserEnd),
timeDiff(&ftKernelBegin, &ftKernelEnd));
}
}
#define BEGIN_TIMER beginTimer()
#define END_TIMER endTimer()
#define HAS_TIMER hasTimer()
#else
#define BEGIN_TIMER
#define END_TIMER
#define HAS_TIMER 0
#endif
/*
** Used to prevent warnings about unused parameters
*/
#define UNUSED_PARAMETER(x) (void)(x)
#ifdef SQLITE_DEBUG
# define CHECK_RETURN_EQUAL(val, func) assert((val)==(func))
#else
# define CHECK_RETURN_EQUAL(val, func) func
#endif
/*
** Number of elements in an array and one past its end
*/
#define ArraySize(X) (int)(sizeof(X)/sizeof(X[0]))
#define PastArray(X) ((X)+ArraySize(X))
/*
** If the following flag is set, then command execution stops
** at an error if we are not interactive.
*/
static int bail_on_error = 0;
/*
** Treat stdin as an interactive input if the following variable
** is true. Otherwise, assume stdin is connected to a file or pipe.
*/
static int stdin_is_interactive = 1;
#if (defined(_WIN32) || defined(WIN32)) && SHELL_USE_LOCAL_GETLINE \
&& !defined(SHELL_OMIT_WIN_UTF8)
# define SHELL_WIN_UTF8_OPT 1
#else
# define SHELL_WIN_UTF8_OPT 0
#endif
#if SHELL_WIN_UTF8_OPT
/*
** Setup console for UTF-8 input/output when following variable true.
*/
static int console_utf8 = 0;
#endif
/*
** On Windows systems we have to know if standard output is a console
** in order to translate UTF-8 into MBCS. The following variable is
** true if translation is required.
*/
static int stdout_is_console = 1;
/*
** Disable certain features for FIDDLE build.
*/
#ifdef SQLITE_SHELL_FIDDLE
# undef SQLITE_OMIT_DYNAPROMPT
# undef SHELL_OMIT_LOAD_EXTENSION
# define SQLITE_OMIT_DYNAPROMPT 1
# define SHELL_OMIT_LOAD_EXTENSION 1
#endif
/*
** The following is the open SQLite database. We make a pointer
** to this database a static variable so that it can be accessed
** by the SIGINT handler to interrupt database processing.
*/
static volatile sqlite3 *globalDb = 0;
/* Shorten volatile cast-away for this. */
#define GLOBAL_DB ((sqlite3 *)globalDb)
/*
** Mutex used to access *globalDb from main thread or ^C handler and to
** guard against referenced DB race where it is closed and interrupted.
*/
static sqlite3_mutex *pGlobalDbLock = 0;
/*
** Greater than 0 if an interrupt (Control-C) has been received.
*/
static volatile int seenInterrupt = 0;
/*
** This is the name of our program. It is set in main(), used
** in a number of other places, mostly for error messages.
*/
static char *Argv0;
/* This is variant of the standard-library strncpy() routine with the
** one change that the destination string is always zero-terminated, even
** if there is no zero-terminator in the first n-1 characters of the source
** string.
*/
static char *shell_strncpy(char *dest, const char *src, size_t n){
size_t i;
for(i=0; i<n-1 && src[i]!=0; i++) dest[i] = src[i];
dest[i] = 0;
return dest;
}
/*
** Prompt strings. Initialized in main. Settable with
** .prompt main continue
*/
#define PROMPT_LEN_MAX 20
/* First line prompt. default: "sqlite> " */
static char mainPrompt[PROMPT_LEN_MAX];
/* Continuation prompt. default: " ...> " */
static char continuePrompt[PROMPT_LEN_MAX];
#define SET_MAIN_PROMPT(z) shell_strncpy(mainPrompt,z,PROMPT_LEN_MAX-1)
#define SET_MORE_PROMPT(z) shell_strncpy(continuePrompt,z,PROMPT_LEN_MAX-1);
/* Prompts as ready to be used by shell's input function */
static Prompts shellPrompts = { mainPrompt, continuePrompt };
#ifdef SQLITE_OMIT_DYNAPROMPT
/*
** Optionally disable dynamic continuation prompt.
** Unless disabled, the continuation prompt shows open SQL lexemes if any,
** or open parentheses level if non-zero, or continuation prompt as set.
** This facility interacts with the scanner and process_input() where the
** below 5 macros are used.
*/
# define PROMPTS_UPDATE(ika) /**/
# define CONTINUE_PROMPT_RESET
# define CONTINUE_PROMPT_AWAITS(p,s)
# define CONTINUE_PROMPT_AWAITC(p,c)
# define CONTINUE_PAREN_INCR(p,n)
# define CONTINUE_PROMPT_PSTATE 0
typedef void *t_NoDynaPrompt;
# define SCAN_TRACKER_REFTYPE t_NoDynaPrompt
#else
# define PROMPTS_UPDATE(ikActionable) \
if(ikActionable) dynamicContinuePrompt(); else
# define CONTINUE_PROMPT_RESET \
do {setLexemeOpen(&dynPrompt,0,0); trackParenLevel(&dynPrompt,0);} while(0)
# define CONTINUE_PROMPT_AWAITS(p,s) \
if(p && stdin_is_interactive) setLexemeOpen(p, s, 0)
# define CONTINUE_PROMPT_AWAITC(p,c) \
if(p && stdin_is_interactive) setLexemeOpen(p, 0, c)
# define CONTINUE_PAREN_INCR(p,n) \
if(p && stdin_is_interactive) (trackParenLevel(p,n))
# define CONTINUE_PROMPT_PSTATE (&dynPrompt)
typedef struct DynaPrompt *t_DynaPromptRef;
# define SCAN_TRACKER_REFTYPE t_DynaPromptRef
static struct DynaPrompt {
char dynamicPrompt[PROMPT_LEN_MAX];
char acAwait[2];
int inParenLevel;
char *zScannerAwaits;
} dynPrompt = { {0}, {0}, 0, 0 };
/* Record parenthesis nesting level change, or force level to 0. */
static void trackParenLevel(struct DynaPrompt *p, int ni){
p->inParenLevel += ni;
if( ni==0 ) p->inParenLevel = 0;
p->zScannerAwaits = 0;
}
/* Record that a lexeme is opened, or closed with args==0. */
static void setLexemeOpen(struct DynaPrompt *p, char *s, char c){
if( s!=0 || c==0 ){
p->zScannerAwaits = s;
p->acAwait[0] = 0;
}else{
p->acAwait[0] = c;
p->zScannerAwaits = p->acAwait;
}
}
/* Upon demand, derive the continuation prompt to display. */
static void dynamicContinuePrompt(void){
if( continuePrompt[0]==0
|| (dynPrompt.zScannerAwaits==0 && dynPrompt.inParenLevel == 0) ){
plain_continuation:
shellPrompts.zContinue = continuePrompt;
return;
}
if( dynPrompt.zScannerAwaits ){
size_t ncp = strlen(continuePrompt);
size_t ndp = strlen(dynPrompt.zScannerAwaits);
if( ndp > ncp-3 ) goto plain_continuation;
strcpy(dynPrompt.dynamicPrompt, dynPrompt.zScannerAwaits);
while( ndp<3 ) dynPrompt.dynamicPrompt[ndp++] = ' ';
shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3,
PROMPT_LEN_MAX-4);
}else{
if( dynPrompt.inParenLevel>9 ){
shell_strncpy(dynPrompt.dynamicPrompt, "(..", 4);
}else if( dynPrompt.inParenLevel<0 ){
shell_strncpy(dynPrompt.dynamicPrompt, ")x!", 4);
}else{
shell_strncpy(dynPrompt.dynamicPrompt, "(x.", 4);
dynPrompt.dynamicPrompt[2] = (char)('0'+dynPrompt.inParenLevel);
}
shell_strncpy(dynPrompt.dynamicPrompt+3,continuePrompt+3,PROMPT_LEN_MAX-4);
}
shellPrompts.zContinue = dynPrompt.dynamicPrompt;
}
#endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */
#ifndef SHELL_NOT_CALLABLE
/*
** Provide an atexit() replacement for when shell is called by an application.
*/
typedef void (*terminate_callback_t)(void);
#define MAX_TERM_CALLBACKS 4
static terminate_callback_t terminate_callbacks[MAX_TERM_CALLBACKS];
static int terminate_callback_count = 0;
static void upon_terminate(terminate_callback_t tcb){
assert(terminate_callback_count < MAX_TERM_CALLBACKS);
if( terminate_callback_count<MAX_TERM_CALLBACKS ){
terminate_callbacks[terminate_callback_count++] = tcb;
}
}
static void terminate_actions(void){
while( terminate_callback_count>0 ){
(terminate_callbacks[--terminate_callback_count])();
}
}
#else
# define upon_terminate(x) atexit(x)
# define terminate_actions()
#endif /* !defined(SHELL_NOT_CALLABLE) */
#if SHELL_WIN_UTF8_OPT
/* Following struct is used for -utf8 operation. */
static struct ConsoleState {
int stdinEof; /* EOF has been seen on console input */
int infsMode; /* Input file stream mode upon shell start */
UINT inCodePage; /* Input code page upon shell start */
UINT outCodePage; /* Output code page upon shell start */
HANDLE hConsoleIn; /* Console input handle */
DWORD consoleMode; /* Console mode upon shell start */
} conState = { 0, 0, 0, 0, INVALID_HANDLE_VALUE, 0 };
#ifndef _O_U16TEXT /* For build environments lacking this constant: */
# define _O_U16TEXT 0x20000
#endif
/*
** Prepare console, (if known to be a WIN32 console), for UTF-8
** input (from either typing or suitable paste operations) and for
** UTF-8 rendering. This may "fail" with a message to stderr, where
** the preparation is not done and common "code page" issues occur.
*/
static void console_prepare(void){
HANDLE hCI = GetStdHandle(STD_INPUT_HANDLE);
DWORD consoleMode = 0;
if( isatty(0) && GetFileType(hCI)==FILE_TYPE_CHAR
&& GetConsoleMode( hCI, &consoleMode) ){
if( !IsValidCodePage(CP_UTF8) ){
fprintf(stderr, "Cannot use UTF-8 code page.\n");
console_utf8 = 0;
return;
}
conState.hConsoleIn = hCI;
conState.consoleMode = consoleMode;
conState.inCodePage = GetConsoleCP();
conState.outCodePage = GetConsoleOutputCP();
SetConsoleCP(CP_UTF8);
SetConsoleOutputCP(CP_UTF8);
consoleMode |= ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
SetConsoleMode(conState.hConsoleIn, consoleMode);
conState.infsMode = _setmode(_fileno(stdin), _O_U16TEXT);
console_utf8 = 1;
}else{
console_utf8 = 0;
}
}
/*
** Undo the effects of console_prepare(), if any.
*/
static void SQLITE_CDECL console_restore(void){
if( console_utf8 && conState.inCodePage!=0
&& conState.hConsoleIn!=INVALID_HANDLE_VALUE ){
_setmode(_fileno(stdin), conState.infsMode);
SetConsoleCP(conState.inCodePage);
SetConsoleOutputCP(conState.outCodePage);
SetConsoleMode(conState.hConsoleIn, conState.consoleMode);
/* Avoid multiple calls. */
conState.hConsoleIn = INVALID_HANDLE_VALUE;
conState.consoleMode = 0;
console_utf8 = 0;
}
}
/*
** Collect input like fgets(...) with special provisions for input
** from the Windows console to get around its strange coding issues.
** Defers to plain fgets() when input is not interactive or when the
** startup option, -utf8, has not been provided or taken effect.
*/
static char* utf8_fgets(char *buf, int ncmax, FILE *fin){
if( fin==0 ) fin = stdin;
if( fin==stdin && stdin_is_interactive && console_utf8 ){
# define SQLITE_IALIM 150
wchar_t wbuf[SQLITE_IALIM];
int lend = 0;
int noc = 0;
if( ncmax==0 || conState.stdinEof ) return 0;
buf[0] = 0;
while( noc<ncmax-7-1 && !lend ){
/* There is room for at least 2 more characters and a 0-terminator. */
int na = (ncmax > SQLITE_IALIM*4+1 + noc)
? SQLITE_IALIM : (ncmax-1 - noc)/4;
# undef SQLITE_IALIM
DWORD nbr = 0;
BOOL bRC = ReadConsoleW(conState.hConsoleIn, wbuf, na, &nbr, 0);
if( !bRC || (noc==0 && nbr==0) ) return 0;
if( nbr > 0 ){
int nmb = WideCharToMultiByte(CP_UTF8,WC_COMPOSITECHECK|WC_DEFAULTCHAR,
wbuf,nbr,0,0,0,0);
if( nmb !=0 && noc+nmb <= ncmax ){
int iseg = noc;
nmb = WideCharToMultiByte(CP_UTF8,WC_COMPOSITECHECK|WC_DEFAULTCHAR,
wbuf,nbr,buf+noc,nmb,0,0);
noc += nmb;
/* Fixup line-ends as coded by Windows for CR (or "Enter".)*/
if( noc > 0 ){
if( buf[noc-1]=='\n' ){
lend = 1;
if( noc > 1 && buf[noc-2]=='\r' ){
buf[noc-2] = '\n';
--noc;
}
}
}
/* Check for ^Z (anywhere in line) too. */
while( iseg < noc ){
if( buf[iseg]==0x1a ){
conState.stdinEof = 1;
noc = iseg; /* Chop ^Z and anything following. */
break;
}
++iseg;
}
}else break; /* Drop apparent garbage in. (Could assert.) */
}else break;
}
/* If got nothing, (after ^Z chop), must be at end-of-file. */
if( noc == 0 ) return 0;
buf[noc] = 0;
return buf;
}else{
return fgets(buf, ncmax, fin);
}
}
# define fgets(b,n,f) utf8_fgets(b,n,f)
#endif /* SHELL_WIN_UTF8_OPT */
/*
** Render output like fprintf(). Except, if the output is going to the
** console and if this is running on a Windows machine, and if the -utf8
** option is unavailable or (available and inactive), translate the
** output from UTF-8 into MBCS for output through 8-bit stdout stream.
** (With -utf8 active, no translation is needed and must not be done.)
*/
#if defined(_WIN32) || defined(WIN32)
void vf_utf8_printf(FILE *out, const char *zFormat, va_list ap){
if( stdout_is_console && (out==STD_OUT || out==STD_ERR)
# if SHELL_WIN_UTF8_OPT
&& !console_utf8
# endif
){
char *z1 = sqlite3_vmprintf(zFormat, ap);
char *z2 = sqlite3_win32_utf8_to_mbcs_v2(z1, 0);
sqlite3_free(z1);
if( z2 ) fputs(z2, out);
sqlite3_free(z2);
}else{
vfprintf(out, zFormat, ap);
}
}
void utf8_printf(FILE *out, const char *zFormat, ...){
va_list ap;
va_start(ap, zFormat);
vf_utf8_printf(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
/*
** Provide a way for embedding apps to handle OOM or other shell-fatal
** conditions their way. A plain exit() call will undoubtedly leak
** memory and handles. The default scheme, when SHELL_TERMINATE is
** not defined, uses setjmp()/longjmp() execution stack ripping
** together with a resource stack which can be popped to release
** resources automatically. One replacement could be:
# define SHELL_TERMINATE(why) \
fprintf(stderr, "Error: Terminating due to %s\n", why);\
exit(1);
*/
/*
** Provide an abrupt exit/termination for routines that cannot proceed
** or whose failure means the CLI cannot usefully continue.
*/
#ifdef SHELL_TERMINATE
ResourceMark exit_mark = 0; /* May be set by CLI-hosting application. */
#endif
static void shell_terminate(const char *zWhy){
#ifndef SHELL_TERMINATE
quit_moan(zWhy, 2);
#else
release_holders_mark(exit_mark);
SHELL_TERMINATE(zWhy);
#endif
}
/* Indicate out-of-memory and terminate. */
static void shell_out_of_memory(void){
shell_terminate("out of memory");
}
#ifdef SQLITE_DEBUG
static int fake_oom_countdown = 0;
static void maybe_fake_oom(void){
if( fake_oom_countdown>0 && --fake_oom_countdown==0 ){
shell_out_of_memory();
}
}
/* The next 2 routines normally check for an OOM error. However, when
** fake_oom_countdown is non-zero, it is decremented and, upon reaching
** zero, a fake OOM condition is emulated. Additionally, (to keep leak
** detection reporting useful), the non-zero memory pointer is freed.
*/
/* Check a malloc()'ed (or equivalent) pointer to see if it is NULL.
** If so, terminate with an out-of-memory error.
*/
static void shell_check_oomm(const void *p){
if( p==0 ) shell_out_of_memory();
if( fake_oom_countdown>0 && --fake_oom_countdown==0 ){
free((void*)p);
shell_out_of_memory();
}
}
/* Check a sqlite3_malloc()'ed (or equivalent) pointer to see if it is NULL.
** If so, terminate with an out-of-memory error.
*/
static void shell_check_ooms(const void *p){
if( p==0 ) shell_out_of_memory();
if( fake_oom_countdown>0 && --fake_oom_countdown==0 ){
sqlite3_free((void*)p);
shell_out_of_memory();
}
}
#else
# define shell_check_ooms(p) do{ if((p)==0) shell_out_of_memory(); }while(0)
# define shell_check_oomm(p) do{ if((p)==0) shell_out_of_memory(); }while(0)
# define maybe_fake_oom()
#endif
/* Check a SQLite result code for out-of-memory indication.
** If that is so, terminate with an out-of-memory error.
*/
static int shell_check_nomem(int rc){
if( SQLITE_NOMEM==rc ) shell_out_of_memory();
return rc;
}
/* Convenience functions using shell_check_nomem supporting OOM testing: */
static int s3_exec_noom(sqlite3 *db, const char *sql,
int (*callback)(void*,int,char**,char**), void *pvcb,
char **pzErr){
int rc;
char *zErrHere = 0;
if( pzErr ) *pzErr = 0;
maybe_fake_oom();
rc = sqlite3_exec(db, sql, callback, pvcb, &zErrHere);
if( rc==SQLITE_NOMEM ){
sqlite3_free(zErrHere);
shell_out_of_memory();
}else{
if( pzErr ) *pzErr = zErrHere;
}
return rc;
}
static int s3_step_noom(sqlite3_stmt *pstmt){
maybe_fake_oom();
return shell_check_nomem(sqlite3_step(pstmt));
}
static int s3_prepare_v2_noom(sqlite3 *db, const char *zSql, int nByte,
sqlite3_stmt **ppStmt, const char **pzTail){
maybe_fake_oom();
return shell_check_nomem(sqlite3_prepare_v2(db,zSql,nByte,ppStmt,pzTail));
}
/* Shorten a sqlite3_prepare_v2() usage pattern common in this code:
** Build a query string; OOM-check it; prepare; free and mark the string.
** There is no length or pzTail argument -- (useless in this context.)
** On return (if any), *pzSql will have been set to 0. */
static int s3_prep_noom_free(sqlite3 *db, char **pzSql, sqlite3_stmt **ppStmt){
int rc;
sstr_ptr_holder(pzSql);
maybe_fake_oom();
shell_check_ooms(*pzSql);
rc = sqlite3_prepare_v2(db,*pzSql,-1,ppStmt,0);
shell_check_nomem(rc);
release_holder();
*pzSql = 0;
return rc;
}
/*
** Write I/O traces to the following stream.
*/
#ifdef SQLITE_ENABLE_IOTRACE
static FILE *iotrace = 0;
#endif
/*
** This routine works like printf in that its first argument is a
** format string and subsequent arguments are values to be substituted
** in place of % fields. The result of formatting this string
** is written to iotrace.
*/
#ifdef SQLITE_ENABLE_IOTRACE
static void SQLITE_CDECL iotracePrintf(const char *zFormat, ...){
va_list ap;
char *z;
if( iotrace==0 ) return;
va_start(ap, zFormat);
z = sqlite3_vmprintf(zFormat, ap);
va_end(ap);
utf8_printf(iotrace, "%s", z);
sqlite3_free(z);
}
#endif
/*
** Output string zUtf to stream pOut as w characters. If w is negative,
** then right-justify the text. W is the width in UTF-8 characters, not
** in bytes. This is different from the %*.*s specification in printf
** since with %*.*s the width is measured in bytes, not characters.
*/
static void utf8_width_print(FILE *pOut, int w, const char *zUtf){
int i;
int n;
int aw = w<0 ? -w : w;
if( zUtf==0 ) zUtf = "";
for(i=n=0; zUtf[i]; i++){
if( (zUtf[i]&0xc0)!=0x80 ){
n++;
if( n==aw ){
do{ i++; }while( (zUtf[i]&0xc0)==0x80 );
break;
}
}
}
if( n>=aw ){
utf8_printf(pOut, "%.*s", i, zUtf);
}else if( w<0 ){
utf8_printf(pOut, "%*s%s", aw-n, "", zUtf);
}else{
utf8_printf(pOut, "%s%*s", zUtf, aw-n, "");
}
}
/*
** Determines if a string is a number of not.
*/
static int isNumber(const char *z, int *realnum){
if( *z=='-' || *z=='+' ) z++;
if( !IsDigit(*z) ){
return 0;
}
z++;
if( realnum ) *realnum = 0;
while( IsDigit(*z) ){ z++; }
if( *z=='.' ){
z++;
if( !IsDigit(*z) ) return 0;
while( IsDigit(*z) ){ z++; }
if( realnum ) *realnum = 1;
}
if( *z=='e' || *z=='E' ){
z++;
if( *z=='+' || *z=='-' ) z++;
if( !IsDigit(*z) ) return 0;
while( IsDigit(*z) ){ z++; }
if( realnum ) *realnum = 1;
}
return *z==0;
}
/*
** Compute a string length that is limited to what can be stored in
** lower 30 bits of a 32-bit signed integer.
*/
static int strlen30(const char *z){
const char *z2 = z;
while( *z2 ){ z2++; }
return 0x3fffffff & (int)(z2 - z);
}
/*
** Return the length of a string in characters. Multibyte UTF8 characters
** count as a single character.
*/
static int strlenChar(const char *z){
int n = 0;
while( *z ){
if( (0xc0&*(z++))!=0x80 ) n++;
}
return n;
}
/*
** Return open FILE * if zFile exists, can be opened for read
** and is an ordinary file or a character stream source.
** Otherwise return 0.
*/
static FILE * openChrSource(const char *zFile){
#ifdef _WIN32
struct _stat x = {0};
# define STAT_CHR_SRC(mode) ((mode & (_S_IFCHR|_S_IFIFO|_S_IFREG))!=0)
/* On Windows, open first, then check the stream nature. This order
** is necessary because _stat() and sibs, when checking a named pipe,
** effectively break the pipe as its supplier sees it. */
FILE *rv = fopen(zFile, "rb");
if( rv==0 ) return 0;
if( _fstat(_fileno(rv), &x) != 0
|| !STAT_CHR_SRC(x.st_mode)){
fclose(rv);
rv = 0;
}
return rv;
#else
struct stat x = {0};
int rc = stat(zFile, &x);
# define STAT_CHR_SRC(mode) (S_ISREG(mode)||S_ISFIFO(mode)||S_ISCHR(mode))
if( rc!=0 ) return 0;
if( STAT_CHR_SRC(x.st_mode) ){
return fopen(zFile, "rb");
}else{
return 0;
}
#endif
#undef STAT_CHR_SRC
}
/*
** Arrange for shell input from either a FILE or a string.
** For applicable invariants, see strLineGet(...) which is
** the only modifier of this struct. (4rd and 5th members)
** All other members are simply initialized to select the
** input stream and track input sources, set by whatever
** routines redirect the input.
*/
typedef struct InSource {
struct InSource *pFrom; /* Aid redirect tracking and auto-unnesting. */
FILE *inFile; /* Will be 0 when input is to be taken from string. */
char *zStrIn; /* Will be 0 when no input is available from string. */
int iReadOffset; /* Offset into zStrIn where next "read" to be done */
int lineno; /* How many lines have been read from this source */
const char *zSourceSay; /* For complaints, a name kept for this source */
union {
int (*stream)(FILE *);
void (*ptext)(char *);
} closer; /* Closer for the file or freer for the text, set by opener */
} InSource;
#define INSOURCE_STR_REDIR(str, tagTo, isFrom) {isFrom, 0, str, 0, 0, tagTo, 0 }
#define INSOURCE_FILE_REDIR(fh, tagTo, isFrom) {isFrom, fh, 0, 0, 0, tagTo, 0 }
#define INSOURCE_IS_INVOKEARG(pIS) \
((pIS)==&cmdInSource)
#ifndef SQLITE_SHELL_FIDDLE
# define INSOURCE_IS_INTERACTIVE(pIS) \
((pIS)==&termInSource && stdin_is_interactive )
#else
# define INSOURCE_IS_INTERACTIVE(pIS) 0
#endif
/* These instances' addresses are taken as part of interactive input test
* or test for other special handling as a command-line argument. */
static InSource cmdInSource = { 0, 0, 0, 0, 0, "<cmdLine>", 0 };
#ifndef SQLITE_SHELL_FIDDLE
static InSource termInSource = { 0, 0, 0, 0, 0, "<terminal>", 0 };
static InSource stdInSource = { 0, 0, 0, 0, 0, "<stdin>", 0 };
#else
static InSource fiddleInSource = { 0, 0, 0, 0, 0, "<fiddle>", 0 };
#endif /* defined(SQLITE_SHELL_FIDDLE) */
/* Initializer for just above 3 (non-FIDDLE) InSource objects */
#ifndef SQLITE_SHELL_FIDDLE
static void init_std_inputs(FILE *pIn ){
termInSource.inFile = pIn;
stdInSource.inFile = pIn;
cmdInSource.lineno = 0;
}
#else
# define init_std_inputs(x)
#endif
/* Setup cmdInSource to supply given text as input. */
static void set_invocation_cmd(char *zDo){
cmdInSource.iReadOffset = 0;
cmdInSource.zStrIn = zDo;
++cmdInSource.lineno;
}
#ifdef SQLITE_SHELL_FIDDLE
static void set_fiddle_input_text(char *zDo){
fiddleInSource.iReadOffset = 0;
fiddleInSource.zStrIn = zDo;
if( zDo ) ++fiddleInSource.lineno;
}
#endif /* defined(SQLITE_SHELL_FIDDLE) */
/* Close an InSource object and unlink it from redirect nesting. */
static void finish_InSource( InSource **ppIS ){
if( ppIS!=0 && *ppIS!=0 ){
InSource *pIS = *ppIS;
if( pIS->closer.stream != 0 ){
if( pIS->inFile != 0 ){
(pIS->closer.stream)(pIS->inFile);
pIS->inFile = 0;
}else if( pIS->zStrIn != 0 ){
(pIS->closer.ptext)(pIS->zStrIn);
pIS->zStrIn = 0;
}
}
*ppIS = pIS->pFrom;
pIS->pFrom = 0;
}
}
/* Similar to fgets(buffer, limit, file) but for InSource holding a string. */
static char *strLineGet(char *zBuf, int ncMax, InSource *pInSrc){
if( pInSrc->inFile!=0 ){
char *zRet = fgets(zBuf, ncMax, pInSrc->inFile );
if( zRet!=0 ){
int iRead = strlen30(zRet);
if( iRead>0 && zRet[iRead-1]=='\n' ) ++pInSrc->lineno;
/* Consider: record line length to avoid rescan for it. */
return zRet;
}
}
else if( pInSrc->zStrIn!=0 ){
char *zBegin = pInSrc->zStrIn + pInSrc->iReadOffset;
if( *zBegin!=0 ){
int iTake = 0;
char c;
ncMax -= 1;
while( iTake<ncMax && (c=zBegin[iTake])!=0 ){
zBuf[iTake++] = c;
if( c=='\n' ){
++pInSrc->lineno;
break;
}
}
if( ncMax>=0 ) zBuf[iTake] = 0;
pInSrc->iReadOffset += iTake;
/* Consider: record line length to avoid rescan for it. */
return zBuf;
}
}
return 0;
}
/*
** This routine reads a line of text from designated stream source,
** stores the text in memory obtained from malloc() and returns a
** pointer to the text. NULL is returned at end of file.
** There will be no return if malloc() fails.
**
** The trailing newline (or other line-end chars) are stripped.
**
** If zLine is not NULL then it is a malloced buffer returned from
** a previous call to this routine that may be reused.
*/
static char *local_getline(char *zLine, InSource *pInSrc){
int nLine = zLine==0 ? 0 : 100;
int n = 0;
while( 1 ){
if( n+100>nLine ){
nLine = nLine*2 + 100;
zLine = realloc(zLine, nLine);
shell_check_oomm(zLine);
}
if( strLineGet(&zLine[n], nLine - n, pInSrc)==0 ){
if( n==0 ){
free(zLine);
return 0;
}
zLine[n] = 0;
break;
}
while( zLine[n] ) n++;
if( n>0 && zLine[n-1]=='\n' ){
n--;
if( n>0 && zLine[n-1]=='\r' ) n--;
zLine[n] = 0;
break;
}
}
#if defined(_WIN32) || defined(WIN32)
/* For interactive input on Windows systems, without -utf8,
** translate the multi-byte characterset characters into UTF-8.
** This is the translation that predates the -utf8 option. */
if( stdin_is_interactive && pInSrc==&termInSource
# if SHELL_WIN_UTF8_OPT
&& !console_utf8
# endif /* SHELL_WIN_UTF8_OPT */
){
char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0);
if( zTrans ){
i64 nTrans = strlen(zTrans)+1;
if( nTrans>nLine ){
char *zL = realloc(zLine, nTrans);
if( zL!=0 ) zLine = zL;
else{
free(zLine);
sqlite3_free(zTrans);
shell_out_of_memory();
}
}
memcpy(zLine, zTrans, nTrans);
sqlite3_free(zTrans);
}
}
#endif /* defined(_WIN32) || defined(WIN32) */
return zLine;
}
/*
** Retrieve a single line of input text from designated input source.
**
** If the input is interactive, then issue either the continuation prompt
** or the main prompt, per isContinuation, before reading a line from
** standard input. Otherwise, just read a line from the specified source.
**
** If zPrior is not NULL then it is a buffer from a prior call to this
** routine that can be reused. If not used, it must be passed to free().
**
** The result is stored in space obtained from malloc() and must either
** be freed by the caller, using the same allocator[a], or else passed
** back into this routine via the zPrior argument for reuse.
**
** [a. This function is exposed for use by shell extensions which may
** have no access to "the same allocator". This is why the function
** following this one exists, also exposed to shell extensions. ]
**
** If this function is called until it returns NULL, and the prior return
** has been passed in for reuse, then the caller need/must not free it.
** Otherwise, (in case of an early termination of reading from the given
** input), the caller is responsible for freeing a prior, non-NULL return.
**
** The trailing newline (or its ilk), if any, is trimmed.
** The input line number is adjusted (via delegation or directly.)
*/
static char *one_input_line(InSource *pInSrc, char *zPrior,
int isContinuation, Prompts *pCue){
if( !INSOURCE_IS_INTERACTIVE(pInSrc) ){
return local_getline(zPrior, pInSrc);
}else{
static Prompts cueDefault = { "$ ","> " };
const char *zPrompt;
char *zResult;
if( pCue==0 ) pCue = &cueDefault;
zPrompt = isContinuation ? pCue->zContinue : pCue->zMain;
#if SHELL_USE_LOCAL_GETLINE
printf("%s", zPrompt);
fflush(stdout);
do{
zResult = local_getline(zPrior, pInSrc);
zPrior = 0;
/* ^C trap creates a false EOF, so let "interrupt" thread catch up. */
if( zResult==0 ){
sqlite3_sleep(150);
}
}while( zResult==0 && seenInterrupt>0 );
#else
free(zPrior);
while( (zResult = shell_readline(zPrompt))==0 ){
/* ^C trap creates a false EOF, so let "interrupt" thread catch up. */
sqlite3_sleep(150);
if( seenInterrupt==0 ) break;
zPrompt = "\n";
}
if( zResult ){
if( *zResult ) shell_add_history(zResult);
++pInSrc->lineno;
}
#endif
if( seenInterrupt>0 ){
printf("^C\n");
fflush(stdout);
}
return zResult;
}
}
/* For use by shell extensions. See footnote [a] to one_input_line(). */
void free_input_line(char *z){
free(z);
}
/*
** Return the value of a hexadecimal digit, or -1 if c is not a hex digit.
*/
static int hexDigitValue(char c){
if( c>='0' && c<='9' ) return c - '0';
if( c>='a' && c<='f' ) return c - 'a' + 10;
if( c>='A' && c<='F' ) return c - 'A' + 10;
return -1;
}
/*
** Interpret zArg as an integer value, possibly with suffixes.
*/
static sqlite3_int64 integerValue(const char *zArg){
sqlite3_int64 v = 0;
static const struct { char *zSuffix; int iMult; } aMult[] = {
{ "KiB", 1024 },
{ "MiB", 1024*1024 },
{ "GiB", 1024*1024*1024 },
{ "KB", 1000 },
{ "MB", 1000000 },
{ "GB", 1000000000 },
{ "K", 1000 },
{ "M", 1000000 },
{ "G", 1000000000 },
};
int i;
int isNeg = 0;
if( zArg[0]=='-' ){
isNeg = 1;
zArg++;
}else if( zArg[0]=='+' ){
zArg++;
}
if( zArg[0]=='0' && zArg[1]=='x' ){
int x;
zArg += 2;
while( (x = hexDigitValue(zArg[0]))>=0 ){
v = (v<<4) + x;
zArg++;
}
}else{
while( IsDigit(zArg[0]) ){
v = v*10 + zArg[0] - '0';
zArg++;
}
}
for(i=0; i<ArraySize(aMult); i++){
if( sqlite3_stricmp(aMult[i].zSuffix, zArg)==0 ){
v *= aMult[i].iMult;
break;
}
}
return isNeg? -v : v;
}
/*
** A variable length string to which one can append text.
*/
typedef struct ShellText ShellText;
struct ShellText {
char *z;
int n;
int nAlloc;
};
/*
** Initialize and destroy a ShellText object
*/
static void initText(ShellText *p){
memset(p, 0, sizeof(*p));
}
static void freeText(ShellText *p){
free(p->z);
initText(p);
}
/*
** Take ownership of a ShellText's text, leaving it like new.
*/
static char* takeText(ShellText *p){
char *rv = p->z;
initText(p);
return rv;
}
/* zIn is either a pointer to a NULL-terminated string in memory obtained
** from malloc(), or a NULL pointer. The string pointed to by zAppend is
** 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
** quote character for zAppend.
**
** This routine can abruptly exit under OOM conditions.
*/
static void appendText(ShellText *p, const char *zAppend, char quote){
i64 len;
i64 i;
i64 nAppend = strlen30(zAppend);
len = nAppend+p->n+1;
if( quote ){
len += 2;
for(i=0; i<nAppend; i++){
if( zAppend[i]==quote ) len++;
}
}
if( p->z==0 || p->n+len>=p->nAlloc ){
char *zn;
int nna = p->nAlloc*2 + len + 20;
zn = (char*)realloc(p->z, nna);
if( !zn ) shell_out_of_memory();
p->nAlloc = nna;
p->z = zn;
}
if( quote ){
char *zCsr = p->z+p->n;
*zCsr++ = quote;
for(i=0; i<nAppend; i++){
*zCsr++ = zAppend[i];
if( zAppend[i]==quote ) *zCsr++ = quote;
}
*zCsr++ = quote;
p->n = (int)(zCsr - p->z);
*zCsr = '\0';
}else{
memcpy(p->z+p->n, zAppend, nAppend);
p->n += nAppend;
p->z[p->n] = '\0';
}
}
/*
** Attempt to determine if identifier zName needs to be quoted, either
** because it contains non-alphanumeric characters, or because it is an
** SQLite keyword. Be conservative in this estimate: When in doubt assume
** that quoting is required.
**
** Return '"' if quoting is required. Return 0 if no quoting is required.
*/
static char quoteChar(const char *zName){
int i;
if( zName==0 ) return '"';
if( !isalpha((unsigned char)zName[0]) && zName[0]!='_' ) return '"';
for(i=0; zName[i]; i++){
if( !isalnum((unsigned char)zName[i]) && zName[i]!='_' ) return '"';
}
return sqlite3_keyword_check(zName, i) ? '"' : 0;
}
/*
** Construct a fake object name and column list to describe the structure
** of the view, virtual table, or table valued function zSchema.zName.
** The return is a sqlite3_malloc'ed pointer, caller to free() sometime.
*/
static char *shellFakeSchema(
sqlite3 *db, /* The database connection containing the vtab */
const char *zSchema, /* Schema of the database holding the vtab */
const char *zName /* The name of the virtual table */
){
sqlite3_stmt *pStmt = 0;
ShellText s;
char cQuote;
char *zDiv = "(";
int nRow = 0;
int rc;
char *rv = 0;
ResourceMark rm_mark = holder_mark();
char *zSql = smprintf("PRAGMA \"%w\".table_info=%Q;",
zSchema ? zSchema : "main", zName);
rc = s3_prep_noom_free(db, &zSql, &pStmt);
stmt_holder(pStmt);
initText(&s);
text_ref_holder(&s);
if( zSchema ){
cQuote = quoteChar(zSchema);
if( cQuote && sqlite3_stricmp(zSchema,"temp")==0 ) cQuote = 0;
appendText(&s, zSchema, cQuote);
appendText(&s, ".", 0);
}
cQuote = quoteChar(zName);
appendText(&s, zName, cQuote);
while( (rc = s3_step_noom(pStmt))==SQLITE_ROW ){
const char *zCol = (const char*)sqlite3_column_text(pStmt, 1);
nRow++;
appendText(&s, zDiv, 0);
zDiv = ",";
if( zCol==0 ) zCol = "";
cQuote = quoteChar(zCol);
appendText(&s, zCol, cQuote);
}
if( rc==SQLITE_NOMEM ) shell_out_of_memory();
appendText(&s, ")", 0);
if( nRow!=0 ) rv = takeText(&s);
RESOURCE_FREE(rm_mark);
return rv;
}
/*
** SQL function: strtod(X)
**
** Use the C-library strtod() function to convert string X into a double.
** Used for comparing the accuracy of SQLite's internal text-to-float conversion
** routines against the C-library.
*/
static void shellStrtod(
sqlite3_context *pCtx,
int nVal,
sqlite3_value **apVal
){
char *z = (char*)sqlite3_value_text(apVal[0]);
UNUSED_PARAMETER(nVal);
if( z==0 ) return;
sqlite3_result_double(pCtx, strtod(z,0));
}
/*
** SQL function: dtostr(X)
**
** Use the C-library printf() function to convert real value X into a string.
** Used for comparing the accuracy of SQLite's internal float-to-text conversion
** routines against the C-library.
*/
static void shellDtostr(
sqlite3_context *pCtx,
int nVal,
sqlite3_value **apVal
){
double r = sqlite3_value_double(apVal[0]);
int n = nVal>=2 ? sqlite3_value_int(apVal[1]) : 26;
char z[400];
if( n<1 ) n = 1;
if( n>350 ) n = 350;
sprintf(z, "%#+.*e", n, r);
sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT);
}
/*
** SQL function: shell_module_schema(X)
**
** Return a fake schema for the table-valued function or eponymous virtual
** table X.
*/
static void shellModuleSchema(
sqlite3_context *pCtx,
int nVal,
sqlite3_value **apVal
){
char *zFake = 0;
const char * zName = (const char*)sqlite3_value_text(apVal[0]);
UNUSED_PARAMETER(nVal);
if( zName ){
zFake = shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName);
if( zFake ){
char *zfc = smprintf("/* %s */", zFake);
free(zFake);
shell_check_ooms(zfc);
sqlite3_result_text(pCtx, zfc, -1, sqlite3_free);
}
}
}
/*
** SQL function: shell_add_schema(S,X)
**
** Add the schema name X to the CREATE statement in S and return the result.
** Examples:
**
** CREATE TABLE t1(x) -> CREATE TABLE xyz.t1(x);
**
** Also works on
**
** CREATE INDEX
** CREATE UNIQUE INDEX
** CREATE VIEW
** CREATE TRIGGER
** CREATE VIRTUAL TABLE
**
** This UDF is used by the .schema command to insert the schema name of
** attached databases into the middle of the sqlite_schema.sql field.
*/
static void shellAddSchemaName(
sqlite3_context *pCtx,
int nVal,
sqlite3_value **apVal
){
static const char *aPrefix[] = {
"TABLE",
"INDEX",
"UNIQUE INDEX",
"VIEW",
"TRIGGER",
"VIRTUAL TABLE"
};
int i = 0;
const char *zIn = (const char*)sqlite3_value_text(apVal[0]);
const char *zSchema = (const char*)sqlite3_value_text(apVal[1]);
const char *zName = (const char*)sqlite3_value_text(apVal[2]);
sqlite3 *db = sqlite3_context_db_handle(pCtx);
UNUSED_PARAMETER(nVal);
if( zIn!=0 && cli_strncmp(zIn, "CREATE ", 7)==0 ){
for(i=0; i<ArraySize(aPrefix); i++){
int n = strlen30(aPrefix[i]);
if( cli_strncmp(zIn+7, aPrefix[i], n)==0 && zIn[n+7]==' ' ){
char *z = 0;
char *zFake = 0;
if( zSchema ){
char cQuote = quoteChar(zSchema);
if( cQuote && sqlite3_stricmp(zSchema,"temp")!=0 ){
z = smprintf("%.*s \"%w\".%s", n+7, zIn, zSchema, zIn+n+8);
}else{
z = smprintf("%.*s %s.%s", n+7, zIn, zSchema, zIn+n+8);
}
shell_check_ooms(z);
}
if( zName
&& aPrefix[i][0]=='V'
&& (zFake = shellFakeSchema(db, zSchema, zName))!=0
){
if( z==0 ){
z = smprintf("%s\n/* %s */", zIn, zFake);
}else{
z = smprintf("%z\n/* %s */", z, zFake);
}
free(zFake);
shell_check_ooms(z);
}
if( z ){
sqlite3_result_text(pCtx, z, -1, sqlite3_free);
return;
}
}
}
}
sqlite3_result_value(pCtx, apVal[0]);
}
/*
** The source code for several run-time loadable extensions is inserted
** below by the ../tool/mkshellc.tcl script. Before processing that included
** code, we need to override some macros to make the included program code
** work here in the middle of this regular program.
*/
#undef SQLITE_EXTENSION_INIT1
#undef SQLITE_EXTENSION_INIT2
#define SQLITE_EXTENSION_INIT1
#define SQLITE_EXTENSION_INIT2(X) (void)(X)
#if defined(_WIN32) && defined(_MSC_VER)
INCLUDE test_windirent.h
INCLUDE test_windirent.c
#define dirent DIRENT
#endif
INCLUDE ../ext/misc/shathree.c
INCLUDE ../ext/misc/memtrace.c
INCLUDE ../ext/misc/pcachetrace.c
INCLUDE ../ext/misc/uint.c
INCLUDE ../ext/misc/decimal.c
#undef sqlite3_base_init
#define sqlite3_base_init sqlite3_base64_init
INCLUDE ../ext/misc/base64.c
#undef sqlite3_base_init
#define sqlite3_base_init sqlite3_base85_init
#define OMIT_BASE85_CHECKER
INCLUDE ../ext/misc/base85.c
INCLUDE ../ext/misc/ieee754.c
INCLUDE ../ext/misc/series.c
INCLUDE ../ext/misc/regexp.c
#ifndef SQLITE_SHELL_FIDDLE
INCLUDE ../ext/misc/fileio.c
INCLUDE ../ext/misc/completion.c
INCLUDE ../ext/misc/appendvfs.c
#endif
#ifdef SQLITE_HAVE_ZLIB
INCLUDE ../ext/misc/zipfile.c
INCLUDE ../ext/misc/sqlar.c
#endif
INCLUDE ../ext/expert/sqlite3expert.h
INCLUDE ../ext/expert/sqlite3expert.c
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
# define SQLITE_SHELL_HAVE_RECOVER 1
#else
# define SQLITE_SHELL_HAVE_RECOVER 0
#endif
#if SQLITE_SHELL_HAVE_RECOVER
INCLUDE ../ext/recover/sqlite3recover.h
# ifndef SQLITE_HAVE_SQLITE3R
INCLUDE ../ext/recover/dbdata.c
INCLUDE ../ext/recover/sqlite3recover.c
# endif /* !defined(SQLITE_HAVE_SQLITE3R) */
#endif
#ifdef SQLITE_SHELL_EXTSRC
# include SHELL_STRINGIFY(SQLITE_SHELL_EXTSRC)
#endif
#if defined(SQLITE_ENABLE_SESSION)
/*
** State information for a single open session
*/
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
typedef struct ExpertInfo ExpertInfo;
struct ExpertInfo {
sqlite3expert *pExpert;
int bVerbose;
};
/* A single line in the EQP output */
typedef struct EQPGraphRow EQPGraphRow;
struct EQPGraphRow {
int iEqpId; /* ID for this row */
int iParentId; /* ID of the parent row */
EQPGraphRow *pNext; /* Next row in sequence */
char zText[1]; /* Text to display for this row */
};
/* All EQP output is collected into an instance of the following */
typedef struct EQPGraph EQPGraph;
struct EQPGraph {
EQPGraphRow *pRow; /* Linked list of all rows of the EQP output */
EQPGraphRow *pLast; /* Last element of the pRow list */
char zPrefix[100]; /* Graph prefix */
};
/* By default, omit the extension options that are not done yet.
* "SHELL_EXTENSIONS" is short for "Some shell extensions are built in." */
#ifndef SHELL_OMIT_EXTENSIONS
# define SHELL_OMIT_EXTENSIONS 4
# define SHELL_EXTENSIONS 1
#else
# define SHELL_EXTENSIONS \
(0!=((~SHELL_OMIT_EXTENSIONS) & SHELL_ALL_EXTENSIONS))
#endif
#ifdef SQLITE_OMIT_LOAD_EXTENSION
# define SHELL_OMIT_LOAD_EXTENSION 1
#else
# define SHELL_OMIT_LOAD_EXTENSION 0
#endif
/* Selectively omit features with one PP variable. Value is true iff
** either x is not defined or defined with 0 in bitnum bit position.
*/
#define NOT_IFDEF_BIT(x,bitnum) (x? (!(x & (1<<bitnum))) : !(x+0))
/* Whether build will include extended input parsing option */
#define SHEXT_PARSING_BIT 0
#define SHELL_EXTENDED_PARSING \
NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_PARSING_BIT)
/* Whether build will include runtime extension via .shxload */
#define SHEXT_DYNEXT_BIT 1
#define SHELL_DYNAMIC_EXTENSION ( !SHELL_OMIT_LOAD_EXTENSION \
&& NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_DYNEXT_BIT) )
/* Whether build will include expansion of variables in dot-commands */
#define SHEXT_VAREXP_BIT 2
#define SHELL_VARIABLE_EXPANSION \
NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_VAREXP_BIT)
#define SHELL_ALL_EXTENSIONS \
(1<<SHEXT_PARSING_BIT)+(1<<SHEXT_DYNEXT_BIT)+(1<<SHEXT_VAREXP_BIT)
/* Runtime test for shell extended parsing, given ShellInState pointer */
#if SHELL_EXTENDED_PARSING
# define SHEXT_PARSING(psi) ((psi->bExtendedDotCmds&(1<<SHEXT_PARSING_BIT))!=0)
#else
# define SHEXT_PARSING(psi) 0
#endif
/* Runtime test for shell variable expansion, given ShellInState pointer */
#if SHELL_EXTENDED_PARSING
# define SHEXT_VAREXP(psi) ((psi->bExtendedDotCmds&(1<<SHEXT_VAREXP_BIT))!=0)
#else
# define SHEXT_VAREXP(psi) 0
#endif
/* Enable use of ExportHandler and ImportHandler interfaces for built-in I/O.
** (This deactivates "shuffled" older code which can be deleted some day. The
** code has been partitioned and moved as part of built-in exporters.) */
#define SHELL_DATAIO_EXT 1
#if SHELL_DYNAMIC_EXTENSION
/* This is only used to support extensions that need this information.
* For example, they might need to locate and load related files. */
# if defined(_WIN32) || defined(WIN32)
static char startupDir[MAX_PATH+1] = {0};
# define initStartupDir() (_getcwd(startupDir,MAX_PATH)!=0)
# define IS_PATH_SEP(c) ((c)=='/'||(c)=='\\')
# else
static char startupDir[PATH_MAX+1] = {0};
# define initStartupDir() (getcwd(startupDir, sizeof(startupDir))!=0)
/* Above useless expression avoids an "unused result" warning. */
# define IS_PATH_SEP(c) ((c)=='/')
# endif
# ifndef SHELL_OMIT_EXTBYNAME
/* Is a program invocation name one used for a shell to start as extensible? */
static int isExtendedBasename(const char *zPgm){
int ixe = (zPgm)? (int)strlen(zPgm)-1 : 0;
if( ixe==0 ) return 0;
while( ixe>=0 && !IS_PATH_SEP(zPgm[ixe]) ) --ixe;
/* index is just before the basename with extension(s) */
return sqlite3_strnicmp(&zPgm[ixe+1], "sqlite3x", 8)==0;
}
# else
# define isExtendedBasename(pathname) 0
# endif
/* Tracking and use info for loaded shell extensions
* An instance is kept for each shell extension that is currently loaded.
* They are kept in a simple list (aka dynamic array), index into which
* is used internally to get the extension's object. These indices are
* kept in the dbShell and updated there as the list content changes.
*/
typedef struct ShExtInfo {
ExtensionId extId; /* The xInit function pointer */
void (*extDtor)(void *); /* Extension shutdown on exit or unload */
void *pvExtObj; /* Passed to extDtor(...) at shutdown */
/* Each shell extension library registers 0 or more of its extension
* implementations, interfaces to which are kept in below dynamic.
* arrays. The dbShell DB keeps indices into these arrays and into
* an array of instances of this struct to facilitate lookup by name
* of pointers to the implementations. */
int numDotCommands;
DotCommand **ppDotCommands;
int numExportHandlers;
ExportHandler **ppExportHandlers;
int numImportHandlers;
ImportHandler **ppImportHandlers;
DotCommand *pUnknown; /* .unknown registered for this extension (unowned) */
} ShExtInfo;
#define SHEXT_INFO_INIT {0,0,NULL, 0,NULL, 0,NULL, 0,NULL, NULL}
#endif
/* Parameters affecting columnar mode result display (defaulting together) */
typedef struct ColModeOpts {
int iWrap; /* In columnar modes, wrap lines reaching this limit */
u8 bQuote; /* Quote results for .mode box and table */
u8 bWordWrap; /* In columnar modes, wrap at word boundaries */
} ColModeOpts;
#define ColModeOpts_default { 60, 0, 0 }
#define ColModeOpts_default_qbox { 60, 1, 0 }
/*
** Stored output mode state, for partial save and later restore.
** Returned by:
** outputModeSave *outputModeSave(ShellInState *p, SaveModeWhat what).
** Accepted by:
** outputModeRestore(ShellInState *p, OutputModeSave *pSaved).
** See enum SaveWhatMode regarding what to save and restore.
** Also see outputModePush(...), outputModePushSome(...) and
** outputModePop(...) for usages spanning more than one call.
*/
typedef struct OutputModeSave{
u16 what; /* Set upon creation. See SaveWhatMode for values. */
char itsValues[1]; /* This size is inaccurate unless nothing is saved. */
} OutputModeSave;
#define MODE_STACK_MAX 3 /* How many levels of saved output mode to allow. */
/*
** Shell state information is contained in an instance of the following struct
** and in a ShellExState struct defined in shext_linkage.h, partitioned thus:
**
** Data not exposed for shell extension use, (and which need not be stable),
** are kept in a ShellInState instance. These include facts about the database
** connection, specialized display mode setup, safe mode control, and other
** data associated with the shell's means of operation.
**
** Data which is exposed for shell extension use, (and which should be stable
** or grown only with new members at the end, to preserve layout), is kept
** in the ShellExState instance, which contains a ShellInState pointer, pSIS.
*/
/* Oft-used macros for transition to internal/external shell state access
* ISS(psx) : internal shell state (pointer)
* XSS(psi) : external shell state (pointer)
* DBI(psi) : DB from internal pointer
* DBX(psx) : DB from external pointer
*/
#define ISS(psx) (psx)->pSIS
#define XSS(psi) (psi)->pSXS
#define DBI(psi) (psi)->pSXS->dbUser
#define DBX(psx) (psx)->dbUser
typedef struct ShellInState {
int openFlags; /* Additional flags to open. (SQLITE_OPEN_NOFOLLOW) */
u8 openMode; /* SHELL_OPEN_NORMAL, _APPENDVFS, or _ZIPFILE */
sqlite3_int64 szMax; /* --maxsize argument to .open */
u8 autoExplain; /* Automatically turn on .explain mode */
u8 autoEQP; /* Run EXPLAIN QUERY PLAN prior to each SQL stmt */
u8 autoEQPtest; /* autoEQP is in test mode */
u8 autoEQPtrace; /* autoEQP is in trace mode */
u8 scanstatsOn; /* True to display scan stats before each finalize */
u8 doXdgOpen; /* Invoke start/open/xdg-open in output_reset() */
u8 nEqpLevel; /* Depth of the EQP output graph */
u8 eTraceType; /* SHELL_TRACE_* value for type of trace */
u8 bSafeMode; /* True when unsafe operations are prohibited */
u8 bSafeModeFuture; /* See updateSafeMode regarding use of this. */
u8 bAllowSysDump; /* Allow .dump use for sqlite_* tables. */
u8 bExtendedDotCmds; /* Bits set to enable various shell extensions */
/* Output mode state subject to systematic save/restore: (See OM_STATE.) */
u8 showHeader; /* True to show column names in List or Column mode */
u16 shellFlgs; /* Various flags */
u8 mode; /* An output mode setting */
ColModeOpts cmOpts; /* Option values affecting columnar mode output */
char colSeparator[20]; /* Column separator character for several modes */
char rowSeparator[20]; /* Row separator character for MODE_Ascii */
/* Output mode state-keep for systematic save/restore: (See OM_STATE.) */
u8 nSavedModes; /* number of valid items in next array */
OutputModeSave *pModeStack[MODE_STACK_MAX]; /* saved mode data buffers */
/* Output mode state-keep for certain save/restore operations: */
u8 cMode; /* temporary output mode for the current query */
u8 normalMode; /* Output mode before ".explain on" */
char nullValue[20]; /* Text to print for NULL retrieved from database */
unsigned statsOn; /* True to display memory stats before each finalize */
unsigned mEqpLines; /* Mask of vertical lines in the EQP output graph */
int outCount; /* Revert to stdout when reaching zero */
int inputNesting; /* Track nesting level of .read and other redirects */
InSource *pInSource; /* Read commands and SQL from this stream source */
FILE *out; /* Write results here */
FILE *traceOut; /* Output for sqlite3_trace() */
int nErr; /* Number of errors seen */
int writableSchema; /* True if PRAGMA writable_schema=ON */
int nCheck; /* Number of ".check" commands run */
unsigned nProgress; /* Number of progress callbacks encountered */
unsigned mxProgress; /* Maximum progress callbacks before failing */
unsigned flgProgress; /* Flags for the progress callback */
char *zTempFile; /* Temporary file that might need deleting */
char *zEditor; /* Name of editor if non-zero, then deletable */
char zTestcase[30]; /* Name of current test case */
char outfile[FILENAME_MAX]; /* Filename for *out */
sqlite3_stmt *pStmt; /* Current statement if any. */
FILE *pLog; /* Write log output here */
struct AuxDb { /* Storage space for auxiliary database connections */
sqlite3 *db; /* Connection pointer */
const char *zDbFilename; /* Filename used to open the connection */
char *zFreeOnClose; /* Free this memory allocation on close */
#if defined(SQLITE_ENABLE_SESSION)
int nSession; /* Number of active sessions */
OpenSession aSession[4]; /* Array of sessions. [0] is in focus. */
#endif
} aAuxDb[5], /* Array of all database connections */
*pAuxDb; /* Currently active database connection */
int *aiIndent; /* Array of indents used in MODE_Explain */
int nIndent; /* Size of array aiIndent[] */
int iIndent; /* Index of current op in aiIndent[] */
char *zNonce; /* Nonce for temporary safe-mode suspension */
EQPGraph sGraph; /* Information for the graphical EXPLAIN QUERY PLAN */
ExpertInfo expert; /* Valid if previous command was ".expert OPT..." */
#ifdef SQLITE_SHELL_FIDDLE
const char * zDefaultDbName; /* Default name for db file (? not used here) */
#endif
#if SHELL_DYNAMIC_EXTENSION
/* extension management */
int numExtLoaded; /* Number of extensions presently loaded or emulated */
ShExtInfo *pShxLoaded; /* Tracking and use info for loaded shell extensions */
int ixExtPending; /* Index of pending extension load operations if !0 */
/* scripting integration */
ScriptSupport *script; /* Scripting support, if any, from loaded extension */
ExtensionId scriptXid; /* Id of extension which is supporting scripting */
/* shell event subscription list */
int numSubscriptions; /* Number of active entries in below list */
struct EventSubscription {
ExtensionId eid;
void *pvUserData;
ShellEventNotify eventHandler;
} *pSubscriptions; /* The current shell event subscriptions */
u8 bDbDispatch; /* Cache fact of dbShell dispatch table */
DotCommand *pUnknown; /* Last registered "unknown" dot command */
#endif
#if SHELL_DATAIO_EXT
ExportHandler *pFreeformExporter; /* Default free-form mode exporter */
ExportHandler *pColumnarExporter; /* Default columnar mode exporter */
ExportHandler *pActiveExporter; /* Presently active exporter */
#endif
ShellExState *pSXS; /* Pointer to companion, exposed shell state */
} ShellInState;
#ifdef SQLITE_SHELL_FIDDLE
/* For WASM, keep a static instance so pseudo-main can be called repeatedly. */
static ShellInState shellStateI;
static ShellExState shellStateX;
#endif
/*
** Limit input nesting via .read or any other input redirect.
** It's not too expensive, so a generous allowance can be made.
*/
#define MAX_INPUT_NESTING 25
/*
** This procedure updates the bSafeMode flag after completion of any
** operation (dot-command, SQL submission, or script execution) that
** counts as one for which safe mode might be suspended.
** bSafeModeFuture has 3 states salient here:
** equal 0 => Safe mode is and will remain inactive.
** equal 1 => Safe mode is and will remain active.
** N > 1 => Safe mode is suspended for N-1 operations.
*/
static void updateSafeMode(ShellInState *psi){
switch( psi->bSafeModeFuture ){
case 2:
default:
--psi->bSafeModeFuture;
deliberate_fall_through;
case 0:
psi->bSafeMode = 0;
break;
case 1:
psi->bSafeMode = 1;
}
}
/* Allowed values for ShellInState.autoEQP
*/
#define AUTOEQP_off 0 /* Automatic EXPLAIN QUERY PLAN is off */
#define AUTOEQP_on 1 /* Automatic EQP is on */
#define AUTOEQP_trigger 2 /* On and also show plans for triggers */
#define AUTOEQP_full 3 /* Show full EXPLAIN */
/* Allowed values for ShellInState.openMode
*/
#define SHELL_OPEN_UNSPEC 0 /* No open-mode specified */
#define SHELL_OPEN_NORMAL 1 /* Normal database file */
#define SHELL_OPEN_APPENDVFS 2 /* Use appendvfs */
#define SHELL_OPEN_ZIPFILE 3 /* Use the zipfile virtual table */
#define SHELL_OPEN_READONLY 4 /* Open a normal database read-only */
#define SHELL_OPEN_DESERIALIZE 5 /* Open using sqlite3_deserialize() */
#define SHELL_OPEN_HEXDB 6 /* Use "dbtotxt" output as data source */
/* Allowed values for ShellInState.eTraceType
*/
#define SHELL_TRACE_PLAIN 0 /* Show input SQL text */
#define SHELL_TRACE_EXPANDED 1 /* Show expanded SQL text */
#define SHELL_TRACE_NORMALIZED 2 /* Show normalized SQL text */
/* Bits in the ShellInState.flgProgress variable */
#define SHELL_PROGRESS_QUIET 0x01 /* Omit announcing every progress callback */
#define SHELL_PROGRESS_RESET 0x02 /* Reset the count when the progress
** callback limit is reached, and for each
** top-level SQL statement */
#define SHELL_PROGRESS_ONCE 0x04 /* Cancel the --limit after firing once */
/*
** These are the allowed shellFlgs values
*/
#define SHFLG_Pagecache 0x00000001 /* The --pagecache option is used */
#define SHFLG_Lookaside 0x00000002 /* Lookaside memory is used */
#define SHFLG_Backslash 0x00000004 /* The --backslash option is used */
#define SHFLG_PreserveRowid 0x00000008 /* .dump preserves rowid values */
#define SHFLG_Newlines 0x00000010 /* .dump --newline flag */
#define SHFLG_CountChanges 0x00000020 /* .changes setting */
#define SHFLG_Echo 0x00000040 /* .echo on/off, or --echo setting */
#define SHFLG_HeaderSet 0x00000080 /* showHeader has been specified */
#define SHFLG_DumpDataOnly 0x00000100 /* .dump show data only */
#define SHFLG_DumpNoSys 0x00000200 /* .dump omits system tables */
#define SHFLG_TestingMode 0x00000400 /* allow unsafe testing features */
/*
** Macros for testing and setting shellFlgs
*/
#define ShellHasFlag(psx,X) (((psx)->pSIS->shellFlgs & (X))!=0)
#define ShellSetFlag(psx,X) ((psx)->pSIS->shellFlgs|=(X))
#define ShellClearFlag(psx,X) ((psx)->pSIS->shellFlgs&=(~(X)))
#define ShellSetFlagI(psi,X) ((psi)->shellFlgs|=(X))
#define ShellClearFlagI(psi,X) ((psi)->shellFlgs&=(~(X)))
/*
** These are the allowed modes, in search order (for abbreviation matches.)
*/
#define MODE_Line 0 /* One column per line. Blank line between records */
#define MODE_Column 1 /* One record per line in neat columns */
#define MODE_List 2 /* One record per line with a separator */
#define MODE_Html 3 /* Generate an XHTML table */
#define MODE_Tcl 4 /* Generate ANSI-C or TCL quoted elements */
#define MODE_Csv 5 /* Quote strings, numbers are plain */
#define MODE_Tab 6 /* Transitory, an alias for MODE_List with tabs */
#define MODE_Insert 7 /* Generate SQL "insert" statements */
#define MODE_Quote 8 /* Quote values as for SQL */
#define MODE_Ascii 9 /* Use ASCII unit and record separators (0x1F/0x1E) */
#define MODE_Markdown 10 /* Markdown formatting */
#define MODE_Table 11 /* MySQL-style table formatting */
#define MODE_Box 12 /* Unicode box-drawing characters */
#define MODE_Count 13 /* Output only a count of the rows of output */
#define MODE_Off 14 /* No query output shown */
#define MODE_Json 15 /* Output JSON */
#define MODE_EQP 16 /* Converts EXPLAIN QUERY PLAN output into a graph */
#define MODE_Explain 17 /* Like MODE_Column, but do not truncate data */
#define MODE_Pretty 18 /* Pretty-print schemas */
#define MODE_Semi 19 /* Same as MODE_List but append ";" to each line */
#define MODE_COUNT_OF 20 /* also a known invalid MODE_x value */
/* Note that some of above ordering is assumed for this mode classification. */
#define MODE_IS_COLUMNAR(m) \
( (m)==MODE_Column||((m)>=MODE_Markdown&&(m)<=MODE_Box) )
static struct {
const char *zModeName;
u8 bMayPluralize;
u8 bUserBlocked;
u8 iAliasFor;
} modeDescr[] = {
{ "line", 1, 0, MODE_Line },
{ "column", 1, 0, MODE_Column },
{ "list", 0, 0, MODE_List },
{ "html", 0, 0, MODE_Html },
{ "tcl", 0, 0, MODE_Tcl },
{ "csv", 0, 0, MODE_Csv },
{ "tab", 1, 0, MODE_List },
{ "insert", 0, 0, MODE_Insert },
{ "quote", 0, 0, MODE_Quote },
{ "ascii", 0, 0, MODE_Ascii },
{ "markdown", 0, 0, MODE_Markdown },
{ "table", 0, 0, MODE_Table },
{ "box", 0, 0, MODE_Box },
{ "count", 0, 0, MODE_Count },
{ "off", 0, 0, MODE_Off },
{ "json", 0, 0, MODE_Json },
{ "eqp", 0, 1, MODE_EQP },
{ "explain", 0, 1, MODE_Explain },
{ "prettyprint", 0, 1, MODE_Pretty },
{ "semi", 0, 1, MODE_Semi }
};
/*
** These are the column/row/line separators used by the various
** import/export modes.
*/
#define SEP_Column "|"
#define SEP_Row "\n"
#define SEP_Tab "\t"
#define SEP_Space " "
#define SEP_Comma ","
#define SEP_CrLf "\r\n"
#define SEP_Unit "\x1F"
#define SEP_Record "\x1E"
/*
** A callback for the sqlite3_log() interface.
*/
static void shellLog(void *pArg, int iErrCode, const char *zMsg){
ShellInState *psi = (ShellInState*)pArg;
if( psi->pLog==0 ) return;
utf8_printf(psi->pLog, "(%d) %s\n", iErrCode, zMsg);
fflush(psi->pLog);
}
/*
** SQL function: shell_putsnl(X)
**
** Write the text X to the screen (or whatever output is being directed)
** adding a newline at the end, and then return X. If X is the NULL
** equivalent (due to OOM or otherwise), it acts as an empty string.
*/
static void shellPutsFunc(
sqlite3_context *pCtx,
int nVal,
sqlite3_value **apVal
){
ShellExState *psx = (ShellExState*)sqlite3_user_data(pCtx);
const unsigned char *s = sqlite3_value_text(apVal[0]);
(void)nVal;
utf8_printf(ISS(psx)->out, "%s\n", (s!=0)?(char*)s:"");
sqlite3_result_value(pCtx, apVal[0]);
}
/*
** If in safe mode, print an error message described by the arguments
** and arrange for a semi-abrupt exit. Unsafe actions are blocked
** by their doers calling this and not acting if 1 is returned.
** The process_input() routine detects the semi-abrupt exit, (which
** is indicated by the external shell state member, shellAbruptExit
** being set non-zero), and aborts any further input processing.
**
** The return is 1 if failing for a forbidden unsafe act, 0 otherwise.
*/
static int failIfSafeMode(
ShellExState *psx,
const char *zErrMsg,
...
){
if( ISS(psx)->bSafeMode==1 ){
va_list ap;
char *zMsg;
va_start(ap, zErrMsg);
shell_check_ooms(zMsg = sqlite3_vmprintf(zErrMsg, ap));
va_end(ap);
raw_printf(STD_ERR, "line %d: ", ISS(psx)->pInSource->lineno);
utf8_printf(STD_ERR, "%s\n", zMsg);
sqlite3_free(zMsg);
psx->shellAbruptExit = 0x202;
return 1;
}
return 0;
}
/*
** Emit formatted output to shell's current output, possibly translated
** for the legacy console on the Windows platform. This is exposed as
** a helper for extensions so that they may share a common buffering
** for FILE* output or share output capture when/if that is implemented.
*/
static void utf8_out_printf(ShellExState *p, const char *zFormat, ...){
va_list ap;
va_start(ap, zFormat);
#if defined(_WIN32) || defined(WIN32)
vf_utf8_printf(ISS(p)->out, zFormat, ap);
#else
vfprintf(ISS(p)->out, zFormat, ap);
#endif
va_end(ap);
}
/*
** SQL function: edit(VALUE)
** edit(VALUE,EDITOR)
**
** These steps:
**
** (1) Write VALUE into a temporary file.
** (2) Run program EDITOR on that temporary file.
** (3) Read the temporary file back and return its content as the result.
** (4) Delete the temporary file
**
** If the EDITOR argument is omitted, use the value in the VISUAL
** environment variable. If still there is no EDITOR, through an error.
**
** Also throw an error if the EDITOR program returns a non-zero exit code.
*/
#ifndef SQLITE_NOHAVE_SYSTEM
static void editFunc(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
const char *zEditor;
char *zTempFile = 0;
sqlite3 *db;
char *zCmd = 0;
int bBin;
int rc;
int hasCRNL = 0;
FILE *f = 0;
sqlite3_int64 sz;
sqlite3_int64 x;
unsigned char *p = 0;
if( argc==2 ){
zEditor = (const char*)sqlite3_value_text(argv[1]);
/* If that failed for OOM, just pretend it is not there. */
}else{
zEditor = getenv("VISUAL");
/* Note that this code is NOT threadsafe due to use of the getenv()
** result. Because the shell is single-threaded, this is fine.
** But if the CLI is called as a subroutine in a multi-threaded
** program, adjustments may have to be made. */
}
if( zEditor==0 ){
sqlite3_result_error(context, "no editor for edit()", -1);
return;
}
if( sqlite3_value_type(argv[0])==SQLITE_NULL ){
sqlite3_result_error(context, "NULL input to edit()", -1);
return;
}
db = sqlite3_context_db_handle(context);
sqlite3_file_control(db, 0, SQLITE_FCNTL_TEMPFILENAME, &zTempFile);
if( zTempFile==0 ){
sqlite3_uint64 r = 0;
sqlite3_randomness(sizeof(r), &r);
zTempFile = smprintf("temp%llx", r);
if( zTempFile==0 ){
sqlite3_result_error_nomem(context);
return;
}
}
bBin = sqlite3_value_type(argv[0])==SQLITE_BLOB;
/* When writing the file to be edited, do \n to \r\n conversions on systems
** that want \r\n line endings */
f = fopen(zTempFile, bBin ? "wb" : "w");
if( f==0 ){
sqlite3_result_error(context, "edit() cannot open temp file", -1);
goto edit_func_end;
}
sz = sqlite3_value_bytes(argv[0]);
if( bBin ){
x = fwrite(sqlite3_value_blob(argv[0]), 1, (size_t)sz, f);
}else{
const char *z = (const char*)sqlite3_value_text(argv[0]);
/* Remember whether or not the value originally contained \r\n */
if( z && strstr(z,"\r\n")!=0 ) hasCRNL = 1;
x = fwrite(sqlite3_value_text(argv[0]), 1, (size_t)sz, f);
}
fclose(f);
f = 0;
if( x!=sz ){
sqlite3_result_error(context, "edit() could not write the whole file", -1);
goto edit_func_end;
}
zCmd = smprintf("%s \"%s\"", zEditor, zTempFile);
if( zCmd==0 ){
sqlite3_result_error_nomem(context);
goto edit_func_end;
}
rc = system(zCmd);
sqlite3_free(zCmd);
if( rc ){
sqlite3_result_error(context, "EDITOR returned non-zero", -1);
goto edit_func_end;
}
f = fopen(zTempFile, "rb");
if( f==0 ){
sqlite3_result_error(context,
"edit() cannot reopen temp file after edit", -1);
goto edit_func_end;
}
fseek(f, 0, SEEK_END);
sz = ftell(f);
rewind(f);
p = sqlite3_malloc64( sz+1 );
if( p==0 ){
sqlite3_result_error_nomem(context);
goto edit_func_end;
}
x = fread(p, 1, (size_t)sz, f);
fclose(f);
f = 0;
if( x!=sz ){
sqlite3_result_error(context, "could not read back the whole file", -1);
goto edit_func_end;
}
if( bBin ){
sqlite3_result_blob64(context, p, sz, sqlite3_free);
}else{
sqlite3_int64 i, j;
if( !hasCRNL ){
/* If the file did not originally contain \r\n then convert any new
** \r\n back into \n */
p[sz] = 0;
for(i=j=0; i<sz; i++){
if( p[i]=='\r' && p[i+1]=='\n' ) i++;
p[j++] = p[i];
}
sz = j;
p[sz] = 0;
}
sqlite3_result_text64(context, (const char*)p, sz,
sqlite3_free, SQLITE_UTF8);
}
p = 0;
edit_func_end:
if( f ) fclose(f);
unlink(zTempFile);
sqlite3_free(zTempFile);
sqlite3_free(p);
}
#endif /* SQLITE_NOHAVE_SYSTEM */
/*
** Save or restore the current output mode, partially per spec. (OM_STATE)
*/
typedef enum {
SWM_showHeader = 1, SWM_shellFlags = 2, SWM_mode = 4, SWM_cmOpts = 8,
SWM_colSeparator = 0x10, SWM_rowSeparator = 0x20, SWM_everything = 0x3F,
SWM_CountOf = 6
} SaveWhatMode;
/* This is available in most C89+ C compilers as offsetof(...), but since we
* cater to the most arcane C89-like compilers around, define it for sure:
*/
#define MEMBER_OFFSET(stype, member) ((size_t)&(((stype*)0)->member))
#define MEMBER_SIZEOF(stype, member) (sizeof(((stype*)0)->member))
static struct {
size_t offset;
size_t size;
} outputModeCopy[] = {
#define SS_MEMBER_COPY(mn) \
{ MEMBER_OFFSET(ShellInState,mn), MEMBER_SIZEOF(ShellInState,mn) }
SS_MEMBER_COPY(showHeader), SS_MEMBER_COPY(shellFlgs),
SS_MEMBER_COPY(mode), SS_MEMBER_COPY(cmOpts),
SS_MEMBER_COPY(colSeparator), SS_MEMBER_COPY(rowSeparator)
#undef SS_MEMBER_COPY
};
/* Allocate a buffer, copy requested output mode data to it, and return it.
* This never fails under OOM conditions. Instead, it returns 0.
*/
static OutputModeSave *outputModeSave(ShellInState *psi, SaveWhatMode w){
u16 what = (u16)w;
int i, nAlloc = sizeof(what)+1, mask = 1;
char *pSaved = 0, *pFill;
for( i=0; i<SWM_CountOf; mask<<=1, ++i ){
if( (what & mask)!=0 ) nAlloc += (int)outputModeCopy[i].size;
}
assert(i==ArraySize(outputModeCopy));
pSaved = sqlite3_malloc(nAlloc);
if( pSaved==0 ) return 0;
*(u16 *)pSaved = what;
pFill = pSaved + sizeof(what);
for( mask=1, i=0; i<SWM_CountOf; mask<<=1, ++i ){
if( (what & mask)!=0 ){
size_t nb = outputModeCopy[i].size;
memcpy(pFill, ((char*)psi)+outputModeCopy[i].offset, nb);
pFill += nb;
}
}
*pFill = 0xA5;
return (OutputModeSave *)pSaved;
}
/* From a buffer returned by outputModeSave, restore output mode data.
* The buffer is freed and its pointer is invalidated.
* If called with some other buffer, results are undefined, likely bad.
*/
static void outputModeRestore(ShellInState *psi, OutputModeSave *pSaved){
sqlite3_uint64 nA = sqlite3_msize(pSaved);
u16 what = (nA>sizeof(what))? *((u16 *)pSaved) : 0;
int i, nAlloc = sizeof(what)+1, mask = 1;
char *pTake = (char *)pSaved + sizeof(what);
for( i=0; i<SWM_CountOf && nAlloc<nA; mask<<=1, ++i ){
if( (what & mask)!=0 ){
size_t nb = outputModeCopy[i].size;
memcpy(((char*)psi)+outputModeCopy[i].offset, pTake, nb);
pTake += nb;
}
}
assert(*pTake==(char)0xA5);
sqlite3_free(pSaved);
}
/*
** Save or restore the current output mode, in whole or in part.
** A save may abort on OOM.
*/
static void outputModePushSome(ShellInState *psi, SaveWhatMode w){
OutputModeSave *pOMS;
assert(psi->nSavedModes<MODE_STACK_MAX); /* Fail hard for this logic error. */
if( psi->nSavedModes>=MODE_STACK_MAX ) return;
pOMS = outputModeSave(psi, w);
shell_check_ooms(pOMS);
psi->pModeStack[psi->nSavedModes++] = pOMS;
}
static void outputModePush(ShellInState *psi){
outputModePushSome(psi, SWM_everything);
}
static void outputModePop(ShellInState *p){
OutputModeSave *pOMS;
assert(p->nSavedModes>0); /* Should not be here unless something pushed. */
if( p->nSavedModes==0 ) return;
pOMS = p->pModeStack[--p->nSavedModes];
assert(pOMS!=0);
p->pModeStack[p->nSavedModes] = 0;
outputModeRestore(p, pOMS);
}
/*
** Output the given string as a hex-encoded blob (eg. X'1234' )
*/
static void output_hex_blob(FILE *out, const void *pBlob, int nBlob){
int i;
unsigned char *aBlob = (unsigned char*)pBlob;
char *zStr = sqlite3_malloc(nBlob*2 + 1);
shell_check_ooms(zStr);
for(i=0; i<nBlob; i++){
static const char aHex[] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
zStr[i*2] = aHex[ (aBlob[i] >> 4) ];
zStr[i*2+1] = aHex[ (aBlob[i] & 0x0F) ];
}
zStr[i*2] = '\0';
raw_printf(out,"X'%s'", zStr);
sqlite3_free(zStr);
}
/*
** Find a string that is not found anywhere in z[]. Return a pointer
** to that string.
**
** Try to use zA and zB first. If both of those are already found in z[]
** then make up some string and store it in the buffer zBuf.
** The length of this buffer must be no less than SHELL_QESC_GENLEN .
*/
#define SHELL_QESC_GENLEN 20
static const char *unused_string(
const char *z, /* Result must not appear anywhere in z */
const char *zA, const char *zB, /* Try these first */
char *zBuf /* Space to store a generated string */
){
unsigned i = 0;
if( strstr(z, zA)==0 ) return zA;
if( strstr(z, zB)==0 ) return zB;
do{
sqlite3_snprintf(SHELL_QESC_GENLEN, zBuf,"(%s%u)", zA, i++);
}while( strstr(z,zBuf)!=0 );
return zBuf;
}
/*
** Output the given string as a quoted string using SQL quoting conventions.
**
** See also: output_quoted_escaped_string()
*/
static void output_quoted_string(FILE *out, const char *z){
int i;
char c;
setBinaryMode(out, 1);
if( z==0 ) return;
for(i=0; (c = z[i])!=0 && c!='\''; i++){}
if( c==0 ){
utf8_printf(out,"'%s'",z);
}else{
raw_printf(out, "'");
while( *z ){
for(i=0; (c = z[i])!=0 && c!='\''; i++){}
if( c=='\'' ) i++;
if( i ){
utf8_printf(out, "%.*s", i, z);
z += i;
}
if( c=='\'' ){
raw_printf(out, "'");
continue;
}
if( c==0 ){
break;
}
z++;
}
raw_printf(out, "'");
}
setTextMode(out, 1);
}
/*
** Output the given string as a quoted string using SQL quoting conventions.
** Additionallly , escape the "\n" and "\r" characters so that they do not
** get corrupted by end-of-line translation facilities in some operating
** systems.
**
** This is like output_quoted_string() but with the addition of the \r\n
** escape mechanism.
*/
static void output_quoted_escaped_string(FILE *out, const char *z){
int i;
char c;
setBinaryMode(out, 1);
for(i=0; (c = z[i])!=0 && c!='\'' && c!='\n' && c!='\r'; i++){}
if( c==0 ){
utf8_printf(out,"'%s'",z);
}else{
const char *zNL = 0;
const char *zCR = 0;
int nNL = 0;
int nCR = 0;
char zBuf1[SHELL_QESC_GENLEN], zBuf2[SHELL_QESC_GENLEN];
for(i=0; z[i]; i++){
if( z[i]=='\n' ) nNL++;
if( z[i]=='\r' ) nCR++;
}
if( nNL ){
raw_printf(out, "replace(");
zNL = unused_string(z, "\\n", "\\012", zBuf1);
}
if( nCR ){
raw_printf(out, "replace(");
zCR = unused_string(z, "\\r", "\\015", zBuf2);
}
raw_printf(out, "'");
while( *z ){
for(i=0; (c = z[i])!=0 && c!='\n' && c!='\r' && c!='\''; i++){}
if( c=='\'' ) i++;
if( i ){
utf8_printf(out, "%.*s", i, z);
z += i;
}
if( c=='\'' ){
raw_printf(out, "'");
continue;
}
if( c==0 ){
break;
}
z++;
if( c=='\n' ){
raw_printf(out, "%s", zNL);
continue;
}
raw_printf(out, "%s", zCR);
}
raw_printf(out, "'");
if( nCR ){
raw_printf(out, ",'%s',char(13))", zCR);
}
if( nNL ){
raw_printf(out, ",'%s',char(10))", zNL);
}
}
setTextMode(out, 1);
}
/*
** Output the given string as a quoted according to C or TCL quoting rules.
*/
static void output_c_string(FILE *out, const char *z){
unsigned int c;
fputc('"', out);
while( (c = *(z++))!=0 ){
if( c=='\\' ){
fputc(c, out);
fputc(c, out);
}else if( c=='"' ){
fputc('\\', out);
fputc('"', out);
}else if( c=='\t' ){
fputc('\\', out);
fputc('t', out);
}else if( c=='\n' ){
fputc('\\', out);
fputc('n', out);
}else if( c=='\r' ){
fputc('\\', out);
fputc('r', out);
}else if( !isprint(c&0xff) ){
raw_printf(out, "\\%03o", c&0xff);
}else{
fputc(c, out);
}
}
fputc('"', out);
}
/*
** Output the given string as a quoted according to JSON quoting rules.
*/
static void output_json_string(FILE *out, const char *z, i64 n){
unsigned int c;
if( z==0 ) z = "";
if( n<0 ) n = strlen(z);
fputc('"', out);
while( n-- ){
c = *(z++);
if( c=='\\' || c=='"' ){
fputc('\\', out);
fputc(c, out);
}else if( c<=0x1f ){
fputc('\\', out);
if( c=='\b' ){
fputc('b', out);
}else if( c=='\f' ){
fputc('f', out);
}else if( c=='\n' ){
fputc('n', out);
}else if( c=='\r' ){
fputc('r', out);
}else if( c=='\t' ){
fputc('t', out);
}else{
raw_printf(out, "u%04x",c);
}
}else{
fputc(c, out);
}
}
fputc('"', out);
}
/*
** Output the given string with characters that are special to
** HTML escaped.
*/
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]!='\"'
&& z[i]!='\'';
i++){}
if( i>0 ){
utf8_printf(out,"%.*s",i,z);
}
if( z[i]=='<' ){
raw_printf(out,"&lt;");
}else if( z[i]=='&' ){
raw_printf(out,"&amp;");
}else if( z[i]=='>' ){
raw_printf(out,"&gt;");
}else if( z[i]=='\"' ){
raw_printf(out,"&quot;");
}else if( z[i]=='\'' ){
raw_printf(out,"&#39;");
}else{
break;
}
z += i + 1;
}
}
/*
** If a field contains any character identified by a 1 in the following
** 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,
};
/*
** Output a single term of CSV. Actually, p->colSeparator is used for
** the separator, which may or may not be a comma. p->nullValue is
** the null value. Strings are quoted if necessary. The separator
** is only issued if bSep is true.
** This routine may abort under OOM conditions.
*/
static void output_csv(ShellExState *psx, const char *z, int bSep){
FILE *out = ISS(psx)->out;
char *zColSep = psx->zFieldSeparator;
if( z==0 ){
utf8_printf(out,"%s",psx->zNullValue);
}else{
unsigned i;
for(i=0; z[i]; i++){
if( needCsvQuote[((unsigned char*)z)[i]] ){
i = 0;
break;
}
}
if( i==0 || strstr(z, zColSep)!=0 ){
char *zQuoted = smprintf("\"%w\"", z);
shell_check_ooms(zQuoted);
utf8_printf(out, "%s", zQuoted);
sqlite3_free(zQuoted);
}else{
utf8_printf(out, "%s", z);
}
}
if( bSep ){
utf8_printf(out, "%s", zColSep);
}
}
/*
** Interrupt running VDBE (if any). The mutex on globalDb is taken to ensure
** the active DB is not closed as this interrupt is done. Return SQLITE_OK
** if able to do so (or no DB is active), else SQLITE_ERROR.
*/
static int active_db_interrupt(void){
int itry = 0;
if( !globalDb ) return SQLITE_OK;
while( itry < 10 ){
if( sqlite3_mutex_try(pGlobalDbLock)==SQLITE_OK ){
if( globalDb ) sqlite3_interrupt(GLOBAL_DB);
sqlite3_mutex_leave(pGlobalDbLock);
return SQLITE_OK;
}else{
sqlite3_sleep(50);
++itry;
}
}
return SQLITE_ERROR;
}
/*
** This routine runs when the user presses Ctrl-C
** This may need some changes for embedability, perhaps a client-
** defined action when/if DB operation becomes uninterruptible.
*/
static void interrupt_handler(int NotUsed){
UNUSED_PARAMETER(NotUsed);
if( ++seenInterrupt>1 || active_db_interrupt()!=SQLITE_OK ){
/* Apparently, no polite interruption is working. (Something is
** very busy under mutex protection.) So quit altogether. */
utf8_printf(STD_ERR, "Forced ^C exit.\n");
exit(1);
}
}
#if (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE)
/*
** This routine runs for console events (e.g. Ctrl-C) on Win32
*/
static BOOL WINAPI ConsoleCtrlHandler(
DWORD dwCtrlType /* One of the CTRL_*_EVENT constants */
){
if( dwCtrlType==CTRL_C_EVENT ){
interrupt_handler(0);
return TRUE;
}
return FALSE;
}
#endif
#ifndef SQLITE_OMIT_AUTHORIZATION
/*
** This authorizer runs in safe mode.
*/
static int safeModeAuth(
void *pClientData,
int op,
const char *zA1,
const char *zA2,
const char *zA3,
const char *zA4
){
ShellExState *psx = (ShellExState*)pClientData;
static const char *azProhibitedFunctions[] = {
"edit",
"fts3_tokenizer",
"load_extension",
"readfile",
"writefile",
"zipfile",
"zipfile_cds",
};
UNUSED_PARAMETER(zA1);
UNUSED_PARAMETER(zA3);
UNUSED_PARAMETER(zA4);
switch( op ){
case SQLITE_ATTACH: {
#ifndef SQLITE_SHELL_FIDDLE
/* In WASM builds the filesystem is a virtual sandbox, so allow ATTACH. */
if ( failIfSafeMode(psx, "cannot run ATTACH in safe mode") )
return SQLITE_ERROR;
#endif
break;
}
case SQLITE_FUNCTION: {
int i;
for(i=0; i<ArraySize(azProhibitedFunctions); i++){
if( sqlite3_stricmp(zA2, azProhibitedFunctions[i])==0 ){
if( failIfSafeMode(psx, "cannot use the %s() function in safe mode",
azProhibitedFunctions[i]) )
return SQLITE_ERROR;
}
}
break;
}
}
return SQLITE_OK;
}
/*
** 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
){
ShellExState *psx = (ShellExState*)pClientData;
ShellInState *psi = ISS(psx);
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(psi->out, "authorizer: %s", azAction[op]);
for(i=0; i<4; i++){
raw_printf(psi->out, " ");
if( az[i] ){
output_c_string(psi->out, az[i]);
}else{
raw_printf(psi->out, "NULL");
}
}
raw_printf(psi->out, "\n");
if( psi->bSafeMode ) (void)safeModeAuth(pClientData, op, zA1, zA2, zA3, zA4);
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.
**
** If the schema statement in z[] contains a start-of-comment and if
** sqlite3_complete() returns false, try to terminate the comment before
** printing the result. https://sqlite.org/forum/forumpost/d7be961c5c
*/
static void printSchemaLine(FILE *out, const char *z, const char *zTail){
char *zToFree = 0;
if( z==0 ) return;
if( zTail==0 ) return;
if( zTail[0]==';' && (strstr(z, "/*")!=0 || strstr(z,"--")!=0) ){
const char *zOrig = z;
static const char *azTerm[] = { "", "*/", "\n" };
int i, rc;
for(i=0; i<ArraySize(azTerm); i++){
char *zNew = smprintf("%s%s;", zOrig, azTerm[i]);
shell_check_ooms(zNew);
if( 1==(rc = sqlite3_complete(zNew)) ){
size_t n = strlen(zNew);
zNew[n-1] = 0;
zToFree = zNew;
z = zNew;
break;
}
sqlite3_free(zNew);
if( rc==SQLITE_NOMEM ) shell_out_of_memory();
}
}
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);
}
sqlite3_free(zToFree);
}
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;
}
/*
** Return true if string z[] has nothing but whitespace and comments to the
** end of the first line.
*/
static int wsToEol(const char *z){
int i;
for(i=0; z[i]; i++){
if( z[i]=='\n' ) return 1;
if( IsSpace(z[i]) ) continue;
if( z[i]=='-' && z[i+1]=='-' ) return 1;
return 0;
}
return 1;
}
/*
** Add a new entry to the EXPLAIN QUERY PLAN data
*/
static void eqp_append(ShellInState *psi, int iEqpId, int p2,
const char *zText){
EQPGraphRow *pNew;
i64 nText;
if( zText==0 ) return;
nText = strlen(zText);
if( psi->autoEQPtest ){
utf8_printf(psi->out, "%d,%d,%s\n", iEqpId, p2, zText);
}
pNew = sqlite3_malloc64( sizeof(*pNew) + nText );
shell_check_ooms(pNew);
pNew->iEqpId = iEqpId;
pNew->iParentId = p2;
memcpy(pNew->zText, zText, nText+1);
pNew->pNext = 0;
if( psi->sGraph.pLast ){
psi->sGraph.pLast->pNext = pNew;
}else{
psi->sGraph.pRow = pNew;
}
psi->sGraph.pLast = pNew;
}
/*
** Free and reset the EXPLAIN QUERY PLAN data that has been collected
** in p->sGraph.
*/
static void eqp_reset(ShellInState *psi){
EQPGraphRow *pRow, *pNext;
for(pRow = psi->sGraph.pRow; pRow; pRow = pNext){
pNext = pRow->pNext;
sqlite3_free(pRow);
}
memset(&psi->sGraph, 0, sizeof(psi->sGraph));
}
/* Return the next EXPLAIN QUERY PLAN line with iEqpId that occurs after
** pOld, or return the first such line if pOld is NULL
*/
static EQPGraphRow *eqp_next_row(ShellInState *psi, int iEqpId,
EQPGraphRow *pOld){
EQPGraphRow *pRow = pOld ? pOld->pNext : psi->sGraph.pRow;
while( pRow && pRow->iParentId!=iEqpId ) pRow = pRow->pNext;
return pRow;
}
/* Render a single level of the graph that has iEqpId as its parent. Called
** recursively to render sublevels.
*/
static void eqp_render_level(ShellInState *psi, int iEqpId){
EQPGraphRow *pRow, *pNext;
i64 n = strlen(psi->sGraph.zPrefix);
char *z;
for(pRow = eqp_next_row(psi, iEqpId, 0); pRow; pRow = pNext){
pNext = eqp_next_row(psi, iEqpId, pRow);
z = pRow->zText;
utf8_printf(psi->out, "%s%s%s\n", psi->sGraph.zPrefix,
pNext ? "|--" : "`--", z);
if( n<(i64)sizeof(psi->sGraph.zPrefix)-7 ){
memcpy(&psi->sGraph.zPrefix[n], pNext ? "| " : " ", 4);
eqp_render_level(psi, pRow->iEqpId);
psi->sGraph.zPrefix[n] = 0;
}
}
}
/*
** Display and reset the EXPLAIN QUERY PLAN data
*/
static void eqp_render(ShellInState *psi, i64 nCycle){
EQPGraphRow *pRow = psi->sGraph.pRow;
if( pRow ){
if( pRow->zText[0]=='-' ){
if( pRow->pNext==0 ){
eqp_reset(psi);
return;
}
utf8_printf(psi->out, "%s\n", pRow->zText+3);
psi->sGraph.pRow = pRow->pNext;
sqlite3_free(pRow);
}else if( nCycle>0 ){
utf8_printf(psi->out, "QUERY PLAN (cycles=%lld [100%%])\n", nCycle);
}else{
utf8_printf(psi->out, "QUERY PLAN\n");
}
psi->sGraph.zPrefix[0] = 0;
eqp_render_level(psi, 0);
eqp_reset(psi);
}
}
#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
/*
** Progress handler callback.
*/
static int progress_handler(void *pClientData) {
ShellInState *psi = (ShellInState*)pClientData;
psi->nProgress++;
if( psi->nProgress>=psi->mxProgress && psi->mxProgress>0 ){
raw_printf(psi->out, "Progress limit reached (%u)\n", psi->nProgress);
if( psi->flgProgress & SHELL_PROGRESS_RESET ) psi->nProgress = 0;
if( psi->flgProgress & SHELL_PROGRESS_ONCE ) psi->mxProgress = 0;
return 1;
}
if( (psi->flgProgress & SHELL_PROGRESS_QUIET)==0 ){
raw_printf(psi->out, "Progress %u\n", psi->nProgress);
}
return 0;
}
#endif /* SQLITE_OMIT_PROGRESS_CALLBACK */
/* For debugging or experimentation, the shell DB can be made file-based. */
#ifndef SHELL_DB_FILE
# define SHELL_DB_STORE ":memory:"
#else
# define SHELL_DB_STORE SHELL_STRINGIFY(SHELL_DB_FILE)
#endif
#define SHELL_DISP_SCHEMA "main"
#define SHELL_DISP_TAB "ShellCommands"
#define SHELL_AHELP_TAB "ShellAdHocHelp"
#define SHELL_DISP_VIEW "ShellActiveCmds"
#define SHELL_HELP_VIEW "ShellHelpedCmds"
/*
** Ensure dbShell exists and return SQLITE_OK,
** or complain and return SQLITE_ERROR.
*/
static int ensure_shell_db(ShellExState *psx){
if( psx->dbShell!=0 ) return SQLITE_OK;
else{
int rc = sqlite3_open(SHELL_DB_STORE, &psx->dbShell);
if( rc!=SQLITE_OK ){
utf8_printf(STD_ERR, "Shell DB open failure: %s\n", sqlite3_errstr(rc));
return SQLITE_ERROR;
}
#ifndef SQLITE_NOHAVE_SYSTEM
sqlite3_create_function(psx->dbShell, "edit", 1,
SQLITE_UTF8, 0, editFunc, 0, 0);
sqlite3_create_function(psx->dbShell, "edit", 2,
SQLITE_UTF8, 0, editFunc, 0, 0);
#endif
return rc;
}
}
/* Tell whether the above-created table exists, return true iff exists. */
static int dispatch_table_exists(sqlite3 *dbs){
return sqlite3_table_column_metadata
(dbs, SHELL_DISP_SCHEMA, SHELL_DISP_TAB, 0, 0, 0, 0, 0, 0)==SQLITE_OK;
}
static int ensure_dispatch_table(ShellExState *psx){
int rc = ensure_shell_db(psx);
int i;
if( rc==SQLITE_OK ){
char *zErr = 0;
static const char *azDDL[] = {
"BEGIN TRANSACTION",
#ifdef SHELL_DB_FILE
"DROP TABLE IF EXISTS "SHELL_DISP_TAB,
"DROP VIEW IF EXISTS "SHELL_DISP_VIEW,
"DROP TABLE IF EXISTS "SHELL_AHELP_TAB,
"DROP VIEW IF EXISTS "SHELL_HELP_VIEW,
#endif
"CREATE TABLE "SHELL_AHELP_TAB"("
"name TEXT, extIx INT, helpText TEXT,"
"PRIMARY KEY(name,extIx)) WITHOUT ROWID",
"CREATE TABLE "SHELL_DISP_TAB"("
/* name, extIx, cmdIx */
"name TEXT, extIx INT, cmdIx INT,"
"PRIMARY KEY(extIx,cmdIx)) WITHOUT ROWID",
"CREATE VIEW "SHELL_DISP_VIEW" AS"
" SELECT s.name AS name,"
" max(s.extIx) AS extIx, s.cmdIx AS cmdIx"
" FROM "SHELL_DISP_TAB" s GROUP BY name"
" ORDER BY name",
"CREATE VIEW "SHELL_HELP_VIEW" AS"
/* name, extIx, cmdIx, help */
" SELECT s.name AS name, max(s.extIx) AS extIx,"
" s.cmdIx AS cmdIx, NULL as help"
" FROM "SHELL_DISP_TAB" s GROUP BY name"
" UNION"
" SELECT s.name AS name, max(s.extIx) AS extIx,"
" -1 AS cmdIx, s.helpText AS help"
" FROM "SHELL_AHELP_TAB" s GROUP BY name"
" ORDER BY name",
"COMMIT TRANSACTION"
};
if( dispatch_table_exists(psx->dbShell) ) return rc;
/* Create the dispatch table and view on it. */
sstr_ptr_holder(&zErr);
for( i=0; i<ArraySize(azDDL); ++i ){
rc = s3_exec_noom(psx->dbShell, azDDL[i],0,0,&zErr);
if( rc!=SQLITE_OK || zErr!=0 ){
utf8_printf(STD_ERR, "Shell DB init failure, %s\n", zErr? zErr : "?");
rc = SQLITE_ERROR;
if( i+1<ArraySize(azDDL) ){
s3_exec_noom(psx->dbShell, "ROLLBACK TRANSACTION", 0,0,0);
}
break;
}
}
release_holder();
}
return rc;
}
/*
** Skip over whitespace, returning remainder.
*/
static const char *skipWhite( const char *z ){
while( IsSpace(*z) ) ++z;
return z;
}
/*
** Print N dashes
*/
static void print_dashes(FILE *out, int N){
const char zDash[] = "--------------------------------------------------";
const int nDash = sizeof(zDash) - 1;
while( N>nDash ){
fputs(zDash, out);
N -= nDash;
}
raw_printf(out, "%.*s", N, zDash);
}
/*
** Print a markdown or table-style row separator using ascii-art
*/
static void print_row_separator(
ShellExState *psx,
int nArg,
const char *zSep
){
int i;
if( nArg>0 ){
fputs(zSep, ISS(psx)->out);
print_dashes(ISS(psx)->out, psx->pHaveWidths[0]+2);
for(i=1; i<nArg; i++){
fputs(zSep, ISS(psx)->out);
print_dashes(ISS(psx)->out, psx->pHaveWidths[i]+2);
}
fputs(zSep, ISS(psx)->out);
}
fputs("\n", ISS(psx)->out);
}
/*
** This is the callback routine that the shell
** invokes for each row of a query result.
*/
static int shell_callback(
void *pArg,
int nArg, /* Number of result columns */
char **azArg, /* Text of each result column */
char **azCol, /* Column names */
int *aiType /* Column types. Might be NULL */
){
int i;
ShellExState *psx = (ShellExState*)pArg;
ShellInState *psi = ISS(psx);
FILE *out = psi->out;
if( azArg==0 ) return 0;
switch( psi->cMode ){
case MODE_Count:
case MODE_Off: {
break;
}
case MODE_Line: {
int w = 5;
if( azArg==0 ) break;
for(i=0; i<nArg; i++){
int len = strlen30(azCol[i] ? azCol[i] : "");
if( len>w ) w = len;
}
if( psx->resultCount++>0 ) utf8_printf(out, "%s", psi->rowSeparator);
for(i=0; i<nArg; i++){
utf8_printf(out,"%*s = %s%s", w, azCol[i],
azArg[i] ? azArg[i] : psi->nullValue, psi->rowSeparator);
}
break;
}
case MODE_Explain: {
static const int aExplainWidth[] = {4, 13, 4, 4, 4, 13, 2, 13};
if( nArg>ArraySize(aExplainWidth) ){
nArg = ArraySize(aExplainWidth);
}
if( psx->resultCount++==0 ){
for(i=0; i<nArg; i++){
int w = aExplainWidth[i];
utf8_width_print(out, w, azCol[i]);
fputs(i==nArg-1 ? "\n" : " ", out);
}
for(i=0; i<nArg; i++){
int w = aExplainWidth[i];
print_dashes(out, w);
fputs(i==nArg-1 ? "\n" : " ", out);
}
}
if( azArg==0 ) break;
for(i=0; i<nArg; i++){
int w = aExplainWidth[i];
if( i==nArg-1 ) w = 0;
if( azArg[i] && strlenChar(azArg[i])>w ){
w = strlenChar(azArg[i]);
}
if( i==1 && psi->aiIndent && psi->pStmt ){
if( psi->iIndent<psi->nIndent ){
utf8_printf(out, "%*.s", psi->aiIndent[psi->iIndent], "");
}
psi->iIndent++;
}
utf8_width_print(out, w, azArg[i] ? azArg[i] : psi->nullValue);
fputs(i==nArg-1 ? "\n" : " ", out);
}
break;
}
case MODE_Semi: { /* .schema and .fullschema output */
printSchemaLine(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(out, "%s;\n", azArg[0]);
break;
}
z = smprintf("%s", azArg[0]);
shell_check_ooms(z);
j = 0;
for(i=0; IsSpace(z[i]); i++){}
for(; (c = z[i])!=0; i++){
if( IsSpace(c) ){
if( z[j-1]=='\r' ) z[j-1] = '\n';
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++){ /* Copy from z[i] back to z[j] */
if( c==cEnd ){
cEnd = 0;
}else if( c=='"' || c=='\'' || c=='`' ){
cEnd = c;
}else if( c=='[' ){
cEnd = ']';
}else if( c=='-' && z[i+1]=='-' ){
cEnd = '\n';
}else if( c=='(' ){
nParen++;
}else if( c==')' ){
nParen--;
if( nLine>0 && nParen==0 && j>0 ){
printSchemaLineN(out, z, j, "\n");
j = 0;
}
}
z[j++] = c;
if( nParen==1 && cEnd==0
&& (c=='(' || c=='\n' || (c==',' && !wsToEol(z+i+1)))
){
if( c=='\n' ) j--;
printSchemaLineN(out, z, j, "\n ");
j = 0;
nLine++;
while( IsSpace(z[i+1]) ){ i++; }
}
}
z[j] = 0;
}
printSchemaLine(out, z, ";\n");
sqlite3_free(z);
break;
}
case MODE_List: {
if( psx->resultCount++==0 && psi->showHeader ){
for(i=0; i<nArg; i++){
utf8_printf(out,"%s%s",azCol[i],
i==nArg-1 ? psi->rowSeparator : psi->colSeparator);
}
}
if( azArg==0 ) break;
for(i=0; i<nArg; i++){
char *z = azArg[i];
if( z==0 ) z = psi->nullValue;
utf8_printf(out, "%s", z);
if( i<nArg-1 ){
utf8_printf(out, "%s", psi->colSeparator);
}else{
utf8_printf(out, "%s", psi->rowSeparator);
}
}
break;
}
case MODE_Html: {
if( psx->resultCount++==0 && psi->showHeader ){
raw_printf(out,"<TR>");
for(i=0; i<nArg; i++){
raw_printf(out,"<TH>");
output_html_string(out, azCol[i]);
raw_printf(out,"</TH>\n");
}
raw_printf(out,"</TR>\n");
}
if( azArg==0 ) break;
raw_printf(out,"<TR>");
for(i=0; i<nArg; i++){
raw_printf(out,"<TD>");
output_html_string(out, azArg[i] ? azArg[i] : psi->nullValue);
raw_printf(out,"</TD>\n");
}
raw_printf(out,"</TR>\n");
break;
}
case MODE_Tcl: {
if( psx->resultCount++==0 && psi->showHeader ){
for(i=0; i<nArg; i++){
output_c_string(out,azCol[i] ? azCol[i] : "");
if(i<nArg-1) utf8_printf(out, "%s", psi->colSeparator);
}
utf8_printf(out, "%s", psi->rowSeparator);
}
if( azArg==0 ) break;
for(i=0; i<nArg; i++){
output_c_string(out, azArg[i] ? azArg[i] : psi->nullValue);
if(i<nArg-1) utf8_printf(out, "%s", psi->colSeparator);
}
utf8_printf(out, "%s", psi->rowSeparator);
break;
}
case MODE_Csv: {
setBinaryMode(out, 1);
if( psx->resultCount++==0 && psi->showHeader ){
for(i=0; i<nArg; i++){
output_csv(psx, azCol[i] ? azCol[i] : "", i<nArg-1);
}
utf8_printf(out, "%s", psi->rowSeparator);
}
if( nArg>0 ){
for(i=0; i<nArg; i++){
output_csv(psx, azArg[i], i<nArg-1);
}
utf8_printf(out, "%s", psi->rowSeparator);
}
setTextMode(out, 1);
break;
}
case MODE_Insert: {
if( azArg==0 ) break;
utf8_printf(out,"INSERT INTO %s",psx->zDestTable);
if( psi->showHeader ){
raw_printf(out,"(");
for(i=0; i<nArg; i++){
if( i>0 ) raw_printf(out, ",");
if( quoteChar(azCol[i]) ){
char *z = smprintf("\"%w\"", azCol[i]);
shell_check_ooms(z);
utf8_printf(out, "%s", z);
sqlite3_free(z);
}else{
raw_printf(out, "%s", azCol[i]);
}
}
raw_printf(out,")");
}
psx->resultCount++;
for(i=0; i<nArg; i++){
raw_printf(out, i>0 ? "," : " VALUES(");
if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){
utf8_printf(out,"NULL");
}else if( aiType && aiType[i]==SQLITE_TEXT ){
if( ShellHasFlag(psx, SHFLG_Newlines) ){
output_quoted_string(out, azArg[i]);
}else{
output_quoted_escaped_string(out, azArg[i]);
}
}else if( aiType && aiType[i]==SQLITE_INTEGER ){
utf8_printf(out,"%s", azArg[i]);
}else if( aiType && aiType[i]==SQLITE_FLOAT ){
char z[50];
double r = sqlite3_column_double(psi->pStmt, i);
sqlite3_uint64 ur;
memcpy(&ur,&r,sizeof(r));
if( ur==0x7ff0000000000000LL ){
raw_printf(out, "9.0e+999");
}else if( ur==0xfff0000000000000LL ){
raw_printf(out, "-9.0e+999");
}else{
sqlite3_int64 ir = (sqlite3_int64)r;
if( r==(double)ir ){
sqlite3_snprintf(50,z,"%lld.0", ir);
}else{
sqlite3_snprintf(50,z,"%!.20g", r);
}
raw_printf(out, "%s", z);
}
}else if( aiType && aiType[i]==SQLITE_BLOB && psi->pStmt ){
const void *pBlob = sqlite3_column_blob(psi->pStmt, i);
int nBlob = sqlite3_column_bytes(psi->pStmt, i);
output_hex_blob(out, pBlob, nBlob);
}else if( isNumber(azArg[i], 0) ){
utf8_printf(out,"%s", azArg[i]);
}else if( ShellHasFlag(psx, SHFLG_Newlines) ){
output_quoted_string(out, azArg[i]);
}else{
output_quoted_escaped_string(out, azArg[i]);
}
}
raw_printf(out,");\n");
break;
}
case MODE_Json: {
if( azArg==0 ) break;
if( psx->resultCount==0 ){
fputs("[{", out);
}else{
fputs(",\n{", out);
}
psx->resultCount++;
for(i=0; i<nArg; i++){
output_json_string(out, azCol[i], -1);
putc(':', out);
if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){
fputs("null",out);
}else if( aiType && aiType[i]==SQLITE_FLOAT ){
char z[50];
double r = sqlite3_column_double(psi->pStmt, i);
sqlite3_uint64 ur;
memcpy(&ur,&r,sizeof(r));
if( ur==0x7ff0000000000000LL ){
raw_printf(out, "9.0e+999");
}else if( ur==0xfff0000000000000LL ){
raw_printf(out, "-9.0e+999");
}else{
sqlite3_snprintf(50,z,"%!.20g", r);
raw_printf(out, "%s", z);
}
}else if( aiType && aiType[i]==SQLITE_BLOB && psi->pStmt ){
const void *pBlob = sqlite3_column_blob(psi->pStmt, i);
int nBlob = sqlite3_column_bytes(psi->pStmt, i);
output_json_string(out, pBlob, nBlob);
}else if( aiType && aiType[i]==SQLITE_TEXT ){
output_json_string(out, azArg[i], -1);
}else{
utf8_printf(out,"%s", azArg[i]);
}
if( i<nArg-1 ){
putc(',', out);
}
}
putc('}', out);
break;
}
case MODE_Quote: {
if( azArg==0 ) break;
if( psx->resultCount==0 && psi->showHeader ){
for(i=0; i<nArg; i++){
if( i>0 ) fputs(psi->colSeparator, out);
output_quoted_string(out, azCol[i]);
}
fputs(psi->rowSeparator, out);
}
psx->resultCount++;
for(i=0; i<nArg; i++){
if( i>0 ) fputs(psi->colSeparator, out);
if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){
utf8_printf(out,"NULL");
}else if( aiType && aiType[i]==SQLITE_TEXT ){
output_quoted_string(out, azArg[i]);
}else if( aiType && aiType[i]==SQLITE_INTEGER ){
utf8_printf(out,"%s", azArg[i]);
}else if( aiType && aiType[i]==SQLITE_FLOAT ){
char z[50];
double r = sqlite3_column_double(psi->pStmt, i);
sqlite3_snprintf(50,z,"%!.20g", r);
raw_printf(out, "%s", z);
}else if( aiType && aiType[i]==SQLITE_BLOB && psi->pStmt ){
const void *pBlob = sqlite3_column_blob(psi->pStmt, i);
int nBlob = sqlite3_column_bytes(psi->pStmt, i);
output_hex_blob(out, pBlob, nBlob);
}else if( isNumber(azArg[i], 0) ){
utf8_printf(out,"%s", azArg[i]);
}else{
output_quoted_string(out, azArg[i]);
}
}
fputs(psi->rowSeparator, out);
break;
}
case MODE_Ascii: {
if( psx->resultCount++==0 && psi->showHeader ){
for(i=0; i<nArg; i++){
if( i>0 ) utf8_printf(out, "%s", psi->colSeparator);
utf8_printf(out,"%s",azCol[i] ? azCol[i] : "");
}
utf8_printf(out, "%s", psi->rowSeparator);
}
if( azArg==0 ) break;
for(i=0; i<nArg; i++){
if( i>0 ) utf8_printf(out, "%s", psi->colSeparator);
utf8_printf(out,"%s",azArg[i] ? azArg[i] : psi->nullValue);
}
utf8_printf(out, "%s", psi->rowSeparator);
break;
}
case MODE_EQP: {
eqp_append(psi, atoi(azArg[0]), atoi(azArg[1]), azArg[3]);
break;
}
}
return 0;
}
/*
** This is the callback routine that the SQLite library
** invokes for each row of a query result.
*/
static int callback(void *pArg, int nArg, char **azArg, char **azCol){
/* since we don't have type info, call the shell_callback with a NULL value */
return shell_callback(pArg, nArg, azArg, azCol, NULL);
}
/*
** This is the callback routine from sqlite3_exec() that appends all
** output onto the end of a ShellText object.
*/
static int captureOutputCallback(void *pArg, int nArg, char **azArg, char **az){
ShellText *p = (ShellText*)pArg;
int i;
UNUSED_PARAMETER(az);
if( azArg==0 ) return 0;
if( p->n ) appendText(p, "|", 0);
for(i=0; i<nArg; i++){
if( i ) appendText(p, ",", 0);
if( azArg[i] ) appendText(p, azArg[i], 0);
}
return 0;
}
/*
** Generate an appropriate SELFTEST table in the main database.
*/
static void createSelftestTable(ShellInState *p){
char *zErrMsg = 0;
s3_exec_noom(DBI(p),
"SAVEPOINT selftest_init;\n"
"CREATE TABLE IF NOT EXISTS selftest(\n"
" tno INTEGER PRIMARY KEY,\n" /* Test number */
" op TEXT,\n" /* Operator: memo run */
" cmd TEXT,\n" /* Command text */
" ans TEXT\n" /* Desired answer */
");"
"CREATE TEMP TABLE [_shell$self](op,cmd,ans);\n"
"INSERT INTO [_shell$self](rowid,op,cmd)\n"
" VALUES(coalesce((SELECT (max(tno)+100)/10 FROM selftest),10),\n"
" 'memo','Tests generated by --init');\n"
"INSERT INTO [_shell$self]\n"
" SELECT 'run',\n"
" 'SELECT hex(sha3_query(''SELECT type,name,tbl_name,sql "
"FROM sqlite_schema ORDER BY 2'',224))',\n"
" hex(sha3_query('SELECT type,name,tbl_name,sql "
"FROM sqlite_schema ORDER BY 2',224));\n"
"INSERT INTO [_shell$self]\n"
" SELECT 'run',"
" 'SELECT hex(sha3_query(''SELECT * FROM \"' ||"
" printf('%w',name) || '\" NOT INDEXED'',224))',\n"
" hex(sha3_query(printf('SELECT * FROM \"%w\" NOT INDEXED',name),224))\n"
" FROM (\n"
" SELECT name FROM sqlite_schema\n"
" WHERE type='table'\n"
" AND name<>'selftest'\n"
" AND coalesce(rootpage,0)>0\n"
" )\n"
" ORDER BY name;\n"
"INSERT INTO [_shell$self]\n"
" VALUES('run','PRAGMA integrity_check','ok');\n"
"INSERT INTO selftest(tno,op,cmd,ans)"
" SELECT rowid*10,op,cmd,ans FROM [_shell$self];\n"
"DROP TABLE [_shell$self];"
,0,0,&zErrMsg);
if( zErrMsg ){
utf8_printf(STD_ERR, "SELFTEST initialization failure: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}
s3_exec_noom(DBI(p), "RELEASE selftest_init",0,0,0);
}
/*
** Set the destination table field of the shell state structure to
** the name of the table given. Escape any quote characters in the
** table name.
*/
static void set_table_name(ShellExState *psx, const char *zName){
int i, n;
char cQuote;
char *z;
if( psx->zDestTable ){
free((void *)(psx->zDestTable));
psx->zDestTable = 0;
}
if( zName==0 ) return;
cQuote = quoteChar(zName);
n = strlen30(zName);
if( cQuote ) n += n+2;
psx->zDestTable = (z = malloc( n+1 ));
shell_check_oomm(z);
n = 0;
if( cQuote ) z[n++] = cQuote;
for(i=0; zName[i]; i++){
z[n++] = zName[i];
if( zName[i]==cQuote ) z[n++] = cQuote;
}
if( cQuote ) z[n++] = cQuote;
z[n] = 0;
}
/*
** Maybe construct two lines of text that point out the position of a
** syntax error. Return a pointer to the text, in memory obtained from
** sqlite3_malloc(). Or, if the most recent error does not involve a
** specific token that we can point to, return an empty string.
**
** In all cases, the memory returned is obtained from sqlite3_malloc64()
** and should be released by the caller invoking sqlite3_free().
*/
static char *shell_error_context(const char *zSql, sqlite3 *db){
int iOffset;
size_t len;
char *zCode;
char *zMsg;
int i;
if( db==0
|| zSql==0
|| (iOffset = sqlite3_error_offset(db))<0
|| iOffset>=(int)strlen(zSql)
){
zMsg = smprintf("");
}else{
while( iOffset>50 ){
iOffset--;
zSql++;
while( (zSql[0]&0xc0)==0x80 ){ zSql++; iOffset--; }
}
len = strlen(zSql);
if( len>78 ){
len = 78;
while( len>0 && (zSql[len]&0xc0)==0x80 ) len--;
}
zCode = smprintf("%.*s", len, zSql);
shell_check_ooms(zCode);
for(i=0; zCode[i]; i++){ if( IsSpace(zSql[i]) ) zCode[i] = ' '; }
if( iOffset<25 ){
zMsg = smprintf("\n %z\n %*s^--- error here",
zCode, iOffset, "");
}else{
zMsg = smprintf("\n %z\n %*serror here ---^",
zCode, iOffset-14, "");
}
}
shell_check_ooms(zMsg);
return zMsg;
}
/*
** Execute a query statement that will generate SQL output. Print
** the result columns, comma-separated, on a line and then add a
** 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
** "--" comment occurs at the end of the statement, the comment
** won't consume the semicolon terminator.
*/
static int run_table_dump_query(
ShellInState *psi, /* Query context */
const char *zSelect /* SELECT statement to extract content */
){
sqlite3_stmt *pSelect;
int rc;
int nResult;
int i;
const char *z;
rc = s3_prepare_v2_noom(DBI(psi), zSelect, -1, &pSelect, 0);
if( rc!=SQLITE_OK || !pSelect ){
char *zContext = shell_error_context(zSelect, DBI(psi));
utf8_printf(psi->out, "/**** ERROR: (%d) %s *****/\n%s", rc,
sqlite3_errmsg(DBI(psi)), zContext);
sqlite3_free(zContext);
if( (rc&0xff)!=SQLITE_CORRUPT ) psi->nErr++;
return rc;
}
stmt_holder(pSelect);
rc = s3_step_noom(pSelect);
nResult = sqlite3_column_count(pSelect);
while( rc==SQLITE_ROW ){
z = (const char*)sqlite3_column_text(pSelect, 0);
utf8_printf(psi->out, "%s", z);
for(i=1; i<nResult; i++){
utf8_printf(psi->out, ",%s", sqlite3_column_text(pSelect, i));
}
if( z==0 ) z = "";
while( z[0] && (z[0]!='-' || z[1]!='-') ) z++;
if( z[0] ){
raw_printf(psi->out, "\n;\n");
}else{
raw_printf(psi->out, ";\n");
}
rc = s3_step_noom(pSelect);
}
drop_holder();
rc = sqlite3_finalize(pSelect);
if( rc!=SQLITE_OK ){
utf8_printf(psi->out, "/**** ERROR: (%d) %s *****/\n", rc,
sqlite3_errmsg(DBI(psi)));
if( (rc&0xff)!=SQLITE_CORRUPT ) psi->nErr++;
}
return rc;
}
/*
** Allocate space and save off string indicating current error.
** The return must be passed to sqlite3_free() sometime. This
** function may terminate the shell under OOM conditions.
*/
static char *save_err_msg(
sqlite3 *db, /* Database to query */
const char *zPhase, /* When the error occurs */
int rc, /* Error code returned from API */
const char *zSql /* SQL string, or NULL */
){
char *zErr;
char *zContext;
sqlite3_str *pStr = sqlite3_str_new(0);
sqst_ptr_holder(&pStr);
sqlite3_str_appendf(pStr, "%s, %s", zPhase, sqlite3_errmsg(db));
if( rc>1 ){
sqlite3_str_appendf(pStr, " (%d)", rc);
}
zContext = shell_error_context(zSql, db);
if( zContext ){
sqlite3_str_appendall(pStr, zContext);
sqlite3_free(zContext);
}
zErr = sqlite3_str_finish(pStr);
drop_holder();
shell_check_ooms(zErr);
return zErr;
}
#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 = strlen30(aTrans[i].zPattern);
if( cli_strncmp(aTrans[i].zPattern, z, n)==0 ){
utf8_printf(out, "%-36s %s", aTrans[i].zDesc, &z[n]);
break;
}
}
}
fclose(in);
}
#endif
/*
** Display a single line of status using 64-bit values.
*/
static void displayStatLine(
ShellInState *psi, /* The shell context (internal) */
char *zLabel, /* Label for this one line */
char *zFormat, /* Format for the result */
int iStatusCtrl, /* Which status to display */
int bReset /* True to reset the stats */
){
sqlite3_int64 iCur = -1;
sqlite3_int64 iHiwtr = -1;
int i, nPercent;
char zLine[200];
sqlite3_status64(iStatusCtrl, &iCur, &iHiwtr, bReset);
for(i=0, nPercent=0; zFormat[i]; i++){
if( zFormat[i]=='%' ) nPercent++;
}
if( nPercent>1 ){
sqlite3_snprintf(sizeof(zLine), zLine, zFormat, iCur, iHiwtr);
}else{
sqlite3_snprintf(sizeof(zLine), zLine, zFormat, iHiwtr);
}
raw_printf(psi->out, "%-36s %s\n", zLabel, zLine);
}
/*
** Display memory stats.
*/
static int display_stats(
sqlite3 *db, /* Database to query */
ShellInState *psi, /* Pointer to shell internal state */
int bReset /* True to reset the stats */
){
int iCur;
int iHiwtr;
FILE *out;
if( psi==0 || psi->out==0 ) return 0;
out = psi->out;
if( psi->pStmt && psi->statsOn==2 ){
int nCol, i, x;
sqlite3_stmt *pStmt = psi->pStmt;
char z[100];
nCol = sqlite3_column_count(pStmt);
raw_printf(out, "%-36s %d\n", "Number of output columns:", nCol);
for(i=0; i<nCol; i++){
sqlite3_snprintf(sizeof(z),z,"Column %d %nname:", i, &x);
utf8_printf(out, "%-36s %s\n", z, sqlite3_column_name(pStmt,i));
#ifndef SQLITE_OMIT_DECLTYPE
sqlite3_snprintf(30, z+x, "declared type:");
utf8_printf(out, "%-36s %s\n", z, sqlite3_column_decltype(pStmt, i));
#endif
#ifdef SQLITE_ENABLE_COLUMN_METADATA
sqlite3_snprintf(30, z+x, "database name:");
utf8_printf(out, "%-36s %s\n", z, sqlite3_column_database_name(pStmt,i));
sqlite3_snprintf(30, z+x, "table name:");
utf8_printf(out, "%-36s %s\n", z, sqlite3_column_table_name(pStmt,i));
sqlite3_snprintf(30, z+x, "origin name:");
utf8_printf(out, "%-36s %s\n", z, sqlite3_column_origin_name(pStmt,i));
#endif
}
}
if( psi->statsOn==3 ){
if( psi->pStmt ){
iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset);
raw_printf(psi->out, "VM-steps: %d\n", iCur);
}
return 0;
}
displayStatLine(psi, "Memory Used:",
"%lld (max %lld) bytes", SQLITE_STATUS_MEMORY_USED, bReset);
displayStatLine(psi, "Number of Outstanding Allocations:",
"%lld (max %lld)", SQLITE_STATUS_MALLOC_COUNT, bReset);
if( psi->shellFlgs & SHFLG_Pagecache ){
displayStatLine(psi, "Number of Pcache Pages Used:",
"%lld (max %lld) pages", SQLITE_STATUS_PAGECACHE_USED, bReset);
}
displayStatLine(psi, "Number of Pcache Overflow Bytes:",
"%lld (max %lld) bytes", SQLITE_STATUS_PAGECACHE_OVERFLOW, bReset);
displayStatLine(psi, "Largest Allocation:",
"%lld bytes", SQLITE_STATUS_MALLOC_SIZE, bReset);
displayStatLine(psi, "Largest Pcache Allocation:",
"%lld bytes", SQLITE_STATUS_PAGECACHE_SIZE, bReset);
#ifdef YYTRACKMAXSTACKDEPTH
displayStatLine(psi, "Deepest Parser Stack:",
"%lld (max %lld)", SQLITE_STATUS_PARSER_STACK, bReset);
#endif
if( db ){
if( psi->shellFlgs & SHFLG_Lookaside ){
iHiwtr = iCur = -1;
sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED,
&iCur, &iHiwtr, bReset);
raw_printf(psi->out,
"Lookaside Slots Used: %d (max %d)\n",
iCur, iHiwtr);
sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_HIT,
&iCur, &iHiwtr, bReset);
raw_printf(psi->out, "Successful lookaside attempts: %d\n",
iHiwtr);
sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE,
&iCur, &iHiwtr, bReset);
raw_printf(psi->out, "Lookaside failures due to size: %d\n",
iHiwtr);
sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL,
&iCur, &iHiwtr, bReset);
raw_printf(psi->out, "Lookaside failures due to OOM: %d\n",
iHiwtr);
}
iHiwtr = iCur = -1;
sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHiwtr, bReset);
raw_printf(psi->out, "Pager Heap Usage: %d bytes\n",
iCur);
iHiwtr = iCur = -1;
sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_HIT, &iCur, &iHiwtr, 1);
raw_printf(psi->out, "Page cache hits: %d\n", iCur);
iHiwtr = iCur = -1;
sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1);
raw_printf(psi->out, "Page cache misses: %d\n", iCur);
iHiwtr = iCur = -1;
sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1);
raw_printf(psi->out, "Page cache writes: %d\n", iCur);
iHiwtr = iCur = -1;
sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_SPILL, &iCur, &iHiwtr, 1);
raw_printf(psi->out, "Page cache spills: %d\n", iCur);
iHiwtr = iCur = -1;
sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset);
raw_printf(psi->out, "Schema Heap Usage: %d bytes\n",
iCur);
iHiwtr = iCur = -1;
sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHiwtr, bReset);
raw_printf(psi->out, "Statement Heap/Lookaside Usage: %d bytes\n",
iCur);
}
if( psi->pStmt ){
int iHit, iMiss;
iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP,
bReset);
raw_printf(psi->out, "Fullscan Steps: %d\n", iCur);
iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_SORT, bReset);
raw_printf(psi->out, "Sort Operations: %d\n", iCur);
iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_AUTOINDEX,bReset);
raw_printf(psi->out, "Autoindex Inserts: %d\n", iCur);
iHit = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_FILTER_HIT,
bReset);
iMiss = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_FILTER_MISS,
bReset);
if( iHit || iMiss ){
raw_printf(psi->out, "Bloom filter bypass taken: %d/%d\n",
iHit, iHit+iMiss);
}
iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset);
raw_printf(psi->out, "Virtual Machine Steps: %d\n", iCur);
iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_REPREPARE,bReset);
raw_printf(psi->out, "Reprepare operations: %d\n", iCur);
iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_RUN, bReset);
raw_printf(psi->out, "Number of times run: %d\n", iCur);
iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_MEMUSED, bReset);
raw_printf(psi->out, "Memory used by prepared stmt: %d\n", iCur);
}
#ifdef __linux__
displayLinuxIoStats(psi->out);
#endif
/* Do not remove this machine readable comment: extra-stats-output-here */
return 0;
}
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
static int scanStatsHeight(sqlite3_stmt *p, int iEntry){
int iPid = 0;
int ret = 1;
sqlite3_stmt_scanstatus_v2(p, iEntry,
SQLITE_SCANSTAT_SELECTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iPid
);
while( iPid!=0 ){
int ii;
for(ii=0; 1; ii++){
int iId;
int res;
res = sqlite3_stmt_scanstatus_v2(p, ii,
SQLITE_SCANSTAT_SELECTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iId
);
if( res ) break;
if( iId==iPid ){
sqlite3_stmt_scanstatus_v2(p, ii,
SQLITE_SCANSTAT_PARENTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iPid
);
}
}
ret++;
}
return ret;
}
#endif
/*
** Display scan stats.
*/
static void display_scanstats(sqlite3 *db, ShellInState *psi){
#ifndef SQLITE_ENABLE_STMT_SCANSTATUS
UNUSED_PARAMETER(db);
UNUSED_PARAMETER(psi);
#else
static const int f = SQLITE_SCANSTAT_COMPLEX;
sqlite3_stmt *p = psi->pStmt;
int ii = 0;
i64 nTotal = 0;
int nWidth = 0;
eqp_reset(psi);
for(ii=0; 1; ii++){
const char *z = 0;
int n = 0;
if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){
break;
}
n = strlen(z) + scanStatsHeight(p, ii)*3;
if( n>nWidth ) nWidth = n;
}
nWidth += 4;
sqlite3_stmt_scanstatus_v2(p, -1, SQLITE_SCANSTAT_NCYCLE, f, (void*)&nTotal);
for(ii=0; 1; ii++){
i64 nLoop = 0;
i64 nRow = 0;
i64 nCycle = 0;
int iId = 0;
int iPid = 0;
const char *z = 0;
const char *zName = 0;
char *zText = 0;
double rEst = 0.0;
if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){
break;
}
sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_EST,f,(void*)&rEst);
sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NLOOP,f,(void*)&nLoop);
sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NVISIT,f,(void*)&nRow);
sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NCYCLE,f,(void*)&nCycle);
sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_SELECTID,f,(void*)&iId);
sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_PARENTID,f,(void*)&iPid);
sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NAME,f,(void*)&zName);
zText = smprintf("%s", z);
if( nCycle>=0 || nLoop>=0 || nRow>=0 ){
char *z = 0;
if( nCycle>=0 && nTotal>0 ){
z = smprintf("%zcycles=%lld [%d%%]",
z, nCycle, ((nCycle*100)+nTotal/2) / nTotal
);
}
if( nLoop>=0 ){
z = smprintf("%z%sloops=%lld", z, z ? " " : "", nLoop);
}
if( nRow>=0 ){
z = smprintf("%z%srows=%lld", z, z ? " " : "", nRow);
}
if( zName && psi->scanstatsOn>1 ){
double rpl = (double)nRow / (double)nLoop;
z = smprintf("%z rpl=%.1f est=%.1f", z, rpl, rEst);
}
zText = smprintf("% *z (%z)", scanStatsHeight(p, ii)*3-nWidth, zText, z);
}
eqp_append(psi, iId, iPid, zText);
sqlite3_free(zText);
}
eqp_render(psi, nTotal);
#endif
}
/*
** Parameter azArray points to a zero-terminated array of strings. zStr
** points to a single nul-terminated string. Return non-zero if zStr
** is equal, according to strcmp(), to any of the strings in the array.
** Otherwise, return zero.
*/
static int str_in_array(const char *zStr, const char **azArray){
int i;
for(i=0; azArray[i]; i++){
if( 0==cli_strcmp(zStr, azArray[i]) ) return 1;
}
return 0;
}
/*
** If compiled statement pSql appears to be an EXPLAIN statement, allocate
** and populate the ShellInState.aiIndent[] array with the number of
** spaces each opcode should be indented before it is output.
**
** The indenting rules are:
**
** * For each "Next", "Prev", "VNext" or "VPrev" instruction, indent
** all opcodes that occur between the p2 jump destination and the opcode
** itself by 2 spaces.
**
** * Do the previous for "Return" instructions for when P2 is positive.
** See tag-20220407a in wherecode.c and vdbe.c.
**
** * For each "Goto", if the jump destination is earlier in the program
** and ends on one of:
** Yield SeekGt SeekLt RowSetRead Rewind
** or if the P1 parameter is one instead of zero,
** then indent all opcodes between the earlier instruction
** and "Goto" by 2 spaces.
*/
static void explain_data_prepare(ShellInState *psi, sqlite3_stmt *pSql){
const char *zSql; /* The text of the SQL statement */
const char *z; /* Used to check if this is an EXPLAIN */
int *abYield = 0; /* True if op is an OP_Yield */
int *ai; /* Temporary aiIndent[] (to avoid leak) */
int nAlloc = 0; /* Allocated size of p->aiIndent[], abYield */
int iOp; /* Index of operation in p->aiIndent[] */
const char *azNext[] = { "Next", "Prev", "VPrev", "VNext", "SorterNext",
"Return", 0 };
const char *azYield[] = { "Yield", "SeekLT", "SeekGT", "RowSetRead",
"Rewind", 0 };
const char *azGoto[] = { "Goto", 0 };
/* Try to figure out if this is really an EXPLAIN statement. If this
** cannot be verified, return early. */
if( sqlite3_column_count(pSql)!=8 ){
psi->cMode = psi->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) ){
psi->cMode = psi->mode;
return;
}
smem_ptr_holder((void**)&abYield);
for(iOp=0; SQLITE_ROW==sqlite3_step(pSql); iOp++){
int i;
int iAddr = sqlite3_column_int(pSql, 0);
const char *zOp = (const char*)sqlite3_column_text(pSql, 1);
/* Set p2 to the P2 field of the current opcode. Then, assuming that
** p2 is an instruction address, set variable p2op to the index of that
** instruction in the aiIndent[] array. p2 and p2op may be different if
** the current instruction is part of a sub-program generated by an
** SQL trigger or foreign key. */
int p2 = sqlite3_column_int(pSql, 3);
int p2op = (p2 + (iOp-iAddr));
/* Grow the p->aiIndent array as required */
if( iOp>=nAlloc ){
if( iOp==0 ){
/* Do further verification 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( cli_strcmp(sqlite3_column_name(pSql,jj),explainCols[jj])!=0 ){
psi->cMode = psi->mode;
release_holder();
sqlite3_reset(pSql);
return;
}
}
}
nAlloc += 100;
ai = (int*)sqlite3_realloc64(psi->aiIndent, nAlloc*sizeof(int));
shell_check_ooms(ai);
psi->aiIndent = ai;
abYield = (int*)sqlite3_realloc64(abYield, nAlloc*sizeof(int));
shell_check_ooms(abYield);
}
abYield[iOp] = str_in_array(zOp, azYield);
psi->aiIndent[iOp] = 0;
psi->nIndent = iOp+1;
if( str_in_array(zOp, azNext) && p2op>0 ){
for(i=p2op; i<iOp; i++) psi->aiIndent[i] += 2;
}
if( str_in_array(zOp, azGoto) && p2op<psi->nIndent
&& (abYield[p2op] || sqlite3_column_int(pSql, 2))
){
for(i=p2op; i<iOp; i++) psi->aiIndent[i] += 2;
}
}
psi->iIndent = 0;
release_holder();
sqlite3_reset(pSql);
}
/*
** Free the array allocated by explain_data_prepare().
*/
static void explain_data_delete(ShellInState *psi){
sqlite3_free(psi->aiIndent);
psi->aiIndent = 0;
psi->nIndent = 0;
psi->iIndent = 0;
}
/*
** Disable and restore .wheretrace and .selecttrace settings.
*/
static unsigned int savedSelectTrace;
static unsigned int savedWhereTrace;
static void disable_debug_trace_modes(void){
unsigned int zero = 0;
sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 0, &savedSelectTrace);
sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &zero);
sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 2, &savedWhereTrace);
sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &zero);
}
static void restore_debug_trace_modes(void){
sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &savedSelectTrace);
sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &savedWhereTrace);
}
/* DB schema protection for use by next two utility functions */
typedef struct DbProtectState {
int wrSchema;
int defensiveMode;
} DbProtectState;
/* Allow system (sqlite_*) schema changes, return prior protection state. */
static DbProtectState allow_sys_schema_change(sqlite3 *db){
DbProtectState rv;
sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, -1, &rv.defensiveMode);
sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0);
sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, -1, &rv.wrSchema);
sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, 0);
return rv;
}
/* Restore protection state using allow_sys_schema_change() return. */
static void restore_sys_schema_protection( sqlite3 *db, DbProtectState *pPS ){
sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, pPS->wrSchema, 0);
sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, pPS->defensiveMode, 0);
}
/* Partition the temp.sqlite_parameters and main.sqlite_variables
* key namespace according to use.
*/
typedef enum {
PTU_Binding = 0, /* Binding parameter, value to be bound */
#define SPTU_Binding SHELL_STRINGIFY(0)
PTU_Script = 1, /* A value containing shell SQL and dot-commands */
#define SPTU_Script SHELL_STRINGIFY(1)
PTU_Entry = 2, /* Value as entered for non-text binding parameters */
#define SPTU_Entry SHELL_STRINGIFY(2)
PTU_Ref = 3, /* Name was recently referenced */
#define SPTU_Ref SHELL_STRINGIFY(3)
PTU_Nil = 4 /* Unspecified */
} ParamTableUse;
static ParamTableUse classify_param_name( const char *zName ){
char c = *zName;
switch( c ){
case '$': case ':': case '@': case '?': return PTU_Binding;
default: return isalpha(c)? PTU_Script : PTU_Nil;
}
}
#ifndef SQLITE_NOHAVE_SYSTEM
/* Possibly using a -editor=X argument and environment variable VISUAL,
* attempt to get the zEditor shell state member set iff not already set.
* If there is no such argument, said variable is retrieved if set.
* If the argument is -editor=X or --editor=X, use that and leave
* the zEditor member set accordingly. Returns are:
* 0 => editor set, zEd was not the -editor option
* 1 => editor set, zEd consumed as -editor option
* -1 => editor not set, and error/advice message issued.
*
* This implements an undocumented fall-back for the .vars and
* .parameters edit subcommands, so that users need not restart
* a shell session to get an editor specified upon need for it. */
int attempt_editor_set(ShellInState *psi, char *zDot, const char *zEd){
if( psi->zEditor==0 ){
const char *zE = getenv("VISUAL");
if( zE!=0 ) psi->zEditor = smprintf("%s", zE);
}
if( zEd && zEd[0]=='-' ){
zEd += 1 + (zEd[1]=='-');
if( cli_strncmp(zEd,"editor=",7)==0 ){
sqlite3_free(psi->zEditor);
/* Accept an initial -editor=? option. */
psi->zEditor = smprintf("%s", zEd+7);
return 1;
}
}
if( psi->zEditor==0 ){
utf8_printf(STD_ERR,
"Either set env-var VISUAL to name an"
" editor and restart, or rerun\n "
".%s edit with an initial edit option,"
" --editor=EDITOR_COMMAND .\n", zDot);
return -1;
}
return 0;
}
#endif
/* The table kept for user DBs if .parameter command is used usefully. */
#define PARAM_TABLE_NAME "sqlite_parameters"
#define PARAM_TABLE_SCHEMA "temp"
#define PARAM_TABLE_SNAME PARAM_TABLE_SCHEMA"."PARAM_TABLE_NAME
/* The table kept for the shell DB if .vars command is used usefully. */
#define SHVAR_TABLE_NAME "sqlite_variables"
#define SHVAR_TABLE_SCHEMA "main"
#define SHVAR_TABLE_SNAME SHVAR_TABLE_SCHEMA"."SHVAR_TABLE_NAME
#ifndef SH_KV_STORE_NAME
/* Name for table keeping user's saved parameters */
# define SH_KV_STORE_NAME "SQLiteShell_KeyValuePairs"
#endif
#ifndef SH_KV_STORE_SCHEMA
/* Schema name used to attach saved parameters DB during load/save */
# define SH_KV_STORE_SCHEMA "SQLiteShell"
#endif
#define SH_KV_STORE_SNAME SH_KV_STORE_SCHEMA"."SH_KV_STORE_NAME
/* Create the TEMP table used to store parameter bindings */
static void param_table_init(sqlite3 *db){
DbProtectState dps = allow_sys_schema_change(db);
s3_exec_noom(db,
"CREATE TABLE IF NOT EXISTS "PARAM_TABLE_SNAME"(\n"
" key TEXT,\n"
" value,\n"
" uses INT DEFAULT ("SPTU_Binding"),"
" UNIQUE(key,uses) ON CONFLICT REPLACE"
")",
0, 0, 0);
restore_sys_schema_protection( db, &dps );
}
/* Tell whether the above-created table exists, return true iff exists. */
static int param_table_exists( sqlite3 *db ){
return sqlite3_table_column_metadata
(db, PARAM_TABLE_SCHEMA, PARAM_TABLE_NAME, 0, 0, 0, 0, 0, 0)==SQLITE_OK;
}
/* Create the shell DB table used to store shell variables or scripts */
static int shvars_table_init(sqlite3 *db){
DbProtectState dps = allow_sys_schema_change(db);
int rc = s3_exec_noom(db,
"CREATE TABLE IF NOT EXISTS "SHVAR_TABLE_SNAME"(\n"
" key TEXT PRIMARY KEY,\n"
" value,\n"
" uses INT DEFAULT ("SPTU_Script")"
") WITHOUT ROWID",
0, 0, 0);
restore_sys_schema_protection( db, &dps );
return rc!=SQLITE_OK;
}
/* Tell whether the above-created table exists, return true iff exists. */
static int shvars_table_exists( sqlite3 *db ){
return sqlite3_table_column_metadata
(db, SHVAR_TABLE_SCHEMA, SHVAR_TABLE_NAME, 0, 0, 0, 0, 0, 0)==SQLITE_OK;
}
/* Make shell vars table exist. */
static int ensure_shvars_table(sqlite3 *dbs){
if( shvars_table_exists(dbs) ) return SQLITE_OK;
else return shvars_table_init(dbs);
}
/*
** Bind parameters on a prepared statement.
**
** Normal parameter bindings are taken from a TEMP table of the form:
**
** CREATE TEMP TABLE
** sqlite_parameters(key TEXT PRIMARY KEY, value, uses INT)
** WITHOUT ROWID;
**
** Normal bindings do not occur if this table does not exist. The name of
** the table begins with "sqlite_" so that it will not collide with ordinary
** application tables. The table must be in the TEMP schema. Only rows with
** uses=PTU_Binding are eligible for parameter binding.
** Whether the table exists or not, parameter names like _NAN or _INF are
** bound to the similarly named floating point "values".
**
** If noteUses is non-zero, binding attempts and success are recorded in the
** sqlite_parameters table with uses = PTU_Binding. Failed attempts get value
** 0 and successful bindings get value 1.
*/
static void bind_prepared_stmt(sqlite3 *db, sqlite3_stmt *pStmt, u8 noteUses){
int nVar = sqlite3_bind_parameter_count(pStmt);
int i;
int rc;
int haveParams = param_table_exists(db);
sqlite3_stmt *pQ = 0;
sqlite3_stmt *pU = 0;
if( nVar==0 ) return; /* Nothing to do */
if( haveParams ){
rc = s3_prepare_v2_noom(db,
"SELECT value FROM temp."PARAM_TABLE_NAME
" WHERE key=?1 AND uses="SPTU_Binding, -1, &pQ, 0);
if( rc!=SQLITE_OK || pQ==0 ) haveParams = 0;
else if( noteUses ){
rc = s3_prepare_v2_noom(db,
"INSERT INTO temp."PARAM_TABLE_NAME
"(key, value, uses) VALUES (?1,?2,"SPTU_Ref")",
-1, &pU, 0);
s3_exec_noom(db,
"DELETE FROM temp."PARAM_TABLE_NAME" WHERE uses="SPTU_Ref,
0,0,0);
if( rc!=SQLITE_OK ) noteUses = 0;
}
}
stmt_holder(pQ);
stmt_holder(pU);
for(i=1; i<=nVar; i++){
char zNum[30];
const char *zVar = sqlite3_bind_parameter_name(pStmt, i);
if( zVar==0 ){
sqlite3_snprintf(sizeof(zNum),zNum,"?%d",i);
zVar = zNum;
}
sqlite3_bind_text(pQ, 1, zVar, -1, SQLITE_STATIC);
if( haveParams && noteUses ){
sqlite3_reset(pU);
sqlite3_bind_text(pU, 1, zVar, -1, SQLITE_STATIC);
sqlite3_bind_int(pU, 2, 0);
}
if( haveParams && s3_step_noom(pQ)==SQLITE_ROW ){
sqlite3_bind_value(pStmt, i, sqlite3_column_value(pQ, 0));
if( noteUses ){
sqlite3_bind_int(pU, 2, 1);
s3_step_noom(pU);
}
#ifdef NAN
}else if( sqlite3_strlike("_NAN", zVar, 0)==0 ){
sqlite3_bind_double(pStmt, i, NAN);
#endif
#ifdef INFINITY
}else if( sqlite3_strlike("_INF", zVar, 0)==0 ){
sqlite3_bind_double(pStmt, i, INFINITY);
#endif
}else{
if( haveParams && noteUses ) s3_step_noom(pU);
sqlite3_bind_null(pStmt, i);
}
sqlite3_reset(pQ); /* Undocumented: NULL pQ is ok. */
}
release_holders(2);
}
/*
** UTF8 box-drawing characters. Imagine box lines like this:
**
** 1
** |
** 4 --+-- 2
** |
** 3
**
** Each box character has between 2 and 4 of the lines leading from
** the center. The characters are here identified by the numbers of
** their corresponding lines.
*/
#define BOX_24 "\342\224\200" /* U+2500 --- */
#define BOX_13 "\342\224\202" /* U+2502 | */
#define BOX_23 "\342\224\214" /* U+250c ,- */
#define BOX_34 "\342\224\220" /* U+2510 -, */
#define BOX_12 "\342\224\224" /* U+2514 '- */
#define BOX_14 "\342\224\230" /* U+2518 -' */
#define BOX_123 "\342\224\234" /* U+251c |- */
#define BOX_134 "\342\224\244" /* U+2524 -| */
#define BOX_234 "\342\224\254" /* U+252c -,- */
#define BOX_124 "\342\224\264" /* U+2534 -'- */
#define BOX_1234 "\342\224\274" /* U+253c -|- */
/* Draw horizontal line N characters long using unicode box
** characters
*/
static void print_box_line(FILE *out, int N){
const char zDash[] =
BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24
BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24;
const int nDash = sizeof(zDash) - 1;
N *= 3;
while( N>nDash ){
utf8_printf(out, zDash);
N -= nDash;
}
utf8_printf(out, "%.*s", N, zDash);
}
/*
** Draw a horizontal separator for a MODE_Box table.
*/
static void print_box_row_separator(
ShellExState *psx,
int nArg,
const char *zSep1,
const char *zSep2,
const char *zSep3
){
int i;
FILE *out = ISS(psx)->out;
if( nArg>0 ){
utf8_printf(out, "%s", zSep1);
print_box_line(out, psx->pHaveWidths[0]+2);
for(i=1; i<nArg; i++){
utf8_printf(out, "%s", zSep2);
print_box_line(out, psx->pHaveWidths[i]+2);
}
utf8_printf(out, "%s", zSep3);
}
fputs("\n", out);
}
/*
** z[] is a line of text that is to be displayed the .mode box or table or
** similar tabular formats. z[] might contain control characters such
** as \n, \t, \f, or \r.
**
** Compute characters to display on the first line of z[]. Stop at the
** first \r, \n, or \f. Expand \t into spaces. Return a copy (obtained
** from malloc()) of that first line, which caller should free sometime.
** Write anything to display on the next line into *pzTail. If this is
** the last line, write a NULL into *pzTail. (*pzTail is not allocated.)
*/
static char *translateForDisplayAndDup(
const unsigned char *z, /* Input text to be transformed */
const unsigned char **pzTail, /* OUT: Tail of the input for next line */
int mxWidth, /* Max width. 0 means no limit */
u8 bWordWrap /* If true, avoid breaking mid-word */
){
int i; /* Input bytes consumed */
int j; /* Output bytes generated */
int k; /* Input bytes to be displayed */
int n; /* Output column number */
unsigned char *zOut; /* Output text */
if( z==0 ){
*pzTail = 0;
return 0;
}
if( mxWidth<0 ) mxWidth = -mxWidth;
if( mxWidth==0 ) mxWidth = 1000000;
i = j = n = 0;
while( n<mxWidth ){
if( z[i]>=' ' ){
n++;
do{ i++; j++; }while( (z[i]&0xc0)==0x80 );
continue;
}
if( z[i]=='\t' ){
do{
n++;
j++;
}while( (n&7)!=0 && n<mxWidth );
i++;
continue;
}
break;
}
if( n>=mxWidth && bWordWrap ){
/* Perhaps try to back up to a better place to break the line */
for(k=i; k>i/2; k--){
if( isspace(z[k-1]) ) break;
}
if( k<=i/2 ){
for(k=i; k>i/2; k--){
if( isalnum(z[k-1])!=isalnum(z[k]) && (z[k]&0xc0)!=0x80 ) break;
}
}
if( k<=i/2 ){
k = i;
}else{
i = k;
while( z[i]==' ' ) i++;
}
}else{
k = i;
}
if( n>=mxWidth && z[i]>=' ' ){
*pzTail = &z[i];
}else if( z[i]=='\r' && z[i+1]=='\n' ){
*pzTail = z[i+2] ? &z[i+2] : 0;
}else if( z[i]==0 || z[i+1]==0 ){
*pzTail = 0;
}else{
*pzTail = &z[i+1];
}
zOut = malloc( j+1 );
shell_check_oomm(zOut);
i = j = n = 0;
while( i<k ){
if( z[i]>=' ' ){
n++;
do{ zOut[j++] = z[i++]; }while( (z[i]&0xc0)==0x80 );
continue;
}
if( z[i]=='\t' ){
do{
n++;
zOut[j++] = ' ';
}while( (n&7)!=0 && n<mxWidth );
i++;
continue;
}
break;
}
zOut[j] = 0;
return (char*)zOut;
}
/* Extract the value of the i-th current column for pStmt as an SQL literal
** value. Memory is obtained from sqlite3_malloc64() and must be freed by
** the caller.
*/
static char *quoted_column(sqlite3_stmt *pStmt, int i){
switch( sqlite3_column_type(pStmt, i) ){
case SQLITE_NULL: {
return smprintf("NULL");
}
case SQLITE_INTEGER:
case SQLITE_FLOAT: {
return smprintf("%s",sqlite3_column_text(pStmt,i));
}
case SQLITE_TEXT: {
return smprintf("%Q",sqlite3_column_text(pStmt,i));
}
case SQLITE_BLOB: {
int j;
sqlite3_str *pStr = sqlite3_str_new(0);
const unsigned char *a = sqlite3_column_blob(pStmt,i);
int n = sqlite3_column_bytes(pStmt,i);
sqlite3_str_append(pStr, "x'", 2);
for(j=0; j<n; j++){
sqlite3_str_appendf(pStr, "%02x", a[j]);
}
sqlite3_str_append(pStr, "'", 1);
return sqlite3_str_finish(pStr);
}
}
return 0; /* Not reached */
}
#if SHELL_DATAIO_EXT
/*
** Run a prepared statement with output as determined by ExportHandler.
*/
static void exec_prepared_stmt(
ShellExState *psx, /* Pointer to shell state */
sqlite3_stmt *pStmt /* Statement to run */
){
ShellInState *psi = ISS(psx);
ExportHandler *pExporter = psi->pActiveExporter;
char *zErr = 0;
int rc;
rc = pExporter->pMethods->prependResultsOut(pExporter, psx, &zErr, pStmt);
if( rc==SQLITE_OK ){
do{
rc = pExporter->pMethods->rowResultsOut(pExporter, psx, &zErr, pStmt);
}while( rc==SQLITE_ROW );
rc = pExporter->pMethods->appendResultsOut(pExporter, psx, &zErr, pStmt);
}
}
#else /* !SHELL_DATAIO_EXT */
/*
** Run a prepared statement and output the result in one of the
** table-oriented formats, for which MODE_IS_COLUMNAR(m) is true:
** MODE_Column, MODE_Markdown, MODE_Table, or MODE_Box.
**
** This is different from ordinary exec_prepared_stmt() in that
** it has to run the entire query and gather the results into memory
** first, in order to determine column widths, before providing
** any output.
*/
static void exec_prepared_stmt_columnar(
ShellExState *psx, /* Pointer to shell state */
sqlite3_stmt *pStmt /* Statement to run */
){
ShellInState *psi = ISS(psx);
sqlite3_int64 nRow = 0;
int nColumn = 0;
char **azData = 0;
sqlite3_int64 nAlloc = 0;
char *abRowDiv = 0;
const unsigned char *uz;
const char *z;
char **azQuoted = 0;
int rc;
sqlite3_int64 i, nData;
int j, nTotal, w, n;
const char *colSep = 0;
const char *rowSep = 0;
const unsigned char **azNextLine = 0;
int bNextLine = 0;
int bMultiLineRowExists = 0;
int bw = psi->cmOpts.bWordWrap;
const char *zEmpty = "";
const char *zShowNull = psi->nullValue;
rc = sqlite3_step(pStmt);
if( rc!=SQLITE_ROW ) return;
nColumn = sqlite3_column_count(pStmt);
nAlloc = nColumn*4;
if( nAlloc<=0 ) nAlloc = 1;
azData = sqlite3_malloc64( nAlloc*sizeof(char*) );
shell_check_ooms(azData);
azNextLine = sqlite3_malloc64( nColumn*sizeof(char*) );
shell_check_ooms(azNextLine);
memset((void*)azNextLine, 0, nColumn*sizeof(char*) );
if( psi->cmOpts.bQuote ){
azQuoted = sqlite3_malloc64( nColumn*sizeof(char*) );
shell_check_ooms(azQuoted);
memset(azQuoted, 0, nColumn*sizeof(char*) );
}
abRowDiv = sqlite3_malloc64( nAlloc/nColumn );
shell_check_ooms(abRowDiv);
if( nColumn>psx->numWidths ){
psx->pSpecWidths = realloc(psx->pSpecWidths, (nColumn+1)*2*sizeof(int));
shell_check_oomm(psx->pSpecWidths);
for(i=psx->numWidths; i<nColumn; i++) psx->pSpecWidths[i] = 0;
psx->numWidths = nColumn;
psx->pHaveWidths = &psx->pSpecWidths[nColumn];
}
memset(psx->pHaveWidths, 0, nColumn*sizeof(int));
for(i=0; i<nColumn; i++){
w = psx->pSpecWidths[i];
if( w<0 ) w = -w;
psx->pHaveWidths[i] = w;
}
for(i=0; i<nColumn; i++){
const unsigned char *zNotUsed;
int wx = psx->pSpecWidths[i];
if( wx==0 ){
wx = psi->cmOpts.iWrap;
}
if( wx<0 ) wx = -wx;
uz = (const unsigned char*)sqlite3_column_name(pStmt,i);
if( uz==0 ) uz = (u8*)"";
azData[i] = translateForDisplayAndDup(uz, &zNotUsed, wx, bw);
}
do{
int useNextLine = bNextLine;
bNextLine = 0;
if( (nRow+2)*nColumn >= nAlloc ){
nAlloc *= 2;
azData = sqlite3_realloc64(azData, nAlloc*sizeof(char*));
shell_check_ooms(azData);
abRowDiv = sqlite3_realloc64(abRowDiv, nAlloc/nColumn);
shell_check_ooms(abRowDiv);
}
abRowDiv[nRow] = 1;
nRow++;
for(i=0; i<nColumn; i++){
int wx = psx->pSpecWidths[i];
if( wx==0 ){
wx = psi->cmOpts.iWrap;
}
if( wx<0 ) wx = -wx;
if( useNextLine ){
uz = azNextiLne[i];
if( uz==0 ) uz = (u8*)zEmpty;
}else if( psi->cmOpts.bQuote ){
sqlite3_free(azQuoted[i]);
azQuoted[i] = quoted_column(pStmt,i);
uz = (const unsigned char*)azQuoted[i];
}else{
uz = (const unsigned char*)sqlite3_column_text(pStmt,i);
if( uz==0 ) uz = (u8*)zShowNull;
}
azData[nRow*nColumn + i]
= translateForDisplayAndDup(uz, &azNextLine[i], wx, bw);
if( azNextLine[i] ){
bNextLine = 1;
abRowDiv[nRow-1] = 0;
bMultiLineRowExists = 1;
}
}
}while( bNextLine || sqlite3_step(pStmt)==SQLITE_ROW );
nTotal = nColumn*(nRow+1);
for(i=0; i<nTotal; i++){
z = azData[i];
if( z==0 ) z = (char*)zEmpty;
n = strlenChar(z);
j = i%nColumn;
if( n>psx->pHaveWidths[j] ) psx->pHaveWidths[j] = n;
}
if( seenInterrupt ) goto columnar_end;
if( nColumn==0 ) goto columnar_end;
switch( psi->cMode ){
case MODE_Column: {
colSep = " ";
rowSep = "\n";
if( psi->showHeader ){
for(i=0; i<nColumn; i++){
w = psx->pHaveWidths[i];
if( psx->pSpecWidths[i]<0 ) w = -w;
utf8_width_print(psi->out, w, azData[i]);
fputs(i==nColumn-1?"\n":" ", psi->out);
}
for(i=0; i<nColumn; i++){
print_dashes(psi->out, psx->pHaveWidths[i]);
fputs(i==nColumn-1?"\n":" ", psi->out);
}
}
break;
}
case MODE_Table: {
colSep = " | ";
rowSep = " |\n";
print_row_separator(psx, nColumn, "+");
fputs("| ", psi->out);
for(i=0; i<nColumn; i++){
w = psx->pHaveWidths[i];
n = strlenChar(azData[i]);
utf8_printf(psi->out, "%*s%s%*s",
(w-n)/2, "", azData[i], (w-n+1)/2, "");
fputs(i==nColumn-1?" |\n":" | ", psi->out);
}
print_row_separator(psx, nColumn, "+");
break;
}
case MODE_Markdown: {
colSep = " | ";
rowSep = " |\n";
fputs("| ", psi->out);
for(i=0; i<nColumn; i++){
w = psx->pHaveWidths[i];
n = strlenChar(azData[i]);
utf8_printf(psi->out, "%*s%s%*s",
(w-n)/2, "", azData[i], (w-n+1)/2, "");
fputs(i==nColumn-1?" |\n":" | ", psi->out);
}
print_row_separator(psx, nColumn, "|");
break;
}
case MODE_Box: {
colSep = " " BOX_13 " ";
rowSep = " " BOX_13 "\n";
print_box_row_separator(psx, nColumn, BOX_23, BOX_234, BOX_34);
utf8_printf(psi->out, BOX_13 " ");
for(i=0; i<nColumn; i++){
w = psx->pHaveWidths[i];
n = strlenChar(azData[i]);
utf8_printf(psi->out, "%*s%s%*s%s",
(w-n)/2, "", azData[i], (w-n+1)/2, "",
i==nColumn-1?" "BOX_13"\n":" "BOX_13" ");
}
print_box_row_separator(psx, nColumn, BOX_123, BOX_1234, BOX_134);
break;
}
}
for(i=nColumn, j=0; i<nTotal; i++, j++){
if( j==0 && psi->cMode!=MODE_Column ){
utf8_printf(psi->out, "%s", psi->cMode==MODE_Box?BOX_13" ":"| ");
}
z = azData[i];
if( z==0 ) z = zEmpty;
w = psx->pHaveWidths[j];
if( psx->pSpecWidths[j]<0 ) w = -w;
utf8_width_print(psi->out, w, z);
if( j==nColumn-1 ){
utf8_printf(psi->out, "%s", rowSep);
if( bMultiLineRowExists && abRowDiv[i/nColumn-1] && i+1<nTotal ){
if( psi->cMode==MODE_Table ){
print_row_separator(psx, nColumn, "+");
}else if( psi->cMode==MODE_Box ){
print_box_row_separator(psx, nColumn, BOX_123, BOX_1234, BOX_134);
}else if( psi->cMode==MODE_Column ){
raw_printf(psi->out, "\n");
}
}
j = -1;
if( seenInterrupt ) goto columnar_end;
}else{
utf8_printf(psi->out, "%s", colSep);
}
}
if( psi->cMode==MODE_Table ){
print_row_separator(psx, nColumn, "+");
}else if( psi->cMode==MODE_Box ){
print_box_row_separator(psx, nColumn, BOX_12, BOX_124, BOX_14);
}
columnar_end:
if( seenInterrupt ){
utf8_printf(psi->out, "Interrupt\n");
}
nData = (nRow+1)*nColumn;
for(i=0; i<nData; i++){
z = azData[i];
if( z!=zEmpty && z!=zShowNull ) sqlite3_free(azData[i]);
}
sqlite3_free(azData);
sqlite3_free((void*)azNextLine);
sqlite3_free(abRowDiv);
if( azQuoted ){
for(i=0; i<nColumn; i++) sqlite3_free(azQuoted[i]);
sqlite3_free(azQuoted);
}
}
/*
** Run a prepared statement
*/
static void exec_prepared_stmt(
ShellExState *pArg, /* Pointer to shell state */
sqlite3_stmt *pStmt /* Statement to run */
){
int rc;
ShellInState *psi = ISS(pArg);
sqlite3_uint64 nRow = 0;
if( MODE_IS_COLUMNAR(psi->cMode) ){
exec_prepared_stmt_columnar(pArg, pStmt);
return;
}
/* 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 ){
/* 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 ){
shell_out_of_memory();
}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{
nRow++;
/* 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
&& (psi->cMode==MODE_Insert || psi->cMode==MODE_Quote) ){
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( shell_callback(pArg, nCol, azVals, azCols, aiTypes) ){
rc = SQLITE_ABORT;
}else{
rc = sqlite3_step(pStmt);
}
}
} while( SQLITE_ROW == rc );
sqlite3_free(pData);
if( psi->cMode==MODE_Json ){
fputs("]\n", psi->out);
}else if( psi->cMode==MODE_Count ){
utf8_printf(psi->out, "%llu row%s\n", nRow, nRow!=1 ? "s" : "");
}
}
}
}
#endif /* !SHELL_DATAIO_EXT */
#ifndef SQLITE_OMIT_VIRTUALTABLE
/*
** This function is called to process SQL if the previous shell command
** was ".expert". It passes the SQL in the second argument directly to
** the sqlite3expert object.
**
** If successful, SQLITE_OK is returned. Otherwise, an SQLite error
** code. In this case, (*pzErr) may be set to point to a buffer containing
** an English language error message. It is the responsibility of the
** caller to eventually free this buffer using sqlite3_free().
*/
static int expertHandleSQL(
ShellInState *psi,
const char *zSql,
char **pzErr
){
assert( psi->expert.pExpert );
assert( pzErr==0 || *pzErr==0 );
return sqlite3_expert_sql(psi->expert.pExpert, zSql, pzErr);
}
/*
** This function is called either to silently clean up the object
** created by the ".expert" command (if bCancel==1), or to generate a
** report from it and then clean it up (if bCancel==0).
**
** If successful, SQLITE_OK is returned. Otherwise, an SQLite error
** code. In this case, (*pzErr) may be set to point to a buffer containing
** an English language error message. It is the responsibility of the
** caller to eventually free this buffer using sqlite3_free().
*/
static int expertFinish(
ShellInState *psi,
int bCancel,
char **pzErr
){
int rc = SQLITE_OK;
sqlite3expert *p = psi->expert.pExpert;
assert( p );
assert( bCancel || pzErr==0 || *pzErr==0 );
if( bCancel==0 ){
FILE *out = psi->out;
int bVerbose = psi->expert.bVerbose;
rc = sqlite3_expert_analyze(p, pzErr);
if( rc==SQLITE_OK ){
int nQuery = sqlite3_expert_count(p);
int i;
if( bVerbose ){
const char *zCand = sqlite3_expert_report(p,0,EXPERT_REPORT_CANDIDATES);
raw_printf(out, "-- Candidates -----------------------------\n");
raw_printf(out, "%s\n", zCand);
}
for(i=0; i<nQuery; i++){
const char *zSql = sqlite3_expert_report(p, i, EXPERT_REPORT_SQL);
const char *zIdx = sqlite3_expert_report(p, i, EXPERT_REPORT_INDEXES);
const char *zEQP = sqlite3_expert_report(p, i, EXPERT_REPORT_PLAN);
if( zIdx==0 ) zIdx = "(no new indexes)\n";
if( bVerbose ){
raw_printf(out, "-- Query %d --------------------------------\n",i+1);
raw_printf(out, "%s\n\n", zSql);
}
raw_printf(out, "%s\n", zIdx);
raw_printf(out, "%s\n", zEQP);
}
}
}
sqlite3_expert_destroy(p);
psi->expert.pExpert = 0;
return rc;
}
#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */
/* This saves little code and source volume, but provides a nice breakpoint.
** It is called when input is ready to be run, or would be run if it was
** not about to dumped as a no-op. Someday, a tracing facility may enhance
** this function's output to show where the input group has originated.
*/
static void echo_group_input(ShellInState *psi, const char *zDo){
if( (psi->shellFlgs & SHFLG_Echo)!=0 ) printf("%s\n", zDo);
}
/*
** 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
** and callback data argument.
*/
static int shell_exec(
ShellExState *psx, /* Pointer to shell state */
const char *zSql, /* SQL to be evaluated */
char **pzErrMsg /* Error msg written here */
){
ShellInState *psi = ISS(psx);
sqlite3_stmt *pStmt = NULL; /* Statement to execute. */
sqlite3_stmt *pExplain = NULL; /* For explain generate */
char *zEQP = NULL; /* For explain report */
int rc = SQLITE_OK; /* Return Code */
int rc2;
const char *zLeftover; /* Tail of unprocessed SQL */
sqlite3 *db = DBX(psx);
ResourceMark mark = holder_mark();
if( pzErrMsg ){
*pzErrMsg = NULL;
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
if( psi->expert.pExpert ){
rc = expertHandleSQL(psi, zSql, pzErrMsg);
return expertFinish(psi, (rc!=SQLITE_OK), pzErrMsg);
}
#endif
stmt_ptr_holder(&pStmt); /* offset 0 */
stmt_ptr_holder(&pExplain); /* offset 1 */
sstr_ptr_holder(&zEQP); /* offset 2 */
while( zSql[0] && (SQLITE_OK == rc) ){
static const char *zStmtSql;
rc = s3_prepare_v2_noom(db, zSql, -1, &pStmt, &zLeftover);
if( SQLITE_OK != rc ){
if( rc==SQLITE_NOMEM ) shell_out_of_memory();
if( pzErrMsg ){
shell_check_ooms(*pzErrMsg = save_err_msg(db, "in prepare", rc, zSql));
}
}else{
if( !pStmt ){
/* this happens for a comment or white-space */
zSql = skipWhite(zLeftover);
continue;
}
zStmtSql = sqlite3_sql(pStmt);
if( zStmtSql==0 ) zStmtSql = "";
else zStmtSql = skipWhite(zStmtSql);
/* save off the prepared statement handle and reset row count */
if( psx ){
psi->pStmt = pStmt;
psx->resultCount = 0;
}
/* Show the EXPLAIN QUERY PLAN if .eqp is on */
if( psx && psi->autoEQP && sqlite3_stmt_isexplain(pStmt)==0 ){
int triggerEQP = 0;
disable_debug_trace_modes();
sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, -1, &triggerEQP);
if( psi->autoEQP>=AUTOEQP_trigger ){
sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 1, 0);
}
zEQP = smprintf("EXPLAIN QUERY PLAN %s", zStmtSql);
rc = s3_prep_noom_free(db, &zEQP, &pExplain);
if( rc==SQLITE_OK ){
while( sqlite3_step(pExplain)==SQLITE_ROW ){
const char *zEQPLine = (const char*)sqlite3_column_text(pExplain,3);
int iEqpId = sqlite3_column_int(pExplain, 0);
int iParentId = sqlite3_column_int(pExplain, 1);
if( zEQPLine==0 ) zEQPLine = "";
if( zEQPLine[0]=='-' ) eqp_render(psi, 0);
eqp_append(psi, iEqpId, iParentId, zEQPLine);
}
eqp_render(psi, 0);
}
sqlite3_finalize(pExplain);
pExplain = 0;
if( psi->autoEQP>=AUTOEQP_full ){
/* Also do an EXPLAIN for ".eqp full" mode */
zEQP = smprintf("EXPLAIN %s", zStmtSql);
rc = s3_prep_noom_free(db, &zEQP, &pExplain);
if( rc==SQLITE_OK ){
explain_data_prepare(psi, pExplain);
psi->cMode = MODE_Explain;
{
#if SHELL_DATAIO_EXT
ExportHandler *pexSave = psi->pActiveExporter;
psi->pActiveExporter = psi->pFreeformExporter;
#endif
exec_prepared_stmt(psx, pExplain);
#if SHELL_DATAIO_EXT
psi->pActiveExporter = pexSave;
#endif
}
explain_data_delete(psi);
}
sqlite3_finalize(pExplain);
pExplain = 0;
}
if( psi->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){
sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 0, 0);
/* Reprepare pStmt before reactivating trace modes */
sqlite3_finalize(pStmt);
pStmt = 0;
s3_prepare_v2_noom(db, zSql, -1, &pStmt, 0);
if( psx ) psi->pStmt = pStmt;
}
restore_debug_trace_modes();
}
if( psx ){
psi->cMode = psi->mode;
if( psi->autoExplain ){
if( sqlite3_stmt_isexplain(pStmt)==1 ){
psi->cMode = MODE_Explain;
}
if( sqlite3_stmt_isexplain(pStmt)==2 ){
psi->cMode = MODE_EQP;
}
}
/* If the shell is currently in ".explain" mode, gather the extra
** data required to add indents to the output.*/
if( psi->cMode==MODE_Explain ){
explain_data_prepare(psi, pStmt);
}
}
bind_prepared_stmt(DBX(psx), pStmt, 1);
exec_prepared_stmt(psx, pStmt);
explain_data_delete(psi);
eqp_render(psi, 0);
/* print usage stats if stats on */
if( psx && psi->statsOn ){
display_stats(db, psi, 0);
}
/* print loop-counters if required */
if( psx && psi->scanstatsOn ){
display_scanstats(db, psi);
}
/* 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);
pStmt = 0;
if( rc!=SQLITE_NOMEM ) rc = rc2;
if( rc==SQLITE_OK ){
zSql = skipWhite(zLeftover);
}else if( pzErrMsg ){
shell_check_ooms(*pzErrMsg = save_err_msg(db, "stepping", rc, 0));
}
/* clear saved stmt handle */
if( psx ){
psi->pStmt = NULL;
}
}
} /* end while */
CHECK_RETURN_EQUAL(0, RESOURCE_FREE(mark));
return rc;
}
/*
** Release memory previously allocated by tableColumnList() and others.
** The *azCol object is a zero-terminated sequence of char pointers,
** all but first of which is to be sqlite3_free()'ed, after which the
** azCol object is to be sqlite3_free()'ed.
*/
static void freeNameList(char **azCol){
int i;
if( azCol!=0 ){
/* azCol[0] is a static string, not to be freed. */
for(i=1; azCol[i]; i++){
sqlite3_free(azCol[i]);
}
sqlite3_free(azCol);
}
}
/*
** Return a list of pointers to strings which are the names of all
** columns in table zTab. The memory to hold the names is dynamically
** allocated and must be released by the caller using a subsequent call
** to freeNameList().
**
** The azCol[0] entry is usually NULL. However, if zTab contains a rowid
** value that needs to be preserved, then azCol[0] is filled in with the
** name of the rowid column.
**
** The first regular column in the table is azCol[1]. The list is terminated
** by an entry with azCol[i]==0 for i>0. This is an invariant, maintained
** as the list is grown so that freeNameList() can always deal with it.
**
** This function can exit abruptly under OOM conditions.
*/
static char **tableColumnList(sqlite3 *db, const char *zTab, int preserveRowid){
char **azCol = 0;
sqlite3_stmt *pStmt;
char *zSql;
int nCol = 0;
int nAlloc = 0;
int nPK = 0; /* Number of PRIMARY KEY columns seen */
int isIPK = 0; /* True if one PRIMARY KEY column of type INTEGER */
ResourceMark mark = holder_mark();
AnyResourceHolder arh = { 0, (GenericFreer)freeNameList };
int rc;
zSql = smprintf("PRAGMA table_info=%Q", zTab);
rc = s3_prep_noom_free(db, &zSql, &pStmt);
if( rc ) return 0;
stmt_holder(pStmt); /* offset 0 */
any_ref_holder(&arh); /* offset 1, top */
while( sqlite3_step(pStmt)==SQLITE_ROW ){
if( nCol>=nAlloc-2 ){
int nAllocPrev = nAlloc;
nAlloc = nAlloc*2 + nCol + 10;
azCol = sqlite3_realloc(azCol, nAlloc*sizeof(azCol[0]));
shell_check_ooms(azCol);
memset(azCol+nAllocPrev, 0, (nAlloc-nAllocPrev)*sizeof(azCol[0]));
arh.pAny = azCol;
}
azCol[++nCol] = smprintf("%s", sqlite3_column_text(pStmt, 1));
shell_check_ooms(azCol[nCol]);
if( sqlite3_column_int(pStmt, 5) ){
nPK++;
if( nPK==1
&& sqlite3_stricmp((const char*)sqlite3_column_text(pStmt,2),
"INTEGER")==0
){
isIPK = 1;
}else{
isIPK = 0;
}
}
}
drop_holder(); /* Now that it's built, save it from takedown. */
RESOURCE_FREE(mark);
if( azCol==0 ) return 0;
any_ref_holder(&arh); /* offset 1 */
/* azCol[0] = 0; azCol[nCol+1] = 0; -- Done by memset() above. */
/* The decision of whether or not a rowid really needs to be preserved
** is tricky. We never need to preserve a rowid for a WITHOUT ROWID table
** or a table with an INTEGER PRIMARY KEY. We are unable to preserve
** rowids on tables where the rowid is inaccessible because there are other
** columns in the table named "rowid", "_rowid_", and "oid".
*/
if( preserveRowid && isIPK ){
/* If a single PRIMARY KEY column with type INTEGER was seen, then it
** might be an alias for the ROWID. But it might also be a WITHOUT ROWID
** table or a INTEGER PRIMARY KEY DESC column, neither of which are
** ROWID aliases. To distinguish these cases, check to see if
** there is a "pk" entry in "PRAGMA index_list". There will be
** no "pk" index if the PRIMARY KEY really is an alias for the ROWID.
*/
zSql = smprintf("SELECT 1 FROM pragma_index_list(%Q)"
" WHERE origin='pk'", zTab);
rc = s3_prep_noom_free(db, &zSql, &pStmt);
shell_check_nomem(rc);
if( rc ){
RESOURCE_FREE(mark);
return 0;
}
stmt_holder(pStmt);
rc = s3_step_noom(pStmt);
preserveRowid = rc==SQLITE_ROW;
}
if( preserveRowid ){
/* Only preserve the rowid if we can find a name to use for it. */
static char *azRowid[] = { "rowid", "_rowid_", "oid" };
int i, j;
for(j=0; j<3; j++){
for(i=1; i<=nCol; i++){
if( sqlite3_stricmp(azRowid[j],azCol[i])==0 ) break;
}
if( i>nCol ){
/* At this point, we know that azRowid[j] is not the name of any
** ordinary column in the table. Verify that azRowid[j] is a valid
** name for the rowid before adding it to azCol[0]. WITHOUT ROWID
** tables will fail this last check */
rc = sqlite3_table_column_metadata(db,0,zTab,azRowid[j],0,0,0,0,0);
if( rc==SQLITE_OK ) azCol[0] = azRowid[j];
break;
}
}
}
arh.pAny = 0; /* Save built list from takedown (again.) */
RESOURCE_FREE(mark);
return azCol;
}
/*
** Toggle the reverse_unordered_selects setting.
*/
static void toggleSelectOrder(sqlite3 *db){
sqlite3_stmt *pStmt = 0;
int iSetting = 0;
char zStmt[40];
s3_prepare_v2_noom(db, "PRAGMA reverse_unordered_selects", -1, &pStmt, 0);
if( sqlite3_step(pStmt)==SQLITE_ROW ){
iSetting = sqlite3_column_int(pStmt, 0);
}
sqlite3_finalize(pStmt);
sqlite3_snprintf(sizeof(zStmt), zStmt,
"PRAGMA reverse_unordered_selects(%d)", !iSetting);
s3_exec_noom(db, zStmt, 0, 0, 0);
}
/*
** This is a different callback routine used for dumping the database.
** Each row received by this callback consists of a table name,
** the table type ("index" or "table") and SQL to create the table.
** This routine should print text sufficient to recreate the table.
*/
static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){
int rc;
const char *zTable;
const char *zType;
const char *zSql;
ShellInState *psi = (ShellInState *)pArg;
ShellExState *psx = XSS(psi);
int dataOnly;
int noSys;
ResourceMark mark = holder_mark();
UNUSED_PARAMETER(azNotUsed);
if( nArg!=3 || azArg==0 ) return 0;
zTable = azArg[0];
zType = azArg[1];
zSql = azArg[2];
if( zTable==0 || zType==0 ) return 0;
dataOnly = (psi->shellFlgs & SHFLG_DumpDataOnly)!=0;
noSys = (psi->shellFlgs & SHFLG_DumpNoSys)!=0;
if( cli_strcmp(zTable, "sqlite_sequence")==0 && !noSys ){
if( !dataOnly ) raw_printf(psi->out, "DELETE FROM sqlite_sequence;\n");
}else if( sqlite3_strglob("sqlite_stat?", zTable)==0 && !noSys ){
if( !dataOnly ) raw_printf(psi->out, "ANALYZE sqlite_schema;\n");
}else if( psi->bAllowSysDump==0 && cli_strncmp(zTable, "sqlite_", 7)==0 ){
return 0;
}else if( dataOnly ){
/* no-op */
}else if( cli_strncmp(zSql, "CREATE VIRTUAL TABLE", 20)==0 ){
char *zIns;
if( !psi->writableSchema ){
raw_printf(psi->out, "PRAGMA writable_schema=ON;\n");
psi->writableSchema = 1;
}
zIns = smprintf("INSERT INTO sqlite_schema(type,name,tbl_name,rootpage,sql)"
"VALUES('table','%q','%q',0,'%q');",
zTable, zTable, zSql);
shell_check_ooms(zIns);
utf8_printf(psi->out, "%s\n", zIns);
sqlite3_free(zIns);
return 0;
}else{
printSchemaLine(psi->out, zSql, ";\n");
}
if( cli_strcmp(zType, "table")==0 ){
AnyResourceHolder arh = { 0, (GenericFreer)freeNameList };
ShellText sSelect;
ShellText sTable;
char **azCol;
int i;
const char *savedDestTable;
int savedMode;
int preserveRowid = (psi->shellFlgs & SHFLG_PreserveRowid)!=0;
azCol = tableColumnList(DBI(psi), zTable, preserveRowid);
if( azCol==0 ){
psi->nErr++;
return 0;
}
/* Hereafter, resource management is used (for a column list.) */
arh.pAny = azCol;
any_ref_holder(&arh);
/* Always quote the table name, even if it appears to be pure ascii,
** in case it is a keyword. Ex: INSERT INTO "table" ... */
initText(&sTable);
text_ref_holder(&sTable);
appendText(&sTable, zTable, quoteChar(zTable));
/* If preserving the rowid, add a column list after the table name.
** In other words: "INSERT INTO tab(rowid,a,b,c,...) VALUES(...)"
** instead of the usual "INSERT INTO tab VALUES(...)".
*/
if( azCol[0] ){
appendText(&sTable, "(", 0);
appendText(&sTable, azCol[0], 0);
for(i=1; azCol[i]; i++){
appendText(&sTable, ",", 0);
appendText(&sTable, azCol[i], quoteChar(azCol[i]));
}
appendText(&sTable, ")", 0);
}
/* Build an appropriate SELECT statement */
initText(&sSelect);
text_ref_holder(&sSelect);
appendText(&sSelect, "SELECT ", 0);
if( azCol[0] ){
appendText(&sSelect, azCol[0], 0);
appendText(&sSelect, ",", 0);
}
for(i=1; azCol[i]; i++){
appendText(&sSelect, azCol[i], quoteChar(azCol[i]));
if( azCol[i+1] ){
appendText(&sSelect, ",", 0);
}
}
appendText(&sSelect, " FROM ", 0);
appendText(&sSelect, zTable, quoteChar(zTable));
savedDestTable = psx->zDestTable;
savedMode = psi->mode;
psx->zDestTable = sTable.z;
psi->mode = psi->cMode = MODE_Insert;
rc = shell_exec(psx, sSelect.z, 0);
if( (rc&0xff)==SQLITE_CORRUPT ){
raw_printf(psi->out, "/****** CORRUPTION ERROR *******/\n");
toggleSelectOrder(DBX(psx));
shell_exec(psx, sSelect.z, 0);
toggleSelectOrder(DBX(psx));
}
psx->zDestTable = savedDestTable;
psi->mode = savedMode;
if( rc ) psi->nErr++;
RESOURCE_FREE(mark);
}
return 0;
}
/*
** Run zQuery. Use dump_callback() as the callback routine so that
** the contents of the query are output as SQL statements.
**
** If we get a SQLITE_CORRUPT error, rerun the query after appending
** "ORDER BY rowid DESC" to the end.
*/
static int run_schema_dump_query(
ShellInState *psi,
const char *zQuery
){
int rc;
ResourceMark mark = holder_mark();
char *zErr = 0;
sstr_ptr_holder(&zErr);
rc = s3_exec_noom(DBI(psi), zQuery, dump_callback, psi, &zErr);
if( rc==SQLITE_CORRUPT ){
char *zQ2;
int len = strlen30(zQuery);
raw_printf(psi->out, "/****** CORRUPTION ERROR *******/\n");
if( zErr ){
utf8_printf(psi->out, "/****** %s ******/\n", zErr);
sqlite3_free(zErr);
zErr = 0;
}
shell_check_oomm(zQ2 = malloc( len+100 ));
mmem_holder(zQ2);
sqlite3_snprintf(len+100, zQ2, "%s ORDER BY rowid DESC", zQuery);
rc = s3_exec_noom(DBI(psi), zQ2, dump_callback, psi, &zErr);
if( rc ){
utf8_printf(psi->out, "/****** ERROR: %s ******/\n", zErr);
}else{
rc = SQLITE_CORRUPT;
}
}
RESOURCE_FREE(mark);
return rc;
}
/* Configure help text generation to have coalesced secondary help lines
* with trailing newlines on all help lines. This allow help text to be
* representable as an array of two C-strings per dot-command.
*/
DISPATCH_CONFIG[
HELP_COALESCE=1
];
#define HELP_TEXT_FMTP ".%s"
#define HELP_TEXT_FMTS "%s"
/* Above HELP_COALESCE config and HELP_TEXT_FMT PP vars must track.
* Alternative is 0, ".%s\n" and "%s\n" .
*/
/* Forward references */
static int showHelp(FILE *out, const char *zPattern, ShellExState *);
static DotCmdRC process_input(ShellInState *psx);
static DotCommand *builtInCommand(int ix);
/*
** 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.
**
** This function always returns; no abrupt OOM exits are taken.
*/
static char *readFile(const char *zName, int *pnByte){
FILE *in = fopen(zName, "rb");
long nIn;
size_t nRead;
char *pBuf;
int rc;
if( in==0 ) return 0;
rc = fseek(in, 0, SEEK_END);
if( rc!=0 ){
raw_printf(stderr, "Error: '%s' not seekable\n", zName);
fclose(in);
return 0;
}
nIn = ftell(in);
rewind(in);
pBuf = sqlite3_malloc64( nIn+1 );
if( pBuf==0 ){
raw_printf(stderr, "Error: out of memory\n");
fclose(in);
return 0;
}
nRead = fread(pBuf, nIn, 1, in);
fclose(in);
if( nRead!=1 ){
sqlite3_free(pBuf);
raw_printf(stderr, "Error: cannot read '%s'\n", zName);
return 0;
}
pBuf[nIn] = 0;
if( pnByte ) *pnByte = nIn;
return pBuf;
}
#if defined(SQLITE_ENABLE_SESSION)
/*
** Close a single OpenSession object and release all of its associated
** resources.
*/
static void session_close(OpenSession *pSession){
int i;
sqlite3session_delete(pSession->p);
sqlite3_free(pSession->zName);
for(i=0; i<pSession->nFilter; i++){
sqlite3_free(pSession->azFilter[i]);
}
sqlite3_free(pSession->azFilter);
memset(pSession, 0, sizeof(OpenSession));
}
#endif
/*
** Close all OpenSession objects and release all associated resources.
*/
#if defined(SQLITE_ENABLE_SESSION)
static void session_close_all(ShellInState *psi, int i){
int j;
struct AuxDb *pAuxDb = i<0 ? psi->pAuxDb : &psi->aAuxDb[i];
for(j=0; j<pAuxDb->nSession; j++){
session_close(&pAuxDb->aSession[j]);
}
pAuxDb->nSession = 0;
}
#else
# define session_close_all(X,Y)
#endif
/*
** 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
#if SHELL_DYNAMIC_EXTENSION
static int notify_subscribers(ShellInState *psi, NoticeKind nk, void *pvs) {
int six = 0;
int rcFlags = 0;
ShellExState *psx = XSS(psi);
while( six < psi->numSubscriptions ){
struct EventSubscription *pes = psi->pSubscriptions + six++;
rcFlags |= pes->eventHandler(pes->pvUserData, nk, pvs, psx);
}
return rcFlags;
}
#endif
/*
** Try to deduce the type of file for zName based on its content. Return
** one of the SHELL_OPEN_* constants.
**
** If the file does not exist or is empty but its name looks like a ZIP
** archive and the dfltZip flag is true, then assume it is a ZIP archive.
** Otherwise, assume an ordinary database regardless of the filename if
** the type cannot be determined from content.
*/
u8 deduceDatabaseType(const char *zName, int dfltZip){
FILE *f = fopen(zName, "rb");
size_t n;
u8 rc = SHELL_OPEN_UNSPEC;
char zBuf[100];
if( f==0 ){
if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
return SHELL_OPEN_ZIPFILE;
}else{
return SHELL_OPEN_NORMAL;
}
}
n = fread(zBuf, 16, 1, f);
if( n==1 && memcmp(zBuf, "SQLite format 3", 16)==0 ){
fclose(f);
return SHELL_OPEN_NORMAL;
}
fseek(f, -25, SEEK_END);
n = fread(zBuf, 25, 1, f);
if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){
rc = SHELL_OPEN_APPENDVFS;
}else{
fseek(f, -22, SEEK_END);
n = fread(zBuf, 22, 1, f);
if( n==1 && zBuf[0]==0x50 && zBuf[1]==0x4b && zBuf[2]==0x05
&& zBuf[3]==0x06 ){
rc = SHELL_OPEN_ZIPFILE;
}else if( n==0 && dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
rc = SHELL_OPEN_ZIPFILE;
}
}
fclose(f);
return rc;
}
#ifndef SQLITE_OMIT_DESERIALIZE
/*
** Reconstruct an in-memory database using the output from the "dbtotxt"
** program. Read content from the file in p->aAuxDb[].zDbFilename.
** If p->aAuxDb[].zDbFilename is 0, then read from the present input.
*/
static unsigned char *readHexDb(ShellInState *psi, int *pnData){
ResourceMark mark = holder_mark();
unsigned char *a = 0;
int n = 0;
int pgsz = 0;
int iOffset = 0;
int j, k, nlError;
int rc;
static const char *zEndMarker = "| end ";
const char *zDbFilename = psi->pAuxDb->zDbFilename;
/* Need next two objects only if redirecting input to get the hex. */
InSource inRedir = INSOURCE_FILE_REDIR(0, zDbFilename, psi->pInSource);
AnyResourceHolder arh = { &psi->pInSource, (GenericFreer)finish_InSource };
unsigned int x[16];
char zLine[1000];
if( zDbFilename ){
inRedir.inFile = fopen(zDbFilename, "r");
if( inRedir.inFile==0 ){
utf8_printf(STD_ERR, "cannot open \"%s\" for reading\n", zDbFilename);
return 0;
}
psi->pInSource = &inRedir;
inRedir.closer.stream = fclose;
any_ref_holder(&arh);
}else{
/* Will read hex DB lines inline from present input, without redirect. */
if( INSOURCE_IS_INTERACTIVE(psi->pInSource) ){
printf("Reading hex DB from \"%s\", until end-marker input like:\n%s\n",
psi->pInSource->zSourceSay, zEndMarker);
fflush(STD_OUT);
}
}
*pnData = 0;
if( strLineGet(zLine,sizeof(zLine), psi->pInSource)==0 ) goto readHexDb_error;
rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz);
if( rc!=2 ) goto readHexDb_error;
if( n<0 ) goto readHexDb_error;
if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ) goto readHexDb_error;
n = (n+pgsz-1)&~(pgsz-1); /* Round n up to the next multiple of pgsz */
a = sqlite3_malloc( n ? n : 1 );
shell_check_ooms(a);
smem_holder(a);
memset(a, 0, n);
if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){
utf8_printf(STD_ERR, "invalid pagesize\n");
goto readHexDb_error;
}
while( strLineGet(zLine,sizeof(zLine), psi->pInSource)!=0 ){
rc = sscanf(zLine, "| page %d offset %d", &j, &k);
if( rc==2 ){
iOffset = k;
continue;
}
if( cli_strncmp(zLine, zEndMarker, 6)==0 ){
break;
}
rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
&j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7],
&x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]);
if( rc==17 ){
k = iOffset+j;
if( k+16<=n && k>=0 ){
int ii;
for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff;
}
}
}
*pnData = n; /* Record success and size. */
drop_holder();
readHexDb_cleanup:
RESOURCE_FREE(mark);
return a;
readHexDb_error:
nlError = psi->pInSource->lineno;
if( psi->pInSource!=&inRedir ){
/* Since taking input inline, consume through its end marker. */
while( strLineGet(zLine, sizeof(zLine), psi->pInSource)!=0 ){
if(cli_strncmp(zLine, zEndMarker, 6)==0 ) break;
}
}
a = 0;
utf8_printf(STD_ERR,"Error on line %d within --hexdb input\n", nlError);
goto readHexDb_cleanup;
}
#endif /* SQLITE_OMIT_DESERIALIZE */
/*
** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X.
*/
static void shellUSleepFunc(
sqlite3_context *context,
int argcUnused,
sqlite3_value **argv
){
int sleep = sqlite3_value_int(argv[0]);
(void)argcUnused;
sqlite3_sleep(sleep/1000);
sqlite3_result_int(context, sleep);
}
/*
** Attempt to close the database connection. Report errors.
** The close is done under mutex protection for globalDb.
*/
static void close_db(ShellInState *psi, sqlite3 *db){
int rc;
#if SHELL_DYNAMIC_EXTENSION
if( db!=0 ) notify_subscribers(psi, NK_DbAboutToClose, db);
#endif
if( db==globalDb ){
/* This should only block for the time needed to handle ^C interrupt. */
sqlite3_mutex_enter(pGlobalDbLock);
globalDb = 0;
rc = sqlite3_close(db);
sqlite3_mutex_leave(pGlobalDbLock);
}else{
rc = sqlite3_close(db);
}
if( rc ){
utf8_printf(STD_ERR, "Error: sqlite3_close() returns %d: %s\n",
rc, sqlite3_errmsg(db));
}
}
/* Flags for open_db(). ToDo: Conform comments to code or vice-versa.
**
** The default behavior of open_db() is to exit(1) if the database fails to
** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error
** but still returns without calling exit.
**
** The OPEN_DB_ZIPFILE flag causes open_db() to prefer to open files as a
** ZIP archive if the file does not exist or is empty and its name matches
** the *.zip pattern.
*/
#define OPEN_DB_KEEPALIVE 0x001 /* Return after error if true */
#define OPEN_DB_ZIPFILE 0x002 /* Open as ZIP if name matches *.zip */
/*
** Make sure the database is open. If it is not, then open it. If
** the database fails to open, print an error message and exit.
*/
static sqlite3 * open_db(ShellExState *psx, int openFlags){
ShellInState *psi = ISS(psx);
if( DBX(psx)==0 ){
sqlite3 **pDb = &DBX(psx);
const char *zDbFilename = psi->pAuxDb->zDbFilename;
if( psi->openMode==SHELL_OPEN_UNSPEC ){
if( zDbFilename==0 || zDbFilename[0]==0 ){
psi->openMode = SHELL_OPEN_NORMAL;
}else{
psi->openMode = deduceDatabaseType(zDbFilename,
(openFlags & OPEN_DB_ZIPFILE)!=0);
}
}
switch( psi->openMode ){
case SHELL_OPEN_APPENDVFS: {
sqlite3_open_v2
(zDbFilename, pDb,
SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|psi->openFlags,
"apndvfs");
break;
}
case SHELL_OPEN_HEXDB:
case SHELL_OPEN_DESERIALIZE: {
sqlite3_open(0, pDb);
break;
}
case SHELL_OPEN_ZIPFILE: {
sqlite3_open(":memory:", pDb);
break;
}
case SHELL_OPEN_READONLY: {
sqlite3_open_v2(zDbFilename, pDb,
SQLITE_OPEN_READONLY|psi->openFlags, 0);
break;
}
case SHELL_OPEN_UNSPEC:
case SHELL_OPEN_NORMAL: {
sqlite3_open_v2(zDbFilename, pDb,
SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|psi->openFlags, 0);
break;
}
}
globalDb = DBX(psx);
if( DBX(psx)==0 || SQLITE_OK!=sqlite3_errcode(DBX(psx)) ){
const char *zWhy = (DBX(psx)==0)? "(?)" : sqlite3_errmsg(DBX(psx));
utf8_printf(STD_ERR,"Error: unable to open database \"%s\": %s\n",
zDbFilename, zWhy);
close_db(psi,DBX(psx));
if( bail_on_error && !stdin_is_interactive ){
shell_terminate("-bail used in batch mode.");
}
if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){
shell_terminate("with OPEN_DB_KEEPALIVE.");
}
sqlite3_open(":memory:", &DBX(psx));
if( DBX(psx)==0 || SQLITE_OK!=sqlite3_errcode(DBX(psx)) ){
shell_terminate("Also: unable to open substitute in-memory database.");
}else{
utf8_printf(stderr,
"Notice: using substitute in-memory database instead of \"%s\"\n",
zDbFilename);
}
}
sqlite3_db_config(GLOBAL_DB,SQLITE_DBCONFIG_STMT_SCANSTATUS,(int)0,(int*)0);
/* Reflect the use or absence of --unsafe-testing invocation. */
{
int testmode_on = ShellHasFlag(psx,SHFLG_TestingMode);
sqlite3_db_config(GLOBAL_DB,SQLITE_DBCONFIG_TRUSTED_SCHEMA,testmode_on,0);
sqlite3_db_config(GLOBAL_DB,SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0);
}
#ifndef SQLITE_OMIT_LOAD_EXTENSION
sqlite3_enable_load_extension(GLOBAL_DB, 1);
#endif
sqlite3_shathree_init(GLOBAL_DB, 0, 0);
sqlite3_uint_init(GLOBAL_DB, 0, 0);
sqlite3_decimal_init(GLOBAL_DB, 0, 0);
sqlite3_base64_init(GLOBAL_DB, 0, 0);
sqlite3_base85_init(GLOBAL_DB, 0, 0);
sqlite3_regexp_init(GLOBAL_DB, 0, 0);
sqlite3_ieee_init(GLOBAL_DB, 0, 0);
sqlite3_series_init(GLOBAL_DB, 0, 0);
#ifndef SQLITE_SHELL_FIDDLE
sqlite3_fileio_init(GLOBAL_DB, 0, 0);
sqlite3_completion_init(GLOBAL_DB, 0, 0);
#endif
#ifdef SQLITE_HAVE_ZLIB
if( !psi->bSafeModeFuture ){
sqlite3_zipfile_init(GLOBAL_DB, 0, 0);
sqlite3_sqlar_init(GLOBAL_DB, 0, 0);
}
#endif
#ifdef SQLITE_SHELL_EXTFUNCS
/* Create a preprocessing mechanism for extensions to make
* their own provisions for being built into the shell.
* This is a short-span macro. See further below for usage.
*/
#define SHELL_SUB_MACRO(base, variant) base ## _ ## variant
#define SHELL_SUBMACRO(base, variant) SHELL_SUB_MACRO(base, variant)
/* Let custom-included extensions get their ..._init() called.
* The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause
* the extension's sqlite3_*_init( db, pzErrorMsg, pApi )
* initialization routine to be called.
*/
{
int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db);
/* Let custom-included extensions expose their functionality.
* The WHATEVER_EXPOSE( db, pzErrorMsg ) macro should cause
* the SQL functions, virtual tables, collating sequences or
* VFS's implemented by the extension to be registered.
*/
if( irc==SQLITE_OK
|| irc==SQLITE_OK_LOAD_PERMANENTLY ){
SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, EXPOSE)(p->db, 0);
}
#undef SHELL_SUB_MACRO
#undef SHELL_SUBMACRO
}
#endif
sqlite3_create_function(GLOBAL_DB, "strtod", 1, SQLITE_UTF8, 0,
shellStrtod, 0, 0);
sqlite3_create_function(GLOBAL_DB, "dtostr", 1, SQLITE_UTF8, 0,
shellDtostr, 0, 0);
sqlite3_create_function(GLOBAL_DB, "dtostr", 2, SQLITE_UTF8, 0,
shellDtostr, 0, 0);
sqlite3_create_function(GLOBAL_DB, "shell_add_schema", 3,SQLITE_UTF8,0,
shellAddSchemaName, 0, 0);
sqlite3_create_function(GLOBAL_DB, "shell_module_schema", 1,SQLITE_UTF8,0,
shellModuleSchema, 0, 0);
sqlite3_create_function(GLOBAL_DB, "shell_putsnl", 1,SQLITE_UTF8,psx,
shellPutsFunc, 0, 0);
sqlite3_create_function(GLOBAL_DB, "usleep", 1,SQLITE_UTF8,0,
shellUSleepFunc, 0, 0);
#ifndef SQLITE_NOHAVE_SYSTEM
sqlite3_create_function(GLOBAL_DB, "edit", 1, SQLITE_UTF8, 0,
editFunc, 0, 0);
sqlite3_create_function(GLOBAL_DB, "edit", 2, SQLITE_UTF8, 0,
editFunc, 0, 0);
#endif
if( psi->openMode==SHELL_OPEN_ZIPFILE ){
char *zSql = smprintf("CREATE VIRTUAL TABLE zip USING zipfile(%Q);",
zDbFilename);
shell_check_ooms(zSql);
sstr_holder(zSql);
s3_exec_noom(DBX(psx), zSql, 0, 0, 0);
release_holder();
}
#ifndef SQLITE_OMIT_DESERIALIZE
else if( psi->openMode==SHELL_OPEN_DESERIALIZE
|| psi->openMode==SHELL_OPEN_HEXDB ){
int rc;
int nData = 0;
unsigned char *aData;
if( psi->openMode==SHELL_OPEN_DESERIALIZE ){
aData = (unsigned char*)readFile(zDbFilename, &nData);
}else{
aData = readHexDb(psi, &nData);
if( aData==0 ){
return GLOBAL_DB;
}
}
rc = sqlite3_deserialize(DBX(psx), "main", aData, nData, nData,
SQLITE_DESERIALIZE_RESIZEABLE |
SQLITE_DESERIALIZE_FREEONCLOSE);
if( rc ){
utf8_printf(STD_ERR, "Error: sqlite3_deserialize() returns %d\n", rc);
}
if( psi->szMax>0 ){
sqlite3_file_control(DBX(psx), "main", SQLITE_FCNTL_SIZE_LIMIT,
&psi->szMax);
}
}
#endif
if( DBX(psx)!=0 ){
if( psi->bSafeModeFuture ){
sqlite3_set_authorizer(DBX(psx), safeModeAuth, psx);
}
sqlite3_db_config(
DBX(psx), SQLITE_DBCONFIG_STMT_SCANSTATUS, psi->scanstatsOn,(int*)0);
}
#if SHELL_DYNAMIC_EXTENSION
notify_subscribers(psi, NK_DbUserAppeared, DBX(psx));
#endif
}
return GLOBAL_DB;
}
#if HAVE_READLINE || HAVE_EDITLINE
/*
** Readline completion callbacks
*/
static char *readline_completion_generator(const char *text, int state){
static sqlite3_stmt *pStmt = 0;
char *zRet;
if( state==0 ){
char *zSql;
sqlite3_finalize(pStmt);
pStmt = 0;
zSql = smprintf("SELECT DISTINCT candidate COLLATE nocase"
" FROM completion(%Q) ORDER BY 1", text);
s3_prep_noom_free(GLOBAL_DB, &zSql, &pStmt);
}
if( sqlite3_step(pStmt)==SQLITE_ROW ){
const char *z = (const char*)sqlite3_column_text(pStmt,0);
if( z!=0 ){
zRet = strdup(z);
shell_check_oomm(zRet);
}
}else{
sqlite3_finalize(pStmt);
pStmt = 0;
zRet = 0;
}
return zRet;
}
static char **readline_completion(const char *zText, int iStart, int iEnd){
(void)iStart;
(void)iEnd;
rl_attempted_completion_over = 1;
return rl_completion_matches(zText, readline_completion_generator);
}
#elif HAVE_LINENOISE
/*
** Linenoise completion callback
*/
static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){
i64 nLine = strlen(zLine);
i64 i, iStart;
sqlite3_stmt *pStmt = 0;
char *zSql;
char zBuf[1000];
if( nLine>(i64)sizeof(zBuf)-30 ) return;
if( zLine[0]=='.' || zLine[0]=='#') return;
for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){}
if( i==nLine-1 ) return;
iStart = i+1;
memcpy(zBuf, zLine, iStart);
zSql = smprintf("SELECT DISTINCT candidate COLLATE nocase"
" FROM completion(%Q,%Q) ORDER BY 1",
&zLine[iStart], zLine);
s3_prep_noom_free(GLOBAL_DB, &zSql, &pStmt);
sqlite3_exec(GLOBAL_DB, "PRAGMA page_count", 0, 0, 0); /* Load the schema */
while( sqlite3_step(pStmt)==SQLITE_ROW ){
const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0);
int nCompletion = sqlite3_column_bytes(pStmt, 0);
if( iStart+nCompletion < (i64)sizeof(zBuf)-1 && zCompletion ){
memcpy(zBuf+iStart, zCompletion, nCompletion+1);
linenoiseAddCompletion(lc, zBuf);
}
}
sqlite3_finalize(pStmt);
}
#endif
/*
** Do C-language style escape sequence translation.
**
** \a -> alarm
** \b -> backspace
** \t -> tab
** \n -> newline
** \v -> vertical tab
** \f -> form feed
** \r -> carriage return
** \s -> space
** \" -> "
** \' -> '
** \\ -> backslash
** \NNN -> ascii character NNN in octal
** \xHH -> ascii character HH in hexadecimal
*/
static void resolve_backslashes(char *z){
int i, j;
char c;
while( *z && *z!='\\' ) z++;
for(i=j=0; (c = z[i])!=0; i++, j++){
if( c=='\\' && z[i+1]!=0 ){
c = z[++i];
if( c=='a' ){
c = '\a';
}else if( c=='b' ){
c = '\b';
}else if( c=='t' ){
c = '\t';
}else if( c=='n' ){
c = '\n';
}else if( c=='v' ){
c = '\v';
}else if( c=='f' ){
c = '\f';
}else if( c=='r' ){
c = '\r';
}else if( c=='"' ){
c = '"';
}else if( c=='\'' ){
c = '\'';
}else if( c=='\\' ){
c = '\\';
}else if( c=='x' ){
int nhd = 0, hdv;
u8 hv = 0;
while( nhd<2 && (c=z[i+1+nhd])!=0 && (hdv=hexDigitValue(c))>=0 ){
hv = (u8)((hv<<4)|hdv);
++nhd;
}
i += nhd;
c = hv;
}else if( c>='0' && c<='7' ){
c -= '0';
if( z[i+1]>='0' && z[i+1]<='7' ){
i++;
c = (c<<3) + z[i] - '0';
if( z[i+1]>='0' && z[i+1]<='7' ){
i++;
c = (c<<3) + z[i] - '0';
}
}
}
}
z[j] = c;
}
if( j<i ) z[j] = 0;
}
/*
** Interpret zArg as either an integer or a boolean value. Return 1 or 0
** for TRUE and FALSE. Return the integer value if appropriate.
*/
static int booleanValue(const char *zArg){
static const char *zBoolNames[] = {
"no","yes", "off","on",
#ifdef BOOLNAMES_ARE_BOOLEAN
"false","true",
#endif
0
};
int i;
if( zArg[0]=='0' && zArg[1]=='x' ){
for(i=2; hexDigitValue(zArg[i])>=0; i++){}
}else{
for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){}
}
if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff);
for( i=0; zBoolNames[i]!=0; ++i ){
if( sqlite3_stricmp(zArg, zBoolNames[i])==0 ) return i&1;
}
utf8_printf(STD_ERR, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n",
zArg);
return 0;
}
/*
** Set or clear a shell flag according to a boolean value.
*/
static void setOrClearFlag(ShellExState *psx, unsigned mFlag, const char *zArg){
if( booleanValue(zArg) ){
ShellSetFlag(psx, mFlag);
}else{
ShellClearFlag(psx, mFlag);
}
}
/*
** Close an output file, provided it is not stderr or stdout
*/
static void output_file_close(FILE *f){
if( f && f!=STD_OUT && f!=STD_ERR ) fclose(f);
}
/*
** Try to open an output file. The names "stdout" and "stderr" are
** recognized and do the right thing. NULL is returned if the output
** filename is "off".
*/
static FILE *output_file_open(const char *zFile, int bTextMode){
FILE *f;
if( cli_strcmp(zFile,"stdout")==0 ){
f = STD_OUT;
}else if( cli_strcmp(zFile, "stderr")==0 ){
f = STD_ERR;
}else if( cli_strcmp(zFile, "off")==0 ){
f = 0;
}else{
f = fopen(zFile, bTextMode ? "w" : "wb");
if( f==0 ){
utf8_printf(STD_ERR, "Error: cannot open \"%s\"\n", zFile);
}
}
return f;
}
#ifndef SQLITE_OMIT_TRACE
/*
** A routine for handling output from sqlite3_trace().
*/
static int sql_trace_callback(
unsigned mType, /* The trace type */
void *pArg, /* The shell state pointer */
void *pP, /* Usually a pointer to sqlite_stmt */
void *pX /* Auxiliary output */
){
ShellInState *psi = (ShellInState*)pArg;
sqlite3_stmt *pStmt;
const char *zSql;
i64 nSql;
if( psi->traceOut==0 ) return 0;
if( mType==SQLITE_TRACE_CLOSE ){
utf8_printf(psi->traceOut, "-- closing database connection\n");
return 0;
}
if( mType!=SQLITE_TRACE_ROW && pX!=0 && ((const char*)pX)[0]=='-' ){
zSql = (const char*)pX;
}else{
pStmt = (sqlite3_stmt*)pP;
switch( psi->eTraceType ){
case SHELL_TRACE_EXPANDED: {
zSql = sqlite3_expanded_sql(pStmt);
break;
}
#ifdef SQLITE_ENABLE_NORMALIZE
case SHELL_TRACE_NORMALIZED: {
zSql = sqlite3_normalized_sql(pStmt);
break;
}
#endif
default: {
zSql = sqlite3_sql(pStmt);
break;
}
}
}
if( zSql==0 ) return 0;
nSql = strlen(zSql);
if( nSql>1000000000 ) nSql = 1000000000; /* clamp to 1 billion */
while( nSql>0 && zSql[nSql-1]==';' ){ nSql--; }
switch( mType ){
case SQLITE_TRACE_ROW:
case SQLITE_TRACE_STMT: {
utf8_printf(psi->traceOut, "%.*s;\n", (int)nSql, zSql);
break;
}
case SQLITE_TRACE_PROFILE: {
sqlite3_int64 nNanosec = pX ? *(sqlite3_int64*)pX : 0;
utf8_printf(psi->traceOut,"%.*s; -- %lld ns\n", (int)nSql,zSql,nNanosec);
break;
}
}
return 0;
}
#endif
/*
** A no-op routine that runs with the ".breakpoint" dot-command.
** This is a useful spot to set a debugger breakpoint.
**
** This routine does not do anything practical. The code are there simply
** to prevent the compiler from optimizing this routine out.
*/
static void test_breakpoint(void){
static int nCall = 0;
if( (nCall++)==0xffffffff ) printf("Many .breakpoints have run\n");
}
/*
** An object used to read a CSV and other files for import.
*/
typedef struct ImportCtx ImportCtx;
struct ImportCtx {
const char *zFile; /* Name of the input file */
FILE *in; /* Read the CSV text from this input stream */
int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close in */
char *z; /* Accumulated text for a field */
int n; /* Number of bytes in z */
int nAlloc; /* Space allocated for z[] */
int nLine; /* Current line number */
int nRow; /* Number of rows imported */
int nErr; /* Number of errors encountered */
int bNotFirst; /* True if one or more bytes already read */
int cTerm; /* Character that terminated the most recent field */
int cColSep; /* The column separator character. (Usually ",") */
int cRowSep; /* The row separator character. (Usually "\n") */
};
/* Clean up resourced used by an ImportCtx */
static void import_cleanup(ImportCtx *p){
if( p->in!=0 && p->xCloser!=0 ){
p->xCloser(p->in);
p->in = 0;
}
sqlite3_free(p->z);
p->z = 0;
}
/* Append a single byte to z[] */
static void import_append_char(ImportCtx *p, int c){
if( p->n+1>=p->nAlloc ){
p->nAlloc += p->nAlloc + 100;
p->z = sqlite3_realloc64(p->z, p->nAlloc);
shell_check_ooms(p->z);
}
p->z[p->n++] = (char)c;
}
/* Read a single field of CSV text. Compatible with rfc4180 and extended
** with the option of having a separator other than ",".
**
** + Input comes from p->in.
** + Store results in p->z of length p->n. Space to hold p->z comes
** from sqlite3_malloc64().
** + Use p->cSep as the column separator. The default is ",".
** + Use p->rSep as the row separator. The default is "\n".
** + Keep track of the line number in p->nLine.
** + Store the character that terminates the field in p->cTerm. Store
** EOF on end-of-file.
** + Report syntax errors on stderr
*/
static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){
int c;
int cSep = (u8)p->cColSep;
int rSep = (u8)p->cRowSep;
p->n = 0;
c = fgetc(p->in);
if( c==EOF || seenInterrupt ){
p->cTerm = EOF;
return 0;
}
if( c=='"' ){
int pc, ppc;
int startLine = p->nLine;
int cQuote = c;
pc = ppc = 0;
while( 1 ){
c = fgetc(p->in);
if( c==rSep ) p->nLine++;
if( c==cQuote ){
if( pc==cQuote ){
pc = 0;
continue;
}
}
if( (c==cSep && pc==cQuote)
|| (c==rSep && pc==cQuote)
|| (c==rSep && pc=='\r' && ppc==cQuote)
|| (c==EOF && pc==cQuote)
){
do{ p->n--; }while( p->z[p->n]!=cQuote );
p->cTerm = c;
break;
}
if( pc==cQuote && c!='\r' ){
utf8_printf(STD_ERR, "%s:%d: unescaped %c character\n",
p->zFile, p->nLine, cQuote);
}
if( c==EOF ){
utf8_printf(STD_ERR, "%s:%d: unterminated %c-quoted field\n",
p->zFile, startLine, cQuote);
p->cTerm = c;
break;
}
import_append_char(p, c);
ppc = pc;
pc = c;
}
}else{
/* If this is the first field being parsed and it begins with the
** UTF-8 BOM (0xEF BB BF) then skip the BOM */
if( (c&0xff)==0xef && p->bNotFirst==0 ){
import_append_char(p, c);
c = fgetc(p->in);
if( (c&0xff)==0xbb ){
import_append_char(p, c);
c = fgetc(p->in);
if( (c&0xff)==0xbf ){
p->bNotFirst = 1;
p->n = 0;
return csv_read_one_field(p);
}
}
}
while( c!=EOF && c!=cSep && c!=rSep ){
import_append_char(p, c);
c = fgetc(p->in);
}
if( c==rSep ){
p->nLine++;
if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--;
}
p->cTerm = c;
}
if( p->z ) p->z[p->n] = 0;
p->bNotFirst = 1;
return p->z;
}
/* Read a single field of ASCII delimited text.
**
** + Input comes from p->in.
** + Store results in p->z of length p->n. Space to hold p->z comes
** from sqlite3_malloc64().
** + Use p->cSep as the column separator. The default is "\x1F".
** + Use p->rSep as the row separator. The default is "\x1E".
** + Keep track of the row number in p->nLine.
** + Store the character that terminates the field in p->cTerm. Store
** EOF on end-of-file.
** + Report syntax errors on stderr
*/
static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p){
int c;
int cSep = (u8)p->cColSep;
int rSep = (u8)p->cRowSep;
p->n = 0;
c = fgetc(p->in);
if( c==EOF || seenInterrupt ){
p->cTerm = EOF;
return 0;
}
while( c!=EOF && c!=cSep && c!=rSep ){
import_append_char(p, c);
c = fgetc(p->in);
}
if( c==rSep ){
p->nLine++;
}
p->cTerm = c;
if( p->z ) p->z[p->n] = 0;
return p->z;
}
/*
** Try to transfer data for table zTable. If an error is seen while
** moving forward, try to go backwards. The backwards movement won't
** work for WITHOUT ROWID tables.
*/
static void tryToCloneData(
ShellExState *psx,
sqlite3 *newDb,
const char *zTable
){
sqlite3_stmt *pQuery = 0;
sqlite3_stmt *pInsert = 0;
char *zQuery = 0;
char *zInsert = 0;
int rc;
int i, j, n;
int nTable = strlen30(zTable);
int k = 0;
int cnt = 0;
const int spinRate = 10000;
shell_check_ooms(zQuery = smprintf("SELECT * FROM \"%w\"", zTable));
rc = s3_prepare_v2_noom(DBX(psx), zQuery, -1, &pQuery, 0);
if( rc ){
utf8_printf(STD_ERR, "Error %d: %s on [%s]\n",
sqlite3_extended_errcode(DBX(psx)), sqlite3_errmsg(DBX(psx)),
zQuery);
goto end_data_xfer;
}
n = sqlite3_column_count(pQuery);
zInsert = sqlite3_malloc64(200 + nTable + n*3);
shell_check_ooms(zInsert);
sqlite3_snprintf(200+nTable,zInsert,
"INSERT OR IGNORE INTO \"%s\" VALUES(?", zTable);
i = strlen30(zInsert);
for(j=1; j<n; j++){
memcpy(zInsert+i, ",?", 2);
i += 2;
}
memcpy(zInsert+i, ");", 3);
rc = s3_prepare_v2_noom(newDb, zInsert, -1, &pInsert, 0);
if( rc ){
utf8_printf(STD_ERR, "Error %d: %s on [%s]\n",
sqlite3_extended_errcode(newDb), sqlite3_errmsg(newDb),
zInsert);
goto end_data_xfer;
}
for(k=0; k<2; k++){
while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){
for(i=0; i<n; i++){
switch( sqlite3_column_type(pQuery, i) ){
case SQLITE_NULL: {
sqlite3_bind_null(pInsert, i+1);
break;
}
case SQLITE_INTEGER: {
sqlite3_bind_int64(pInsert, i+1, sqlite3_column_int64(pQuery,i));
break;
}
case SQLITE_FLOAT: {
sqlite3_bind_double(pInsert, i+1, sqlite3_column_double(pQuery,i));
break;
}
case SQLITE_TEXT: {
sqlite3_bind_text(pInsert, i+1,
(const char*)sqlite3_column_text(pQuery,i),
-1, SQLITE_STATIC);
break;
}
case SQLITE_BLOB: {
sqlite3_bind_blob(pInsert, i+1, sqlite3_column_blob(pQuery,i),
sqlite3_column_bytes(pQuery,i),
SQLITE_STATIC);
break;
}
}
} /* End for */
rc = sqlite3_step(pInsert);
if( rc!=SQLITE_OK && rc!=SQLITE_ROW && rc!=SQLITE_DONE ){
utf8_printf(STD_ERR, "Error %d: %s\n", sqlite3_extended_errcode(newDb),
sqlite3_errmsg(newDb));
}
sqlite3_reset(pInsert);
cnt++;
if( (cnt%spinRate)==0 ){
fprintf(STD_OUT, "%c\b", "|/-\\"[(cnt/spinRate)%4]);
fflush(STD_OUT);
}
} /* End while */
if( rc==SQLITE_DONE ) break;
sqlite3_finalize(pQuery);
sqlite3_free(zQuery);
zQuery = smprintf("SELECT * FROM \"%w\" ORDER BY rowid DESC;", zTable);
rc = s3_prep_noom_free(DBX(psx), &zQuery, &pQuery);
if( rc ){
utf8_printf(STD_ERR, "Warning: cannot step \"%s\" backwards", zTable);
break;
}
} /* End for(k=0...) */
end_data_xfer:
sqlite3_finalize(pQuery);
sqlite3_finalize(pInsert);
sqlite3_free(zQuery);
sqlite3_free(zInsert);
}
/*
** Try to transfer all rows of the schema that match zWhere. For
** each row, invoke xForEach() on the object defined by that row.
** If an error is encountered while moving forward through the
** sqlite_schema table, try again moving backwards.
*/
static void tryToCloneSchema(
ShellExState *psx,
sqlite3 *newDb,
const char *zWhere,
void (*xForEach)(ShellExState*,sqlite3*,const char*)
){
sqlite3_stmt *pQuery = 0;
char *zQuery = 0;
int rc;
const unsigned char *zName;
const unsigned char *zSql;
char *zErrMsg = 0;
RESOURCE_MARK(mark);
zQuery = smprintf("SELECT name, sql FROM sqlite_schema"
" WHERE %s ORDER BY rowid ASC", zWhere);
shell_check_ooms(zQuery);
sstr_ptr_holder(&zQuery);
rc = s3_prepare_v2_noom(DBX(psx), zQuery, -1, &pQuery, 0);
if( rc ){
utf8_printf(STD_ERR, "Error: (%d) %s on [%s]\n",
sqlite3_extended_errcode(DBX(psx)),
sqlite3_errmsg(DBX(psx)), zQuery);
goto end_schema_xfer;
}
stmt_ptr_holder(&pQuery);
while( (rc = s3_step_noom(pQuery))==SQLITE_ROW ){
zName = sqlite3_column_text(pQuery, 0);
zSql = sqlite3_column_text(pQuery, 1);
if( zName==0 || zSql==0 ) continue;
if( sqlite3_stricmp((char*)zName, "sqlite_sequence")!=0 ){
/* Consider directing this output to current output. */
fprintf(STD_OUT, "%s... ", zName); fflush(STD_OUT);
s3_exec_noom(newDb, (const char*)zSql, 0, 0, &zErrMsg);
if( zErrMsg ){
utf8_printf(STD_ERR, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql);
sqlite3_free(zErrMsg);
zErrMsg = 0;
}
}
if( xForEach ){
xForEach(psx, newDb, (const char*)zName);
}
/* Consider directing this output to current output. */
fprintf(STD_OUT, "done\n");
}
if( rc!=SQLITE_DONE ){
sqlite3_finalize(pQuery);
pQuery = 0;
sqlite3_free(zQuery);
zQuery = smprintf("SELECT name, sql FROM sqlite_schema"
" WHERE %s ORDER BY rowid DESC", zWhere);
shell_check_ooms(zQuery);
rc = s3_prepare_v2_noom(DBX(psx), zQuery, -1, &pQuery, 0);
if( rc ){
utf8_printf(STD_ERR, "Error: (%d) %s on [%s]\n",
sqlite3_extended_errcode(DBX(psx)),
sqlite3_errmsg(DBX(psx)), zQuery);
goto end_schema_xfer;
}
while( (rc = s3_step_noom(pQuery))==SQLITE_ROW ){
zName = sqlite3_column_text(pQuery, 0);
zSql = sqlite3_column_text(pQuery, 1);
if( zName==0 || zSql==0 ) continue;
if( sqlite3_stricmp((char*)zName, "sqlite_sequence")==0 ) continue;
/* Consider directing ... */
fprintf(STD_OUT, "%s... ", zName); fflush(STD_OUT);
s3_exec_noom(newDb, (const char*)zSql, 0, 0, &zErrMsg);
if( zErrMsg ){
utf8_printf(STD_ERR, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql);
sqlite3_free(zErrMsg);
zErrMsg = 0;
}
if( xForEach ){
xForEach(psx, newDb, (const char*)zName);
}
/* Consider directing ... */
fprintf(STD_OUT, "done\n");
}
}
end_schema_xfer:
RESOURCE_FREE(mark);
}
/*
** Open a new database file named "zNewDb". Try to recover as much information
** as possible out of the main database (which might be corrupt) and write it
** into zNewDb.
*/
static void tryToClone(ShellExState *psx, const char *zNewDb){
int rc;
sqlite3 *newDb = 0;
if( access(zNewDb,0)==0 ){
utf8_printf(STD_ERR, "File \"%s\" already exists.\n", zNewDb);
return;
}
rc = sqlite3_open(zNewDb, &newDb);
if( rc ){
utf8_printf(STD_ERR, "Cannot create output database: %s\n",
sqlite3_errmsg(newDb));
}else{
// sqlite3_exec(DBX(psx), "PRAGMA writable_schema=ON;", 0, 0, 0);
sqlite3_db_config(newDb, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, 0);
sqlite3_db_config(newDb, SQLITE_DBCONFIG_DEFENSIVE, 0, 0);
sqlite3_exec(newDb, "BEGIN EXCLUSIVE;", 0, 0, 0);
tryToCloneSchema(psx, newDb, "type='table'", tryToCloneData);
tryToCloneSchema(psx, newDb, "type!='table'", 0);
sqlite3_exec(newDb, "COMMIT;", 0, 0, 0);
// sqlite3_exec(DBX(psx), "PRAGMA writable_schema=OFF;", 0, 0, 0);
}
close_db(ISS(psx),newDb);
}
/*
** Change the output file back to stdout.
**
** If the psi->doXdgOpen flag is set, that means the output was being
** redirected to a temporary file named by psi->zTempFile. In that case,
** launch start/open/xdg-open on that temporary file.
*/
static void output_reset(ShellInState *psi){
if( psi->outfile[0]=='|' ){
#ifndef SQLITE_OMIT_POPEN
pclose(psi->out);
#endif
}else{
output_file_close(psi->out);
#ifndef SQLITE_NOHAVE_SYSTEM
if( psi->doXdgOpen ){
const char *zXdgOpenCmd =
#if defined(_WIN32)
"start";
#elif defined(__APPLE__)
"open";
#else
"xdg-open";
#endif
char *zCmd;
zCmd = smprintf("%s %s", zXdgOpenCmd, psi->zTempFile);
if( system(zCmd) ){
utf8_printf(STD_ERR, "Failed: [%s]\n", zCmd);
}else{
/* Give the start/open/xdg-open command some time to get
** going before we continue, and potential delete the
** psi->zTempFile data file out from under it */
sqlite3_sleep(2000);
}
sqlite3_free(zCmd);
outputModePop(psi);
psi->doXdgOpen = 0;
}
#endif /* !defined(SQLITE_NOHAVE_SYSTEM) */
}
psi->outfile[0] = 0;
psi->out = STD_OUT;
}
/*
** Run an SQL command and return the single integer result.
** No parameter binding is done.
*/
static int db_int(sqlite3 *db, const char *zSql){
sqlite3_stmt *pStmt;
int res = 0;
s3_prepare_v2_noom(db, zSql, -1, &pStmt, 0);
stmt_holder(pStmt);
if( pStmt && s3_step_noom(pStmt)==SQLITE_ROW ){
res = sqlite3_column_int(pStmt,0);
}
release_holder();
return res;
}
/*
** Run an SQL command and return the single text result,
** Parameter binding is done iff bBind is true.
** The return must be freed by caller using sqlite3_free().
*/
static char *db_text(sqlite3 *db, const char *zSql, int bBind){
sqlite3_stmt *pStmt;
char *zRes = 0;
if( s3_prepare_v2_noom(db, zSql, -1, &pStmt, 0)==SQLITE_OK && pStmt!=0 ){
stmt_holder(pStmt);
if( bBind ) bind_prepared_stmt(db, pStmt, 0);
if( s3_step_noom(pStmt)==SQLITE_ROW ){
shell_check_ooms(zRes = smprintf("%s", sqlite3_column_text(pStmt,0)));
}
release_holder();
}
return zRes;
}
#if SQLITE_SHELL_HAVE_RECOVER
/*
** Convert a 2-byte or 4-byte big-endian integer into a native integer
*/
static unsigned int get2byteInt(unsigned char *a){
return (a[0]<<8) + a[1];
}
static unsigned int get4byteInt(unsigned char *a){
return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3];
}
/*
** Implementation of the ".dbinfo" command.
**
** Return 1 on error, 2 to exit, and 0 otherwise.
*/
static int shell_dbinfo_command(ShellExState *psx, int nArg, char **azArg){
static const struct { const char *zName; int ofst; } aField[] = {
{ "file change counter:", 24 },
{ "database page count:", 28 },
{ "freelist page count:", 36 },
{ "schema cookie:", 40 },
{ "schema format:", 44 },
{ "default cache size:", 48 },
{ "autovacuum top root:", 52 },
{ "incremental vacuum:", 64 },
{ "text encoding:", 56 },
{ "user version:", 60 },
{ "application id:", 68 },
{ "software version:", 96 },
};
static const struct { const char *zName; const char *zSql; } aQuery[] = {
{ "number of tables:",
"SELECT count(*) FROM %s WHERE type='table'" },
{ "number of indexes:",
"SELECT count(*) FROM %s WHERE type='index'" },
{ "number of triggers:",
"SELECT count(*) FROM %s WHERE type='trigger'" },
{ "number of views:",
"SELECT count(*) FROM %s WHERE type='view'" },
{ "schema size:",
"SELECT total(length(sql)) FROM %s" },
};
int i, rc;
unsigned iDataVersion;
char *zSchemaTab;
char *zDb = nArg>=2 ? azArg[1] : "main";
sqlite3_stmt *pStmt = 0;
unsigned char aHdr[100];
FILE *out = ISS(psx)->out;
open_db(psx, 0);
if( DBX(psx)==0 ) return 1;
rc = s3_prepare_v2_noom(DBX(psx),
"SELECT data FROM sqlite_dbpage(?1) WHERE pgno=1",
-1, &pStmt, 0);
if( rc ){
utf8_printf(STD_ERR, "error: %s\n", sqlite3_errmsg(DBX(psx)));
sqlite3_finalize(pStmt);
return 1;
}
sqlite3_bind_text(pStmt, 1, zDb, -1, SQLITE_STATIC);
if( sqlite3_step(pStmt)==SQLITE_ROW
&& sqlite3_column_bytes(pStmt,0)>100
){
const u8 *pb = sqlite3_column_blob(pStmt,0);
shell_check_ooms(pb);
memcpy(aHdr, pb, 100);
sqlite3_finalize(pStmt);
}else{
raw_printf(STD_ERR, "unable to read database header\n");
sqlite3_finalize(pStmt);
return 1;
}
i = get2byteInt(aHdr+16);
if( i==1 ) i = 65536;
utf8_printf(out, "%-20s %d\n", "database page size:", i);
utf8_printf(out, "%-20s %d\n", "write format:", aHdr[18]);
utf8_printf(out, "%-20s %d\n", "read format:", aHdr[19]);
utf8_printf(out, "%-20s %d\n", "reserved bytes:", aHdr[20]);
for(i=0; i<ArraySize(aField); i++){
int ofst = aField[i].ofst;
unsigned int val = get4byteInt(aHdr + ofst);
utf8_printf(out, "%-20s %u", aField[i].zName, val);
switch( ofst ){
case 56: {
if( val==1 ) raw_printf(out, " (utf8)");
if( val==2 ) raw_printf(out, " (utf16le)");
if( val==3 ) raw_printf(out, " (utf16be)");
}
}
raw_printf(out, "\n");
}
if( zDb==0 ){
zSchemaTab = smprintf("main.sqlite_schema");
}else if( cli_strcmp(zDb,"temp")==0 ){
zSchemaTab = smprintf("%s", "sqlite_temp_schema");
}else{
zSchemaTab = smprintf("\"%w\".sqlite_schema", zDb);
}
for(i=0; i<ArraySize(aQuery); i++){
char *zSql = smprintf(aQuery[i].zSql, zSchemaTab);
int val = db_int(DBX(psx), zSql);
sqlite3_free(zSql);
utf8_printf(out, "%-20s %d\n", aQuery[i].zName, val);
}
sqlite3_free(zSchemaTab);
sqlite3_file_control(DBX(psx), zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion);
utf8_printf(out, "%-20s %u\n", "data version", iDataVersion);
return 0;
}
#endif /* SQLITE_SHELL_HAVE_RECOVER */
/*
** Print the current sqlite3_errmsg() value to stderr and return 1.
*/
static int shellDatabaseError(sqlite3 *db){
const char *zErr = sqlite3_errmsg(db);
utf8_printf(STD_ERR, "Error: %s\n", zErr);
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;
zGlob = skipWhite(zGlob);
z = skipWhite(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;
}
}
return *skipWhite(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 cli_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;
}
/*
** Try to delete the temporary file (if there is one) and free the
** memory used to hold the name of the temp file.
*/
static void clearTempFile(ShellInState *psi){
if( psi->zTempFile==0 ) return;
if( psi->doXdgOpen ) return;
if( shellDeleteFile(psi->zTempFile) ) return;
sqlite3_free(psi->zTempFile);
psi->zTempFile = 0;
}
/*
** Create a new temp file name with the given suffix.
*/
static void newTempFile(ShellInState *psi, const char *zSuffix){
clearTempFile(psi);
sqlite3_free(psi->zTempFile);
psi->zTempFile = 0;
if( DBI(psi) ){
sqlite3_file_control(DBI(psi), 0, SQLITE_FCNTL_TEMPFILENAME,
&psi->zTempFile);
}
if( psi->zTempFile==0 ){
/* If DB is an in-memory database then the TEMPFILENAME file-control
** will not work and we will need to fallback to guessing */
char *zTemp;
sqlite3_uint64 r;
sqlite3_randomness(sizeof(r), &r);
zTemp = getenv("TEMP");
if( zTemp==0 ) zTemp = getenv("TMP");
if( zTemp==0 ){
#ifdef _WIN32
zTemp = "\\tmp";
#else
zTemp = "/tmp";
#endif
}
psi->zTempFile = smprintf("%s/temp%llx.%s", zTemp, r, zSuffix);
}else{
psi->zTempFile = smprintf("%z.%s", psi->zTempFile, zSuffix);
}
shell_check_ooms(psi->zTempFile);
}
/*
** 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 = smprintf(" COLLATE %s", zParentSeq);
sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT);
sqlite3_free(z);
}
}
#if !defined SQLITE_OMIT_VIRTUALTABLE
static void shellPrepare(
sqlite3 *db,
int *pRc,
const char *zSql,
sqlite3_stmt **ppStmt
){
*ppStmt = 0;
if( *pRc==SQLITE_OK ){
int rc = s3_prepare_v2_noom(db, zSql, -1, ppStmt, 0);
if( rc!=SQLITE_OK ){
raw_printf(STD_ERR, "sql error: %s (%d)\n",
sqlite3_errmsg(db), sqlite3_errcode(db)
);
*pRc = rc;
}
}
}
/*
** Create a prepared statement using printf-style arguments for the SQL.
**
** This routine is could be marked "static". But it is not always used,
** depending on compile-time options. By omitting the "static", we avoid
** nuisance compiler warnings about "defined but not used".
*/
void shellPreparePrintf(
sqlite3 *db,
int *pRc,
sqlite3_stmt **ppStmt,
const char *zFmt,
...
){
*ppStmt = 0;
if( *pRc==SQLITE_OK ){
va_list ap;
char *z;
va_start(ap, zFmt);
z = sqlite3_vmprintf(zFmt, ap);
va_end(ap);
if( z==0 ){
*pRc = SQLITE_NOMEM;
}else{
shellPrepare(db, pRc, z, ppStmt);
sqlite3_free(z);
}
}
}
/* Finalize the prepared statement created using shellPreparePrintf().
**
** This routine is could be marked "static". But it is not always used,
** depending on compile-time options. By omitting the "static", we avoid
** nuisance compiler warnings about "defined but not used".
*/
void shellFinalize(
int *pRc,
sqlite3_stmt *pStmt
){
if( pStmt ){
sqlite3 *db = sqlite3_db_handle(pStmt);
int rc = sqlite3_finalize(pStmt);
if( *pRc==SQLITE_OK ){
if( rc!=SQLITE_OK ){
raw_printf(STD_ERR, "SQL error: %s\n", sqlite3_errmsg(db));
}
*pRc = rc;
}
}
}
/* Reset the prepared statement created using shellPreparePrintf().
**
** This routine is could be marked "static". But it is not always used,
** depending on compile-time options. By omitting the "static", we avoid
** nuisance compiler warnings about "defined but not used".
*/
void shellReset(
int *pRc,
sqlite3_stmt *pStmt
){
int rc = sqlite3_reset(pStmt);
if( *pRc==SQLITE_OK ){
if( rc!=SQLITE_OK ){
sqlite3 *db = sqlite3_db_handle(pStmt);
raw_printf(STD_ERR, "SQL error: %s\n", sqlite3_errmsg(db));
}
*pRc = rc;
}
}
#endif /* !defined SQLITE_OMIT_VIRTUALTABLE */
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
# define ARCHIVE_ENABLE 1
#else
# define ARCHIVE_ENABLE 0
#endif
#if ARCHIVE_ENABLE && !defined(SQLITE_SHELL_FIDDLE)
/******************************************************************************
** The ".archive" or ".ar" command.
*/
/*
** Structure representing a single ".ar" command.
*/
typedef struct ArCommand ArCommand;
struct ArCommand {
u8 eCmd; /* An AR_CMD_* value */
u8 bVerbose; /* True if --verbose */
u8 bZip; /* True if the archive is a ZIP */
u8 bDryRun; /* True if --dry-run */
u8 bAppend; /* True if --append */
u8 bGlob; /* True if --glob */
u8 fromCmdLine; /* Run from -A instead of .archive */
int nArg; /* Number of command arguments */
char *zSrcTable; /* "sqlar", "zipfile($file)" or "zip" */
const char *zFile; /* --file argument, or NULL */
const char *zDir; /* --directory argument, or NULL */
char **azArg; /* Array of command arguments */
ShellExState *p; /* Shell state */
FILE *out; /* Where to put normal messages or info */
sqlite3 *db; /* Database containing the archive */
};
/*
** Print a usage message for the .ar command to stderr and return SQLITE_ERROR.
*/
static int arUsage(FILE *f, ArCommand *pAr){
showHelp(f,"archive", pAr->p);
return SQLITE_ERROR;
}
/*
** Print an error message for the .ar command to stderr and return
** SQLITE_ERROR.
*/
static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){
va_list ap;
char *z;
va_start(ap, zFmt);
z = sqlite3_vmprintf(zFmt, ap);
va_end(ap);
utf8_printf(STD_ERR, "Error: %s\n", z);
if( pAr->fromCmdLine ){
utf8_printf(STD_ERR, "Use \"-A\" for more help\n");
}else{
utf8_printf(STD_ERR, "Use \".archive --help\" for more help\n");
}
sqlite3_free(z);
return SQLITE_ERROR;
}
/*
** Values for ArCommand.eCmd.
*/
#define AR_CMD_CREATE 1
#define AR_CMD_UPDATE 2
#define AR_CMD_INSERT 3
#define AR_CMD_EXTRACT 4
#define AR_CMD_LIST 5
#define AR_CMD_HELP 6
#define AR_CMD_REMOVE 7
/*
** Other (non-command) switches.
*/
#define AR_SWITCH_VERBOSE 8
#define AR_SWITCH_FILE 9
#define AR_SWITCH_DIRECTORY 10
#define AR_SWITCH_APPEND 11
#define AR_SWITCH_DRYRUN 12
#define AR_SWITCH_GLOB 13
static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){
switch( eSwitch ){
case AR_CMD_CREATE:
case AR_CMD_EXTRACT:
case AR_CMD_LIST:
case AR_CMD_REMOVE:
case AR_CMD_UPDATE:
case AR_CMD_INSERT:
case AR_CMD_HELP:
if( pAr->eCmd ){
return arErrorMsg(pAr, "multiple command options");
}
pAr->eCmd = eSwitch;
break;
case AR_SWITCH_DRYRUN:
pAr->bDryRun = 1;
break;
case AR_SWITCH_GLOB:
pAr->bGlob = 1;
break;
case AR_SWITCH_VERBOSE:
pAr->bVerbose = 1;
break;
case AR_SWITCH_APPEND:
pAr->bAppend = 1;
deliberate_fall_through;
case AR_SWITCH_FILE:
pAr->zFile = zArg;
break;
case AR_SWITCH_DIRECTORY:
pAr->zDir = zArg;
break;
}
return SQLITE_OK;
}
/*
** Parse the command line for an ".ar" command. The results are written into
** structure (*pAr). DCR_Ok is returned if the command line is parsed
** successfully, otherwise an error message is written to stderr and an
** appropriate DCR_* argument error is returned.
*/
static DotCmdRC arParseCommand(
char **azArg, /* Array of arguments passed to dot command */
int nArg, /* Number of entries in azArg[] */
ArCommand *pAr /* Populate this object */
){
struct ArSwitch {
const char *zLong;
char cShort;
u8 eSwitch;
u8 bArg;
} aSwitch[] = {
{ "create", 'c', AR_CMD_CREATE, 0 },
{ "extract", 'x', AR_CMD_EXTRACT, 0 },
{ "insert", 'i', AR_CMD_INSERT, 0 },
{ "list", 't', AR_CMD_LIST, 0 },
{ "remove", 'r', AR_CMD_REMOVE, 0 },
{ "update", 'u', AR_CMD_UPDATE, 0 },
{ "help", 'h', AR_CMD_HELP, 0 },
{ "verbose", 'v', AR_SWITCH_VERBOSE, 0 },
{ "file", 'f', AR_SWITCH_FILE, 1 },
{ "append", 'a', AR_SWITCH_APPEND, 1 },
{ "directory", 'C', AR_SWITCH_DIRECTORY, 1 },
{ "dryrun", 'n', AR_SWITCH_DRYRUN, 0 },
{ "glob", 'g', AR_SWITCH_GLOB, 0 },
};
int nSwitch = sizeof(aSwitch) / sizeof(struct ArSwitch);
struct ArSwitch *pEnd = &aSwitch[nSwitch];
DotCmdRC rv = DCR_Ok;
if( nArg<=1 ){
utf8_printf(STD_ERR, "Error: Wrong number of arguments to \"%s\".\n",
azArg[0]);
if( stdin_is_interactive ){
utf8_printf(STD_ERR, "Usage:\n");
arUsage(STD_ERR, pAr);
return DCR_CmdErred;
}
}else{
char *z = azArg[1];
if( z[0]!='-' ){
/* Traditional style [tar] invocation */
int i;
int iArg = 2;
for(i=0; z[i]; i++){
const char *zArg = 0;
struct ArSwitch *pOpt;
for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
if( z[i]==pOpt->cShort ) break;
}
if( pOpt==pEnd ){
arErrorMsg(pAr, "unrecognized option: %c", z[i]);
return DCR_Unknown|i;
}
if( pOpt->bArg ){
if( iArg>=nArg ){
arErrorMsg(pAr, "option requires an argument: %c",z[i]);
return DCR_Unpaired|i;
}
zArg = azArg[iArg++];
}
rv = arProcessSwitch(pAr, pOpt->eSwitch, zArg);
if( rv!=DCR_Ok ) return rv;
}
pAr->nArg = nArg-iArg;
if( pAr->nArg>0 ){
pAr->azArg = &azArg[iArg];
}
}else{
/* Non-traditional invocation */
int iArg;
for(iArg=1; iArg<nArg; iArg++){
int n;
z = azArg[iArg];
if( z[0]!='-' ){
/* All remaining command line words are command arguments. */
pAr->azArg = &azArg[iArg];
pAr->nArg = nArg-iArg;
break;
}
n = strlen30(z);
if( z[1]!='-' ){
int i;
/* One or more short options */
for(i=1; i<n; i++){
const char *zArg = 0;
struct ArSwitch *pOpt;
for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
if( z[i]==pOpt->cShort ) break;
}
if( pOpt==pEnd ){
arErrorMsg(pAr, "unrecognized option: %c", z[i]);
return DCR_Unknown|iArg;
}
if( pOpt->bArg ){
if( i<(n-1) ){
zArg = &z[i+1];
i = n;
}else{
if( iArg>=(nArg-1) ){
arErrorMsg(pAr, "option requires an argument: %c", z[i]);
return DCR_Unpaired|iArg;
}
zArg = azArg[++iArg];
}
}
rv = arProcessSwitch(pAr, pOpt->eSwitch, zArg);
if( rv!=DCR_Ok ) return rv;
}
}else if( z[2]=='\0' ){
/* A -- option, indicating that all remaining command line words
** are command arguments. */
pAr->azArg = &azArg[iArg+1];
pAr->nArg = nArg-iArg-1;
break;
}else{
/* A long option */
const char *zArg = 0; /* Argument for option, if any */
struct ArSwitch *pMatch = 0; /* Matching option */
struct ArSwitch *pOpt; /* Iterator */
for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
const char *zLong = pOpt->zLong;
if( (n-2)<=strlen30(zLong) && 0==memcmp(&z[2], zLong, n-2) ){
if( pMatch ){
arErrorMsg(pAr, "ambiguous option: %s",z);
return DCR_Ambiguous|iArg;
}else{
pMatch = pOpt;
}
}
}
if( pMatch==0 ){
arErrorMsg(pAr, "unrecognized option: %s", z);
return DCR_Unknown|iArg;
}
if( pMatch->bArg ){
if( iArg>=(nArg-1) ){
arErrorMsg(pAr, "option requires an argument: %s", z);
return DCR_Unpaired|iArg;
}
zArg = azArg[++iArg];
}
if( arProcessSwitch(pAr, pMatch->eSwitch, zArg) ) return SQLITE_ERROR;
}
}
}
}
if( pAr->eCmd==0 ){
utf8_printf(stderr, "Required argument missing. Usage:\n");
return arUsage(stderr,pAr);
}
return SQLITE_OK;
}
/*
** This function assumes that all arguments within the ArCommand.azArg[]
** array refer to archive members, as for the --extract, --list or --remove
** commands. It checks that each of them are "present". If any specified
** file is not present in the archive, an error is printed to stderr and an
** error code returned. Otherwise, if all specified arguments are present
** in the archive, SQLITE_OK is returned. Here, "present" means either an
** exact equality when pAr->bGlob is false or a "name GLOB pattern" match
** when pAr->bGlob is true.
**
** This function strips any trailing '/' characters from each argument.
** This is consistent with the way the [tar] command seems to work on
** Linux.
*/
static int arCheckEntries(ArCommand *pAr){
int rc = SQLITE_OK;
if( pAr->nArg ){
int i, j;
sqlite3_stmt *pTest = 0;
const char *zSel = (pAr->bGlob)
? "SELECT name FROM %s WHERE glob($name,name)"
: "SELECT name FROM %s WHERE name=$name";
shellPreparePrintf(pAr->db, &rc, &pTest, zSel, pAr->zSrcTable);
j = sqlite3_bind_parameter_index(pTest, "$name");
for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){
char *z = pAr->azArg[i];
int n = strlen30(z);
int bOk = 0;
while( n>0 && z[n-1]=='/' ) n--;
z[n] = '\0';
sqlite3_bind_text(pTest, j, z, -1, SQLITE_STATIC);
if( SQLITE_ROW==sqlite3_step(pTest) ){
bOk = 1;
}
shellReset(&rc, pTest);
if( rc==SQLITE_OK && bOk==0 ){
utf8_printf(STD_ERR, "not found in archive: %s\n", z);
rc = SQLITE_ERROR;
}
}
shellFinalize(&rc, pTest);
}
return rc;
}
/*
** Format a WHERE clause that can be used against the "sqlar" table to
** identify all archive members that match the command arguments held
** in (*pAr). Leave this WHERE clause in (*pzWhere) before returning.
** The caller is responsible for eventually calling sqlite3_free() on
** any non-NULL (*pzWhere) value. Here, "match" means strict equality
** when pAr->bGlob is false and GLOB match when pAr->bGlob is true.
*/
static void arWhereClause(
int *pRc,
ArCommand *pAr,
char **pzWhere /* OUT: New WHERE clause */
){
char *zWhere = 0;
const char *zSameOp = (pAr->bGlob)? "GLOB" : "=";
if( *pRc==SQLITE_OK ){
if( pAr->nArg==0 ){
zWhere = smprintf("1");
}else{
int i;
const char *zSep = "";
for(i=0; i<pAr->nArg; i++){
const char *z = pAr->azArg[i];
zWhere = smprintf("%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'",
zWhere, zSep, zSameOp, z, strlen30(z)+1, zSameOp, z);
if( zWhere==0 ){
*pRc = SQLITE_NOMEM;
break;
}
zSep = " OR ";
}
}
}
*pzWhere = zWhere;
}
/*
** Implementation of .ar "lisT" command.
*/
static int arListCommand(ArCommand *pAr){
const char *zSql = "SELECT %s FROM %s WHERE %s";
const char *azCols[] = {
"name",
"lsmode(mode), sz, datetime(mtime, 'unixepoch'), name"
};
char *zWhere = 0;
sqlite3_stmt *pSql = 0;
int rc;
rc = arCheckEntries(pAr);
arWhereClause(&rc, pAr, &zWhere);
shellPreparePrintf(pAr->db, &rc, &pSql, zSql, azCols[pAr->bVerbose],
pAr->zSrcTable, zWhere);
if( pAr->bDryRun ){
utf8_printf(pAr->out, "%s\n", sqlite3_sql(pSql));
}else{
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
if( pAr->bVerbose ){
utf8_printf(pAr->out, "%s % 10d %s %s\n",
sqlite3_column_text(pSql, 0),
sqlite3_column_int(pSql, 1),
sqlite3_column_text(pSql, 2),
sqlite3_column_text(pSql, 3)
);
}else{
utf8_printf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0));
}
}
}
shellFinalize(&rc, pSql);
sqlite3_free(zWhere);
return rc;
}
/*
** Implementation of .ar "Remove" command.
*/
static int arRemoveCommand(ArCommand *pAr){
int rc = 0;
char *zSql = 0;
char *zWhere = 0;
if( pAr->nArg ){
/* Verify that args actually exist within the archive before proceeding.
** And formulate a WHERE clause to match them. */
rc = arCheckEntries(pAr);
arWhereClause(&rc, pAr, &zWhere);
}
if( rc==SQLITE_OK ){
zSql = smprintf("DELETE FROM %s WHERE %s;", pAr->zSrcTable, zWhere);
if( pAr->bDryRun ){
utf8_printf(pAr->out, "%s\n", zSql);
}else{
char *zErr = 0;
rc = sqlite3_exec(pAr->db, "SAVEPOINT ar;", 0, 0, 0);
if( rc==SQLITE_OK ){
rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr);
if( rc!=SQLITE_OK ){
sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0);
}else{
rc = sqlite3_exec(pAr->db, "RELEASE ar;", 0, 0, 0);
}
}
if( zErr ){
utf8_printf(STD_OUT, "ERROR: %s\n", zErr);
sqlite3_free(zErr);
}
}
}
sqlite3_free(zWhere);
sqlite3_free(zSql);
return rc;
}
/*
** Implementation of .ar "eXtract" command.
*/
static int arExtractCommand(ArCommand *pAr){
const char *zSql1 =
"SELECT "
" ($dir || name),"
" writefile(($dir || name), %s, mode, mtime) "
"FROM %s WHERE (%s) AND (data IS NULL OR $dirOnly = 0)"
" AND name NOT GLOB '*..[/\\]*'";
const char *azExtraArg[] = {
"sqlar_uncompress(data, sz)",
"data"
};
sqlite3_stmt *pSql = 0;
int rc = SQLITE_OK;
char *zDir = 0;
char *zWhere = 0;
int i, j;
/* If arguments are specified, check that they actually exist within
** the archive before proceeding. And formulate a WHERE clause to
** match them. */
rc = arCheckEntries(pAr);
arWhereClause(&rc, pAr, &zWhere);
if( rc==SQLITE_OK ){
if( pAr->zDir ){
zDir = smprintf("%s/", pAr->zDir);
}else{
zDir = smprintf("");
}
if( zDir==0 ) rc = SQLITE_NOMEM;
}
shellPreparePrintf(pAr->db, &rc, &pSql, zSql1,
azExtraArg[pAr->bZip], pAr->zSrcTable, zWhere
);
if( rc==SQLITE_OK ){
j = sqlite3_bind_parameter_index(pSql, "$dir");
sqlite3_bind_text(pSql, j, zDir, -1, SQLITE_STATIC);
/* Run the SELECT statement twice. The first time, writefile() is called
** for all archive members that should be extracted. The second time,
** only for the directories. This is because the timestamps for
** extracted directories must be reset after they are populated (as
** populating them changes the timestamp). */
for(i=0; i<2; i++){
j = sqlite3_bind_parameter_index(pSql, "$dirOnly");
sqlite3_bind_int(pSql, j, i);
if( pAr->bDryRun ){
utf8_printf(pAr->out, "%s\n", sqlite3_sql(pSql));
}else{
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
if( i==0 && pAr->bVerbose ){
utf8_printf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0));
}
}
}
shellReset(&rc, pSql);
}
shellFinalize(&rc, pSql);
}
sqlite3_free(zDir);
sqlite3_free(zWhere);
return rc;
}
/*
** Run the SQL statement in zSql. Or if doing a --dryrun, merely print it out.
*/
static int arExecSql(ArCommand *pAr, const char *zSql){
int rc;
if( pAr->bDryRun ){
utf8_printf(pAr->out, "%s\n", zSql);
rc = SQLITE_OK;
}else{
char *zErr = 0;
rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr);
if( zErr ){
utf8_printf(STD_OUT, "ERROR: %s\n", zErr);
sqlite3_free(zErr);
}
}
return rc;
}
/*
** Implementation of .ar "create", "insert", and "update" commands.
**
** create -> Create a new SQL archive
** insert -> Insert or reinsert all files listed
** update -> Insert files that have changed or that were not
** previously in the archive
**
** Create the "sqlar" table in the database if it does not already exist.
** Then add each file in the azFile[] array to the archive. Directories
** are added recursively. If argument bVerbose is non-zero, a message is
** printed on stdout for each file archived.
**
** The create command is the same as update, except that it drops
** any existing "sqlar" table before beginning. The "insert" command
** always overwrites every file named on the command-line, where as
** "update" only overwrites if the size or mtime or mode has changed.
*/
static int arCreateOrUpdateCommand(
ArCommand *pAr, /* Command arguments and options */
int bUpdate, /* true for a --create. */
int bOnlyIfChanged /* Only update if file has changed */
){
const char *zCreate =
"CREATE TABLE IF NOT EXISTS sqlar(\n"
" name TEXT PRIMARY KEY, -- name of the file\n"
" mode INT, -- access permissions\n"
" mtime INT, -- last modification time\n"
" sz INT, -- original file size\n"
" data BLOB -- compressed content\n"
")";
const char *zDrop = "DROP TABLE IF EXISTS sqlar";
const char *zInsertFmt[2] = {
"REPLACE INTO %s(name,mode,mtime,sz,data)\n"
" SELECT\n"
" %s,\n"
" mode,\n"
" mtime,\n"
" CASE substr(lsmode(mode),1,1)\n"
" WHEN '-' THEN length(data)\n"
" WHEN 'd' THEN 0\n"
" ELSE -1 END,\n"
" sqlar_compress(data)\n"
" FROM fsdir(%Q,%Q) AS disk\n"
" WHERE lsmode(mode) NOT LIKE '?%%'%s;"
,
"REPLACE INTO %s(name,mode,mtime,data)\n"
" SELECT\n"
" %s,\n"
" mode,\n"
" mtime,\n"
" data\n"
" FROM fsdir(%Q,%Q) AS disk\n"
" WHERE lsmode(mode) NOT LIKE '?%%'%s;"
};
int i; /* For iterating through azFile[] */
int rc; /* Return code */
const char *zTab = 0; /* SQL table into which to insert */
char *zSql;
char zTemp[50];
char *zExists = 0;
arExecSql(pAr, "PRAGMA page_size=512");
rc = arExecSql(pAr, "SAVEPOINT ar;");
if( rc!=SQLITE_OK ) return rc;
zTemp[0] = 0;
if( pAr->bZip ){
/* Initialize the zipfile virtual table, if necessary */
if( pAr->zFile ){
sqlite3_uint64 r;
sqlite3_randomness(sizeof(r),&r);
sqlite3_snprintf(sizeof(zTemp),zTemp,"zip%016llx",r);
zTab = zTemp;
zSql = smprintf("CREATE VIRTUAL TABLE temp.%s USING zipfile(%Q)",
zTab, pAr->zFile
);
rc = arExecSql(pAr, zSql);
sqlite3_free(zSql);
}else{
zTab = "zip";
}
}else{
/* Initialize the table for an SQLAR */
zTab = "sqlar";
if( bUpdate==0 ){
rc = arExecSql(pAr, zDrop);
if( rc!=SQLITE_OK ) goto end_ar_transaction;
}
rc = arExecSql(pAr, zCreate);
}
if( bOnlyIfChanged ){
zExists = smprintf(" AND NOT EXISTS(SELECT 1 FROM %s AS mem"
" WHERE mem.name=disk.name AND mem.mtime=disk.mtime"
" AND mem.mode=disk.mode)", zTab);
}else{
zExists = smprintf("");
}
if( zExists==0 ) rc = SQLITE_NOMEM;
for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){
char *zSql2 = smprintf(zInsertFmt[pAr->bZip], zTab,
pAr->bVerbose ? "shell_putsnl(name)" : "name",
pAr->azArg[i], pAr->zDir, zExists);
rc = arExecSql(pAr, zSql2);
sqlite3_free(zSql2);
}
end_ar_transaction:
if( rc!=SQLITE_OK ){
sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0);
}else{
rc = arExecSql(pAr, "RELEASE ar;");
if( pAr->bZip && pAr->zFile ){
zSql = smprintf("DROP TABLE %s", zTemp);
arExecSql(pAr, zSql);
sqlite3_free(zSql);
}
}
sqlite3_free(zExists);
return rc;
}
/*
** Implementation of ".ar" dot command.
*/
static DotCmdRC arDotCommand(
ShellExState *pState, /* Current shell tool state */
int fromCmdLine, /* True if -A command-line option, not .ar cmd */
char **azArg, /* Array of arguments passed to dot command */
int nArg /* Number of entries in azArg[] */
){
ArCommand cmd;
DotCmdRC rv;
int rc;
memset(&cmd, 0, sizeof(cmd));
cmd.fromCmdLine = fromCmdLine;
rv = arParseCommand(azArg, nArg, &cmd);
cmd.out = ISS(pState)->out;
if( rv==DCR_Ok ){
int eDbType = SHELL_OPEN_UNSPEC;
cmd.p = pState;
cmd.db = DBX(pState);
if( cmd.zFile ){
eDbType = deduceDatabaseType(cmd.zFile, 1);
}else{
eDbType = ISS(pState)->openMode;
}
if( eDbType==SHELL_OPEN_ZIPFILE ){
if( cmd.eCmd==AR_CMD_EXTRACT || cmd.eCmd==AR_CMD_LIST ){
if( cmd.zFile==0 ){
cmd.zSrcTable = smprintf("zip");
}else{
cmd.zSrcTable = smprintf("zipfile(%Q)", cmd.zFile);
}
}
cmd.bZip = 1;
}else if( cmd.zFile ){
int flags;
if( cmd.bAppend ) eDbType = SHELL_OPEN_APPENDVFS;
if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_INSERT
|| cmd.eCmd==AR_CMD_REMOVE || cmd.eCmd==AR_CMD_UPDATE ){
flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE;
}else{
flags = SQLITE_OPEN_READONLY;
}
cmd.db = 0;
if( cmd.bDryRun ){
utf8_printf(cmd.out, "-- open database '%s'%s\n", cmd.zFile,
eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : "");
}
rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags,
eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0);
if( rc!=SQLITE_OK ){
utf8_printf(STD_ERR, "cannot open file: %s (%s)\n",
cmd.zFile, sqlite3_errmsg(cmd.db)
);
goto end_ar_command;
}
sqlite3_fileio_init(cmd.db, 0, 0);
sqlite3_sqlar_init(cmd.db, 0, 0);
sqlite3_create_function(cmd.db, "shell_putsnl", 1, SQLITE_UTF8, cmd.p,
shellPutsFunc, 0, 0);
}
if( cmd.zSrcTable==0 && cmd.bZip==0 && cmd.eCmd!=AR_CMD_HELP ){
if( cmd.eCmd!=AR_CMD_CREATE
&& sqlite3_table_column_metadata(cmd.db,0,"sqlar","name",0,0,0,0,0)
){
utf8_printf(STD_ERR, "database does not contain an 'sqlar' table\n");
rc = SQLITE_ERROR;
goto end_ar_command;
}
cmd.zSrcTable = smprintf("sqlar");
}
switch( cmd.eCmd ){
case AR_CMD_CREATE:
rc = arCreateOrUpdateCommand(&cmd, 0, 0);
break;
case AR_CMD_EXTRACT:
rc = arExtractCommand(&cmd);
break;
case AR_CMD_LIST:
rc = arListCommand(&cmd);
break;
case AR_CMD_HELP:
arUsage(cmd.out, &cmd);
break;
case AR_CMD_INSERT:
rc = arCreateOrUpdateCommand(&cmd, 1, 0);
break;
case AR_CMD_REMOVE:
rc = arRemoveCommand(&cmd);
break;
default:
assert( cmd.eCmd==AR_CMD_UPDATE );
rc = arCreateOrUpdateCommand(&cmd, 1, 1);
break;
}
}
end_ar_command:
if( cmd.db!=DBX(pState) ){
close_db(ISS(pState),cmd.db);
}
sqlite3_free(cmd.zSrcTable);
return (rv!=DCR_Ok)? rv : (DCR_Ok|(rc!=0));
}
/* End of the ".archive" or ".ar" command logic
*******************************************************************************/
#endif /* ARCHIVE_ENABLE && !defined(SQLITE_SHELL_FIDDLE) */
#if SQLITE_SHELL_HAVE_RECOVER
/*
** This function is used as a callback by the recover extension. Simply
** print the supplied SQL statement to stdout.
*/
static int recoverSqlCb(void *pCtx, const char *zSql){
ShellInState *pState = (ShellInState*)pCtx;
utf8_printf(pState->out, "%s;\n", zSql); /* ToDo: get right member here. */
return SQLITE_OK;
}
#endif /* SQLITE_SHELL_HAVE_RECOVER */
#ifndef SQLITE_SHELL_FIDDLE
static DotCmdRC
writeDb( char *azArg[], int nArg, ShellExState *psx, char **pzErr ){
int rc = 0;
const char *zDestFile = 0;
const char *zDb = 0;
sqlite3 *pDest;
sqlite3_backup *pBackup;
int j;
int bAsync = 0;
const char *zVfs = 0;
if( ISS(psx)->bSafeMode ) return DCR_AbortError;
for(j=1; j<nArg; j++){
const char *z = azArg[j];
if( z[0]=='-' ){
if( z[1]=='-' ) z++;
if( cli_strcmp(z, "-append")==0 ){
zVfs = "apndvfs";
}else if( cli_strcmp(z, "-async")==0 ){
bAsync = 1;
}else{
return DCR_Unknown|j;
}
}else if( zDestFile==0 ){
zDestFile = azArg[j];
}else if( zDb==0 ){
zDb = zDestFile;
zDestFile = azArg[j];
}else{
return DCR_TooMany|j;
}
}
if( zDestFile==0 ){
return DCR_Missing;
}
if( zDb==0 ) zDb = "main";
rc = sqlite3_open_v2(zDestFile, &pDest,
SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs);
if( rc!=SQLITE_OK ){
*pzErr = smprintf("cannot open \"%s\"\n", zDestFile);
close_db(ISS(psx),pDest);
return DCR_Error;
}
if( bAsync ){
sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
0, 0, 0);
}
open_db(psx, 0);
pBackup = sqlite3_backup_init(pDest, "main", DBX(psx), zDb);
if( pBackup==0 ){
*pzErr = smprintf("%s failed, %s\n", azArg[0], sqlite3_errmsg(pDest));
close_db(ISS(psx),pDest);
return DCR_Error;
}
while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
sqlite3_backup_finish(pBackup);
if( rc==SQLITE_DONE ){
rc = 0;
}else{
*pzErr = smprintf("%s\n", sqlite3_errmsg(pDest));
rc = 1;
}
close_db(ISS(psx),pDest);
return DCR_Ok|rc;
}
#endif /* !defined(SQLITE_SHELL_FIDDLE) */
/*
* zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it.
* zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE,
* close db and set it to 0, and return the columns spec, to later
* be sqlite3_free()'ed by the caller.
* The return is 0 when either:
* (a) The db was not initialized and zCol==0 (There are no columns.)
* (b) zCol!=0 (Column was added, db initialized as needed.)
* The 3rd argument, pRenamed, references an out parameter. If the
* pointer is non-zero, its referent will be set to a summary of renames
* done if renaming was necessary, or set to 0 if none was done. The out
* string (if any) must be sqlite3_free()'ed by the caller.
*/
#ifdef SHELL_COLFIX_DB /* If this is set, the DB can be in a file. */
static char zCOL_DB[] = SHELL_STRINGIFY(SHELL_COLFIX_DB);
#else /* Otherwise, memory is faster/better for the transient DB. */
static const char *zCOL_DB = ":memory:";
#endif
/* Define character (as C string) to separate generated column ordinal
* from protected part of incoming column names. This defaults to "_"
* so that incoming column identifiers that did not need not be quoted
* remain usable without being quoted. It must be one character.
*/
#ifndef SHELL_AUTOCOLUMN_SEP
# define AUTOCOLUMN_SEP "_"
#else
# define AUTOCOLUMN_SEP SHELL_STRINGIFY(SHELL_AUTOCOLUMN_SEP)
#endif
static char *zAutoColumn(const char *zColNew, sqlite3 **pDb, char **pzRenamed){
/* Queries and D{D,M}L used here */
static const char * const zTabMake = "\
CREATE TABLE ColNames(\
cpos INTEGER PRIMARY KEY,\
name TEXT, nlen INT, chop INT, reps INT, suff TEXT);\
CREATE VIEW RepeatedNames AS \
SELECT DISTINCT t.name FROM ColNames t \
WHERE t.name COLLATE NOCASE IN (\
SELECT o.name FROM ColNames o WHERE o.cpos<>t.cpos\
);\
";
static const char * const zTabFill = "\
INSERT INTO ColNames(name,nlen,chop,reps,suff)\
VALUES(iif(length(?1)>0,?1,'?'),max(length(?1),1),0,0,'')\
";
static const char * const zHasDupes = "\
SELECT count(DISTINCT (substring(name,1,nlen-chop)||suff) COLLATE NOCASE)\
<count(name) FROM ColNames\
";
#ifdef SHELL_COLUMN_RENAME_CLEAN
static const char * const zDedoctor = "\
UPDATE ColNames SET chop=iif(\
(substring(name,nlen,1) BETWEEN '0' AND '9')\
AND (rtrim(name,'0123456790') glob '*"AUTOCOLUMN_SEP"'),\
nlen-length(rtrim(name, '"AUTOCOLUMN_SEP"0123456789')),\
0\
)\
";
#endif
static const char * const zSetReps = "\
UPDATE ColNames AS t SET reps=\
(SELECT count(*) FROM ColNames d \
WHERE substring(t.name,1,t.nlen-t.chop)=substring(d.name,1,d.nlen-d.chop)\
COLLATE NOCASE\
)\
";
#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
static const char * const zColDigits = "\
SELECT CAST(ceil(log(count(*)+0.5)) AS INT) FROM ColNames \
";
#else
/* Counting on SQLITE_MAX_COLUMN < 100,000 here. (32767 is the hard limit.) */
static const char * const zColDigits = "\
SELECT CASE WHEN (nc < 10) THEN 1 WHEN (nc < 100) THEN 2 \
WHEN (nc < 1000) THEN 3 WHEN (nc < 10000) THEN 4 \
ELSE 5 FROM (SELECT count(*) AS nc FROM ColNames) \
";
#endif
static const char * const zRenameRank =
#ifdef SHELL_COLUMN_RENAME_CLEAN
"UPDATE ColNames AS t SET suff="
"iif(reps>1, printf('%c%0*d', '"AUTOCOLUMN_SEP"', $1, cpos), '')"
#else /* ...RENAME_MINIMAL_ONE_PASS */
"WITH Lzn(nlz) AS (" /* Find minimum extraneous leading 0's for uniqueness */
" SELECT 0 AS nlz"
" UNION"
" SELECT nlz+1 AS nlz FROM Lzn"
" WHERE EXISTS("
" SELECT 1"
" FROM ColNames t, ColNames o"
" WHERE"
" iif(t.name IN (SELECT * FROM RepeatedNames),"
" printf('%s"AUTOCOLUMN_SEP"%s',"
" t.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,t.cpos),2)),"
" t.name"
" )"
" ="
" iif(o.name IN (SELECT * FROM RepeatedNames),"
" printf('%s"AUTOCOLUMN_SEP"%s',"
" o.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,o.cpos),2)),"
" o.name"
" )"
" COLLATE NOCASE"
" AND o.cpos<>t.cpos"
" GROUP BY t.cpos"
" )"
") UPDATE Colnames AS t SET"
" chop = 0," /* No chopping, never touch incoming names. */
" suff = iif(name IN (SELECT * FROM RepeatedNames),"
" printf('"AUTOCOLUMN_SEP"%s', substring("
" printf('%.*c%0.*d',(SELECT max(nlz) FROM Lzn)+1,'0',1,t.cpos),2)),"
" ''"
" )"
#endif
;
static const char * const zCollectVar = "\
SELECT\
'('||x'0a'\
|| group_concat(\
cname||' TEXT',\
','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\
||')' AS ColsSpec \
FROM (\
SELECT cpos, printf('\"%w\"',printf('%!.*s%s',nlen-chop,name,suff)) AS cname \
FROM ColNames ORDER BY cpos\
)";
static const char * const zRenamesDone =
"SELECT group_concat("
" printf('\"%w\" to \"%w\"',name,printf('%!.*s%s', nlen-chop, name, suff)),"
" ','||x'0a')"
"FROM ColNames WHERE suff<>'' OR chop!=0"
;
int rc;
sqlite3_stmt *pStmt = 0;
assert(pDb!=0);
if( zColNew ){
/* Add initial or additional column. Init db if necessary. */
if( *pDb==0 ){
if( SQLITE_OK!=sqlite3_open(zCOL_DB, pDb) ) return 0;
#ifdef SHELL_COLFIX_DB
if(*zCOL_DB!=':')
sqlite3_exec(*pDb,"drop table if exists ColNames;"
"drop view if exists RepeatedNames;",0,0,0);
#endif
s3_exec_noom(*pDb, zTabMake, 0, 0, 0);
}
assert(*pDb!=0);
s3_prepare_v2_noom(*pDb, zTabFill, -1, &pStmt, 0);
stmt_holder(pStmt);
shell_check_nomem(sqlite3_bind_text(pStmt, 1, zColNew, -1, 0));
s3_step_noom(pStmt);
release_holder();
return 0;
}else if( *pDb==0 ){
return 0;
}else{
/* Formulate the columns spec, close the DB, zero *pDb. */
char *zColsSpec = 0;
int hasDupes = db_int(*pDb, zHasDupes);
int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0;
if( hasDupes ){
#ifdef SHELL_COLUMN_RENAME_CLEAN
s3_exec_noom(*pDb, zDedoctor, 0, 0, 0);
#endif
s3_exec_noom(*pDb, zSetReps, 0, 0, 0);
s3_prepare_v2_noom(*pDb, zRenameRank, -1, &pStmt, 0);
stmt_holder(pStmt);
sqlite3_bind_int(pStmt, 1, nDigits);
rc = s3_step_noom(pStmt);
release_holder();
if( rc!=SQLITE_DONE ) shell_check_nomem(SQLITE_NOMEM);
}
/* This assert is maybe overly cautious for above de-dup DML, but that can
* be replaced via #define's. So this check is made for debug builds. */
assert(db_int(*pDb, zHasDupes)==0);
rc = s3_prepare_v2_noom(*pDb, zCollectVar, -1, &pStmt, 0);
rc = s3_step_noom(pStmt);
if( rc==SQLITE_ROW ){
zColsSpec = smprintf("%s", sqlite3_column_text(pStmt, 0));
}else{
zColsSpec = 0;
}
if( pzRenamed!=0 ){
if( !hasDupes ) *pzRenamed = 0;
else{
sqlite3_finalize(pStmt);
if( SQLITE_OK==s3_prepare_v2_noom(*pDb, zRenamesDone, -1, &pStmt, 0)
&& SQLITE_ROW==sqlite3_step(pStmt) ){
*pzRenamed = smprintf("%s", sqlite3_column_text(pStmt, 0));
}else
*pzRenamed = 0;
}
}
sqlite3_finalize(pStmt);
sqlite3_close(*pDb);
*pDb = 0;
return zColsSpec;
}
}
#if SHELL_DATAIO_EXT
/*
** Standard ExportHandlers
** These implement the built-in renderers of query results.
** Two are provided, one for free-form results, the other for columnar results.
*/
static void EH_FF_destruct(ExportHandler *pMe);
static void EH_CM_destruct(ExportHandler *pMe);
static const char *EH_FF_name(ExportHandler *pMe);
static const char *EH_CM_name(ExportHandler *pMe);
static const char *EH_help(ExportHandler *pMe, const char *zWhat);
static int EH_FF_config(ExportHandler *pMe, int io, char **pzTell);
static int EH_CM_config(ExportHandler *pMe, int io, char **pzTell);
static int EH_openResultsOutStream(ExportHandler *pMe,
ShellExState *pSES, char **pzErr,
int numArgs, char *azArgs[],
const char * zName);
static int EH_FF_prependResultsOut(ExportHandler *pMe,
ShellExState *pSES, char **pzErr,
sqlite3_stmt *pStmt);
static int EH_CM_prependResultsOut(ExportHandler *pMe,
ShellExState *pSES, char **pzErr,
sqlite3_stmt *pStmt);
static int EH_FF_rowResultsOut(ExportHandler *pMe,
ShellExState *pSES, char **pzErr,
sqlite3_stmt *pStmt);
static int EH_CM_rowResultsOut(ExportHandler *pMe,
ShellExState *pSES, char **pzErr,
sqlite3_stmt *pStmt);
static int EH_FF_appendResultsOut(ExportHandler *pMe,
ShellExState *pSES, char **pzErr,
sqlite3_stmt *pStmt);
static int EH_CM_appendResultsOut(ExportHandler *pMe,
ShellExState *pSES, char **pzErr,
sqlite3_stmt *pStmt);
static void EH_closeResultsOutStream(ExportHandler *pMe,
ShellExState *pSES,
char **pzErr);
static VTABLE_NAME(ExportHandler) exporter_Vtab_FF = {
EH_FF_destruct,
EH_FF_name,
EH_help,
EH_FF_config,
EH_openResultsOutStream,
EH_FF_prependResultsOut,
EH_FF_rowResultsOut,
EH_FF_appendResultsOut,
EH_closeResultsOutStream
};
static VTABLE_NAME(ExportHandler) exporter_Vtab_CM = {
EH_CM_destruct,
EH_CM_name,
EH_help,
EH_CM_config,
EH_openResultsOutStream,
EH_CM_prependResultsOut,
EH_CM_rowResultsOut,
EH_CM_appendResultsOut,
EH_closeResultsOutStream
};
typedef struct {
char **azCols; /* Names of result columns */
char **azVals; /* Results */
int *aiTypes; /* Result types */
} ColumnsInfo;
typedef struct {
VTABLE_NAME(ExportHandler) *pMethods;
ShellInState *psi;
int nCol;
sqlite3_uint64 nRow;
void *pData;
void *pRowInfo;
ColumnsInfo colInfo;
} BuiltInFFExporter;
#define BI_FF_EXPORTER_INIT(psi) { & exporter_Vtab_FF, psi, 0, 0, 0, 0 }
typedef struct {
VTABLE_NAME(ExportHandler) *pMethods;
ShellInState *psi;
int nCol;
sqlite3_uint64 nRow;
void *pData;
void *pRowInfo;
const char *colSep;
const char *rowSep;
} BuiltInCMExporter;
#define BI_CM_EXPORTER_INIT(psi) { & exporter_Vtab_CM, psi, 0, 0, 0, 0 }
static void EH_FF_destruct(ExportHandler *pMe){
/* This serves two purposes: idempotent reinitialize, and final takedown */
BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
if( pbie->nCol!=0 ){
sqlite3_free(pbie->pData);
pbie->pData = 0;
}
pbie->nRow = 0;
pbie->nCol = 0;
}
static const char *zEmpty = "";
static void EH_CM_destruct(ExportHandler *pMe){
/* This serves two purposes: idempotent reinitialize, and final takedown */
BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
if( pbie->nCol!=0 ){
sqlite3_uint64 nData = pbie->nCol * (pbie->nRow + 1), i;
const char *zNull = pbie->psi->nullValue;
char **azData = (char**)pbie->pData;
for(i=0; i<nData; i++){
char *z = azData[i];
if( z!=zEmpty && z!=zNull ) sqlite3_free(z);
}
sqlite3_free(pbie->pData);
sqlite3_free(pbie->pRowInfo);
pbie->pData = 0;
pbie->pRowInfo = 0;
}
pbie->nCol = 0;
pbie->nRow = 0;
pbie->colSep = 0;
pbie->rowSep = 0;
}
static int EH_FF_config(ExportHandler *pMe, int io, char **pzTell){
BuiltInCMExporter *pThis = (BuiltInCMExporter*)pMe;
UNUSED_PARAMETER(io);
UNUSED_PARAMETER(pzTell);
return SQLITE_OK;
}
static int EH_CM_config(ExportHandler *pMe, int io, char **pzTell){
BuiltInFFExporter *pThis = (BuiltInFFExporter*)pMe;
ShellInState *psi = pThis->psi;
if( io==0 && pzTell!=0 ){
*pzTell = smprintf("--wrap %d --wordwrap %s --%squote", psi->cmOpts.iWrap,
psi->cmOpts.bWordWrap ? "on" : "off",
psi->cmOpts.bQuote ? "" : "no");
}
return SQLITE_OK;
}
static const char *zModeName(ShellInState *psi){
int mi = psi->mode;
return (mi>=0 && mi<MODE_COUNT_OF)? modeDescr[mi].zModeName : 0;
}
static const char *EH_FF_name(ExportHandler *pMe){
return zModeName(((BuiltInFFExporter*)pMe)->psi);
}
static const char *EH_CM_name(ExportHandler *pMe){
return zModeName(((BuiltInCMExporter*)pMe)->psi);
}
static const char *EH_help(ExportHandler *pMe, const char *zWhat){
(void)(pMe);
(void)(zWhat);
return 0;
}
static int EH_openResultsOutStream(ExportHandler *pMe,
ShellExState *pSES, char **pzErr,
int numArgs, char *azArgs[],
const char * zName){
/* The built-in exporters have a predetermined destination, and their
* action is set by the shell state .mode member, so this method has
* nothing to do. For similar reasons, the shell never calls it. That
* could change if .mode command functionality is moved to here.
*/
(void)(pMe);
(void)(pSES);
(void)(pzErr);
(void)(numArgs);
(void)(azArgs);
(void)(zName);
return 0;
}
static int EH_CM_prependResultsOut(ExportHandler *pMe,
ShellExState *psx, char **pzErr,
sqlite3_stmt *pStmt){
BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
ShellInState *psi = ISS(psx);
sqlite3_int64 nRow = 0;
char **azData = 0;
sqlite3_int64 nAlloc = 0;
char *abRowDiv = 0;
const unsigned char *uz;
const char *z;
char **azQuoted = 0;
int rc;
sqlite3_int64 i, nData;
int j, w, n;
const unsigned char **azNextLine = 0;
int bNextLine = 0;
int bMultiLineRowExists = 0;
int bw = psi->cmOpts.bWordWrap;
int nColumn = sqlite3_column_count(pStmt);
if( nColumn==0 ){
rc = sqlite3_step(pStmt);
assert(rc!=SQLITE_ROW);
return rc;
}
EH_CM_destruct(pMe);
nAlloc = nColumn*4;
if( nAlloc<=0 ) nAlloc = 1;
azData = sqlite3_malloc64( nAlloc*sizeof(char*) );
shell_check_ooms(azData);
azNextLine = sqlite3_malloc64( nColumn*sizeof(char*) );
shell_check_ooms((void*)azNextLine);
memset((void*)azNextLine, 0, nColumn*sizeof(char*) );
if( psi->cmOpts.bQuote ){
azQuoted = sqlite3_malloc64( nColumn*sizeof(char*) );
shell_check_ooms(azQuoted);
memset(azQuoted, 0, nColumn*sizeof(char*) );
}
abRowDiv = sqlite3_malloc64( nAlloc/nColumn );
shell_check_ooms(abRowDiv);
if( nColumn>psx->numWidths ){
psx->pSpecWidths = realloc(psx->pSpecWidths, (nColumn+1)*2*sizeof(int));
shell_check_oomm(psx->pSpecWidths);
for(i=psx->numWidths; i<nColumn; i++) psx->pSpecWidths[i] = 0;
psx->numWidths = nColumn;
psx->pHaveWidths = &psx->pSpecWidths[nColumn];
}
memset(psx->pHaveWidths, 0, nColumn*sizeof(int));
for(i=0; i<nColumn; i++){
w = psx->pSpecWidths[i];
if( w<0 ) w = -w;
psx->pHaveWidths[i] = w;
}
for(i=0; i<nColumn; i++){
const unsigned char *zNotUsed;
int wx = psx->pSpecWidths[i];
if( wx==0 ){
wx = psi->cmOpts.iWrap;
}
if( wx<0 ) wx = -wx;
uz = (const unsigned char*)sqlite3_column_name(pStmt,i);
if( uz==0 ) uz = (u8*)"";
azData[i] = translateForDisplayAndDup(uz, &zNotUsed, wx, bw);
}
while( bNextLine || SQLITE_ROW==sqlite3_step(pStmt) ){
int useNextLine = bNextLine;
bNextLine = 0;
if( (nRow+2)*nColumn >= nAlloc ){
nAlloc *= 2;
azData = sqlite3_realloc64(azData, nAlloc*sizeof(char*));
shell_check_ooms(azData);
abRowDiv = sqlite3_realloc64(abRowDiv, nAlloc/nColumn);
shell_check_ooms(abRowDiv);
}
abRowDiv[nRow] = 1;
nRow++;
for(i=0; i<nColumn; i++){
int wx = psx->pSpecWidths[i];
if( wx==0 ){
wx = psi->cmOpts.iWrap;
}
if( wx<0 ) wx = -wx;
if( useNextLine ){
uz = azNextLine[i];
if( uz==0 ) uz = (u8*)zEmpty;
}else if( psi->cmOpts.bQuote ){
sqlite3_free(azQuoted[i]);
azQuoted[i] = quoted_column(pStmt,i);
uz = (const unsigned char*)azQuoted[i];
}else{
uz = (const unsigned char*)sqlite3_column_text(pStmt,i);
if( uz==0 ) uz = (u8*)psi->nullValue;
}
azData[nRow*nColumn + i]
= translateForDisplayAndDup(uz, &azNextLine[i], wx, bw);
if( azNextLine[i] ){
bNextLine = 1;
abRowDiv[nRow-1] = 0;
bMultiLineRowExists = 1;
}
}
}
sqlite3_free((void*)azNextLine);
if( azQuoted ){
for(i=0; i<nColumn; i++) sqlite3_free(azQuoted[i]);
sqlite3_free(azQuoted);
}
if( nRow==0 ){
EH_CM_destruct(pMe);
return SQLITE_DONE;
}
nData = nColumn*(nRow+1);
for(i=0; i<nData; i++){
z = azData[i];
if( z==0 ) z = (char*)zEmpty;
n = strlenChar(z);
j = i%nColumn;
if( n>psx->pHaveWidths[j] ) psx->pHaveWidths[j] = n;
}
if( seenInterrupt ) goto done;
switch( psi->cMode ){
case MODE_Column: {
pbie->colSep = " ";
pbie->rowSep = "\n";
if( psi->showHeader ){
for(i=0; i<nColumn; i++){
w = psx->pHaveWidths[i];
if( psx->pSpecWidths[i]<0 ) w = -w;
utf8_width_print(psi->out, w, azData[i]);
fputs(i==nColumn-1?"\n":" ", psi->out);
}
for(i=0; i<nColumn; i++){
print_dashes(psi->out, psx->pHaveWidths[i]);
fputs(i==nColumn-1?"\n":" ", psi->out);
}
}
break;
}
case MODE_Table: {
pbie->colSep = " | ";
pbie->rowSep = " |\n";
print_row_separator(psx, nColumn, "+");
fputs("| ", psi->out);
for(i=0; i<nColumn; i++){
w = psx->pHaveWidths[i];
n = strlenChar(azData[i]);
utf8_printf(psi->out, "%*s%s%*s",
(w-n)/2, "", azData[i], (w-n+1)/2, "");
fputs(i==nColumn-1?" |\n":" | ", psi->out);
}
print_row_separator(psx, nColumn, "+");
break;
}
case MODE_Markdown: {
pbie->colSep = " | ";
pbie->rowSep = " |\n";
fputs("| ", psi->out);
for(i=0; i<nColumn; i++){
w = psx->pHaveWidths[i];
n = strlenChar(azData[i]);
utf8_printf(psi->out, "%*s%s%*s",
(w-n)/2, "", azData[i], (w-n+1)/2, "");
fputs(i==nColumn-1?" |\n":" | ", psi->out);
}
print_row_separator(psx, nColumn, "|");
break;
}
case MODE_Box: {
pbie->colSep = " " BOX_13 " ";
pbie->rowSep = " " BOX_13 "\n";
print_box_row_separator(psx, nColumn, BOX_23, BOX_234, BOX_34);
utf8_printf(psi->out, BOX_13 " ");
for(i=0; i<nColumn; i++){
w = psx->pHaveWidths[i];
n = strlenChar(azData[i]);
utf8_printf(psi->out, "%*s%s%*s%s",
(w-n)/2, "", azData[i], (w-n+1)/2, "",
i==nColumn-1?" "BOX_13"\n":" "BOX_13" ");
}
print_box_row_separator(psx, nColumn, BOX_123, BOX_1234, BOX_134);
break;
}
}
done:
pbie->nCol = nColumn;
pbie->pData = azData;
pbie->nRow = nRow;
if( bMultiLineRowExists ){
pbie->pRowInfo = abRowDiv;
}else{
pbie->pRowInfo = 0;
sqlite3_free(abRowDiv);
}
if( seenInterrupt ){
EH_CM_destruct(pMe);
return SQLITE_INTERRUPT;
}
return SQLITE_OK;
}
static int EH_CM_rowResultsOut(ExportHandler *pMe,
ShellExState *psx, char **pzErr,
sqlite3_stmt *pStmt){
BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
ShellInState *psi = pbie->psi;
sqlite3_int64 nRow = pbie->nRow;
int nColumn = pbie->nCol, j, w;
char **azData = (char**)(pbie->pData);
sqlite3_int64 nData = (nRow+1)*nColumn, i;
char *abRowDiv = pbie->pRowInfo;
const char *z;
(void)(pzErr);
(void)(pStmt);
if( nRow==0 || nColumn==0 ) return SQLITE_INTERRUPT;
for(i=nColumn, j=0; i<nData; i++, j++){
if( j==0 && psi->cMode!=MODE_Column ){
utf8_printf(psi->out, "%s", psi->cMode==MODE_Box?BOX_13" ":"| ");
}
z = azData[i];
if( z==0 ) z = zEmpty;
w = psx->pHaveWidths[j];
if( psx->pSpecWidths[j]<0 ) w = -w;
utf8_width_print(psi->out, w, z);
if( j==nColumn-1 ){
utf8_printf(psi->out, "%s", pbie->rowSep);
if( abRowDiv!=0 && abRowDiv[i/nColumn-1] && i+1<nData ){
if( psi->cMode==MODE_Table ){
print_row_separator(psx, nColumn, "+");
}else if( psi->cMode==MODE_Box ){
print_box_row_separator(psx, nColumn, BOX_123, BOX_1234, BOX_134);
}else if( psi->cMode==MODE_Column ){
raw_printf(psi->out, "\n");
}
}
j = -1;
if( seenInterrupt ){
EH_CM_destruct(pMe);
return SQLITE_INTERRUPT;
}
}else{
utf8_printf(psi->out, "%s", pbie->colSep);
}
}
return SQLITE_DONE;
}
static int EH_CM_appendResultsOut(ExportHandler *pMe,
ShellExState *psx, char **pzErr,
sqlite3_stmt *pStmt){
BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
ShellInState *psi = ISS(psx);
sqlite3_int64 nRow = pbie->nRow;
int nColumn = pbie->nCol;
char **azData = (char**)(pbie->pData);
sqlite3_int64 nData = (nRow+1)*nColumn;
if( nRow==0 || nColumn==0 ) return SQLITE_INTERRUPT;
if( psi->cMode==MODE_Table ){
print_row_separator(psx, nColumn, "+");
}else if( psi->cMode==MODE_Box ){
print_box_row_separator(psx, nColumn, BOX_12, BOX_124, BOX_14);
}
EH_CM_destruct(pMe);
return SQLITE_OK;
}
static int EH_FF_prependResultsOut(ExportHandler *pMe,
ShellExState *pSES, char **pzErr,
sqlite3_stmt *pStmt){
BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
int nc = sqlite3_column_count(pStmt);
int rc;
pbie->pMethods->destruct(pMe);
if( nc>0 ){
/* allocate space for col name ptr, value ptr, and type */
pbie->pData = sqlite3_malloc64(3*nc*sizeof(const char*) + 1);
if( !pbie->pData ){
shell_out_of_memory();
}else{
ColumnsInfo ci
= { (char **)pbie->pData, &ci.azCols[nc], (int *)&ci.azVals[nc] };
int i;
assert(sizeof(int) <= sizeof(char *));
pbie->nCol = nc;
pbie->colInfo = ci;
/* save off ptrs to column names */
for(i=0; i<nc; i++){
pbie->colInfo.azCols[i] = (char *)sqlite3_column_name(pStmt, i);
}
}
return SQLITE_OK;
}
rc = sqlite3_step(pStmt);
assert(rc!=SQLITE_ROW);
return rc;
}
static int EH_FF_rowResultsOut(ExportHandler *pMe,
ShellExState *pSES, char **pzErr,
sqlite3_stmt *pStmt){
BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
ShellInState *psi = ISS(pSES);
int rc = sqlite3_step(pStmt);
int i, x, nc = pbie->nCol;
if( rc==SQLITE_ROW ){
ColumnsInfo *pc = &pbie->colInfo;
sqlite3_uint64 nr = ++(pbie->nRow);
for( i=0; i<nc; ++i ){
pc->aiTypes[i] = x = sqlite3_column_type(pStmt, i);
if( x==SQLITE_BLOB
&& (psi->cMode==MODE_Insert || psi->cMode==MODE_Quote) ){
pc->azVals[i] = "";
}else{
pc->azVals[i] = (char*)sqlite3_column_text(pStmt, i);
}
if( !pc->azVals[i] && (x!=SQLITE_NULL) ){
rc = SQLITE_NOMEM;
break; /* from for */
}
}
/* if data and types extracted successfully... */
if( SQLITE_ROW==rc ){
/* call the supplied callback with the result row data */
if( shell_callback(pSES, nc, pc->azVals, pc->azCols, pc->aiTypes) ){
rc = SQLITE_ABORT;
}
}
}
return rc;
}
static int EH_FF_appendResultsOut(ExportHandler *pMe,
ShellExState *pSES, char **pzErr,
sqlite3_stmt *pStmt){
BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
ShellInState *psi = ISS(pSES);
if( psi->cMode==MODE_Json ){
fputs("]\n", psi->out);
}else if( psi->cMode==MODE_Count ){
utf8_printf(psi->out, "%llu row%s\n", pbie->nRow, pbie->nRow!=1 ? "s" : "");
}
EH_FF_destruct(pMe);
return SQLITE_OK;
}
static void EH_closeResultsOutStream(ExportHandler *pMe,
ShellExState *pSES,
char **pzErr){
/* The built-in exporters have a predetermined destination which is
* never "closed", so this method has nothing to do. For similar
* reasons, it is not called by the shell.
*/
(void)(pMe);
(void)(pSES);
(void)(pzErr);
}
#endif /* SHELL_DATAIO_EXT */
#if SHELL_DYNAMIC_EXTENSION
/* Ensure there is room in loaded extension info list for one being loaded.
* On exit, psi->ixExtPending can be used to index into psi->pShxLoaded.
*/
static ShExtInfo *pending_ext_info(ShellInState *psi){
int ixpe = psi->ixExtPending;
assert(ixpe!=0);
if( ixpe >= psi->numExtLoaded ){
psi->pShxLoaded = sqlite3_realloc(psi->pShxLoaded,
(ixpe+1)*sizeof(ShExtInfo));
shell_check_ooms(psi->pShxLoaded);
++psi->numExtLoaded;
memset(psi->pShxLoaded+ixpe, 0, sizeof(ShExtInfo));
}
return &psi->pShxLoaded[ixpe];
}
/* Register a dot-command, to be called during extension load/init. */
static int register_dot_command(ShellExState *p,
ExtensionId eid, DotCommand *pMC){
ShellInState *psi = ISS(p);
ShExtInfo *psei = pending_ext_info(psi);
const char *zSql
= "INSERT INTO "SHELL_DISP_TAB"(name, extIx, cmdIx) VALUES(?, ?, ?)";
int ie = psi->ixExtPending;
assert(psi->pShxLoaded!=0 && p->dbShell!=0);
if( pMC==0 ) return SQLITE_ERROR;
else{
const char *zName = pMC->pMethods->name(pMC);
sqlite3_stmt *pStmt;
int nc = psei->numDotCommands;
int rc;
if( psei->extId!=0 && psei->extId!=eid ) return SQLITE_MISUSE;
psei->extId = eid;
rc = s3_prepare_v2_noom(p->dbShell, zSql, -1, &pStmt, 0);
if( rc!=SQLITE_OK ) return rc;
psei->ppDotCommands
= sqlite3_realloc(psei->ppDotCommands, (nc+1)*sizeof(DotCommand *));
shell_check_ooms(psei->ppDotCommands);
sqlite3_bind_text(pStmt, 1, zName, -1, 0);
sqlite3_bind_int(pStmt, 2, ie);
sqlite3_bind_int(pStmt, 3, nc);
rc = sqlite3_step(pStmt);
sqlite3_finalize(pStmt);
if( rc==SQLITE_DONE ){
psei->ppDotCommands[nc++] = pMC;
psei->numDotCommands = nc;
notify_subscribers(psi, NK_NewDotCommand, pMC);
if( cli_strcmp("unknown", zName)==0 ){
psi->pUnknown = pMC;
psei->pUnknown = pMC;
}
return SQLITE_OK;
}else{
psei->ppDotCommands[nc] = 0;
}
}
return SQLITE_ERROR;
}
/* Register an output data display (or other disposition) mode */
static int register_exporter(ShellExState *p,
ExtensionId eid, ExportHandler *pEH){
return SQLITE_ERROR;
}
/* Register an import variation from (various sources) for .import */
static int register_importer(ShellExState *p,
ExtensionId eid, ImportHandler *pIH){
return SQLITE_ERROR;
}
/* See registerScripting API in shext_linkage.h */
static int register_scripting(ShellExState *p, ExtensionId eid,
ScriptSupport *pSS){
ShellInState *psi = ISS(p);
if( psi->scriptXid!=0 || psi->script!=0 ){
/* Scripting support already provided. Only one provider is allowed. */
return SQLITE_BUSY;
}
if( eid==0 || pSS==0 || psi->ixExtPending==0 ){
/* Scripting addition allowed only when sqlite3_*_init() runs. */
return SQLITE_MISUSE;
}
psi->script = pSS;
psi->scriptXid = eid;
return SQLITE_OK;
}
/* See registerAdHocCommand API in shext_linkage.h re detailed behavior.
* Depending on zHelp==0, either register or unregister treatment of
* of zName for this extension (identified by eId.)
*/
static int register_adhoc_command(ShellExState *p, ExtensionId eId,
const char *zName, const char *zHelp){
ShellInState *psi = ISS(p);
u8 bRegNotRemove = zHelp!=0;
const char *zSql = bRegNotRemove
? "INSERT OR REPLACE INTO "SHELL_AHELP_TAB
"(name, extIx, helpText) VALUES(?, ?, ?||?||?)"
: "DELETE FROM "SHELL_AHELP_TAB" WHERE name=? AND extIx=?";
sqlite3_stmt *pStmt;
int rc, ie;
assert(psi->pShxLoaded!=0 && p->dbShell!=0);
for( ie=psi->numExtLoaded-1; ie>0; --ie ){
if( psi->pShxLoaded[ie].extId==eId ) break;
}
if( !zName || ie==0 || psi->pShxLoaded[ie].pUnknown==0 ) return SQLITE_MISUSE;
rc = s3_prepare_v2_noom(p->dbShell, zSql, -1, &pStmt, 0);
if( rc!=SQLITE_OK ) return rc;
sqlite3_bind_text(pStmt, 1, zName, -1, 0);
sqlite3_bind_int(pStmt, 2, ie);
if( bRegNotRemove ){
int nc = strlen30(zHelp);
char cLead = *zHelp;
/* Add leading '.' if no help classifier present. */
const char *zCL = (cLead!='.' && cLead!=',')? "." : "";
/* Add trailing newline if not already there. */
const char *zLE = (nc>0 && zHelp[nc-1]!='\n')? "\n" : "";
sqlite3_bind_text(pStmt, 3, zCL, -1, 0);
sqlite3_bind_text(pStmt, 4, zHelp, -1, 0);
sqlite3_bind_text(pStmt, 5, zLE, -1, 0);
}
rc = sqlite3_step(pStmt);
sqlite3_finalize(pStmt);
return (rc==SQLITE_DONE)? SQLITE_OK : SQLITE_ERROR;
}
/*
* Subscribe to (or unsubscribe from) messages about various changes.
* Unsubscribe, effected when nkMin==NK_Unsubscribe, always succeeds.
* Return SQLITE_OK on success, or one of these error codes:
* SQLITE_ERROR when the nkMin value is unsupported by this host;
* SQLITE_NOMEM when a required allocation failed; or
* SQLITE_MISUSE when the provided eId or eventHandler is invalid.
*/
static int subscribe_events(ShellExState *p, ExtensionId eId, void *pvUserData,
NoticeKind nkMin, ShellEventNotify eventHandler){
ShellInState *psi = ISS(p);
struct EventSubscription *pes = psi->pSubscriptions;
struct EventSubscription *pesLim = pes + psi->numSubscriptions;
if( nkMin==NK_Unsubscribe ){
/* unsubscribe (if now subscribed) */
while( pes < pesLim ){
if( (eventHandler==0 || eventHandler==pes->eventHandler)
&& (pes->eid==0 || pes->eid==eId)
&& (eId!=0 || eventHandler!=0 ||/*for shell use*/ pvUserData==p ) ){
int nLeft = pesLim - pes;
assert(pes->eventHandler!=0);
pes->eventHandler(pes->pvUserData, NK_Unsubscribe, pes->eid, p);
if( nLeft>1 ) memmove(pes, pes+1, (nLeft-1)*sizeof(*pes));
--pesLim;
--psi->numSubscriptions;
}else{
++pes;
}
}
if( psi->numSubscriptions==0 ){
sqlite3_free(psi->pSubscriptions);
psi->pSubscriptions = 0;
}
return SQLITE_OK;
}else{
/* subscribe only if minimum NoticeKind supported by this host */
if( nkMin > NK_CountOf ) return SQLITE_ERROR;
if( eventHandler==0 || eId==0 ) return SQLITE_MISUSE;
while( pes < pesLim ){
/* Never add duplicate handlers, but may renew their user data. */
if( pes->eid==eId && pes->eventHandler==eventHandler ){
pes->pvUserData = pvUserData;
return SQLITE_OK;
}
++pes;
}
assert(pes==pesLim);
pes = sqlite3_realloc(psi->pSubscriptions,
(psi->numSubscriptions+1)*sizeof(*pes));
if( pes==0 ) return SQLITE_NOMEM;
psi->pSubscriptions = pes;
pes += (psi->numSubscriptions++);
pes->eid = eId;
pes->pvUserData = pvUserData;
pes->eventHandler = eventHandler;
return SQLITE_OK;
}
}
/*
* Unsubscribe all event listeners having an ExtensionId > 0. This is
* done just prior closing the shell DB (when dynamic extensions will
* be unloaded and accessing them in any way is good for a crash.)
*/
static void unsubscribe_extensions(ShellInState *psi){
ShellExState *psx = XSS(psi);
int esix = 0;
if( psi->numExtLoaded<=1 ) return; /* Ignore shell pseudo-extension. */
while( esix<psi->numSubscriptions ){
struct EventSubscription *pes = psi->pSubscriptions+esix;
if( pes->eid > 0 ){
int nsin = psi->numSubscriptions;
subscribe_events(psx, pes->eid, psx, NK_Unsubscribe, 0);
esix = esix + 1 + (psi->numSubscriptions - nsin);
}else ++esix;
}
}
static struct InSource *currentInputSource(ShellExState *p){
return ISS(p)->pInSource;
}
static int nowInteractive(ShellExState *p){
return INSOURCE_IS_INTERACTIVE(ISS(p)->pInSource);
}
static const char *shellInvokedAs(void){
return Argv0;
}
static const char *shellStartupDir(void){
return startupDir;
}
static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths);
static DotCommand * findDotCommand(const char *, ShellExState *, int *);
static DotCmdRC runDotCommand(DotCommand*, char *[], int na, ShellExState*);
static ExtensionHelpers extHelpers = {
13,
{
failIfSafeMode,
utf8_out_printf,
currentInputSource,
strLineGet,
findDotCommand,
runDotCommand,
setColumnWidths,
nowInteractive,
shellInvokedAs,
shellStartupDir,
one_input_line,
free_input_line,
sqlite3_enable_load_extension,
0
}
};
static ShellExtensionAPI shellExtAPI = {
&extHelpers, 6, {
register_dot_command,
register_exporter,
register_importer,
register_scripting,
subscribe_events,
register_adhoc_command,
0
}
};
/* This SQL function provides a way for a just-loaded shell extension to
* obtain a ShellExtensionLink pointer from the shell core while using
* the same sqlite3_load_extension API used for SQLite extensions.
*
* (It is also useful for debugging a shell extension, as a breakpoint
* on it will be hit soon after loading and before real work is done.)
*/
static void shell_linkage(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
int linkKind = 0;
void *pv;
if( argc>0 ){
linkKind = sqlite3_value_int(argv[0]);
}
switch (linkKind){
case 0:
pv = sqlite3_user_data(context);
break;
case 1:
pv = &extHelpers;
break;
case 2:
pv = &shellExtAPI;
break;
default:
pv = 0;
}
if( pv==0 ) sqlite3_result_null(context);
else sqlite3_result_pointer(context, pv, SHELLEXT_API_POINTERS, 0);
}
/* Free the memory held by a ShExtInfo object but not the object itself.
* No notifications associated with takedown and termination are done. */
static void free_ShExtInfo( ShExtInfo *psei ){
if( psei ){
if( psei->ppDotCommands ) sqlite3_free(psei->ppDotCommands);
if( psei->ppExportHandlers ) sqlite3_free(psei->ppExportHandlers);
if( psei->ppImportHandlers ) sqlite3_free(psei->ppImportHandlers);
memset(psei, 0, sizeof(ShExtInfo));
}
}
/* Do the initialization needed for use of dbShell for command lookup
* and dispatch and for I/O handler lookup and dispatch.
*/
static int begin_db_dispatch(ShellExState *psx){
ShellInState *psi = ISS(psx);
sqlite3_stmt *pStmt = 0;
int ic, rc1, rc2;
int rc = 0;
char *zErr = 0;
const char *zSql;
ShExtInfo sei = SHEXT_INFO_INIT;
AnyResourceHolder arh_sei = {&sei, (GenericFreer)free_ShExtInfo};
ResourceMark mark = holder_mark();
sstr_ptr_holder(&zErr);
/* Consider: Store these dynamic arrays in the DB as indexed-into blobs. */
assert(psx->dbShell==0 || (psi->numExtLoaded==0 && psi->pShxLoaded==0));
rc = ensure_shell_db(psx);
if( rc!=SQLITE_OK ){
utf8_printf(STD_ERR, "Error: Shell DB uncreatable. Best to .quit soon.\n");
return SQLITE_ERROR;
}
if( ensure_dispatch_table(psx)!=SQLITE_OK ) return 1;
psi->pShxLoaded = (ShExtInfo *)sqlite3_malloc(2*sizeof(ShExtInfo));
shell_check_ooms(psi->pShxLoaded);
/* The ShellInState object now owns above allocation, so initialize it. */
memset(psi->pShxLoaded, 0, 2*sizeof(ShExtInfo));
any_ref_holder(&arh_sei); /* protect against early aborts */
sei.ppDotCommands
= (DotCommand **)sqlite3_malloc((numCommands+2)*sizeof(DotCommand *));
sei.ppExportHandlers
= (ExportHandler **)sqlite3_malloc(2*sizeof(ExportHandler *));
sei.ppImportHandlers
= (ImportHandler **)sqlite3_malloc(2*sizeof(ImportHandler *));
if( sei.ppDotCommands==0||sei.ppExportHandlers==0||sei.ppImportHandlers==0
|| psi->pShxLoaded==0 ){
shell_out_of_memory();
}
sei.numExportHandlers = 0;
sei.numImportHandlers = 0;
for( ic=0; ic<(int)numCommands; ++ic ){
sei.ppDotCommands[ic] = builtInCommand(ic);
}
sei.numDotCommands = ic;
zSql = "INSERT INTO "SHELL_DISP_TAB"(name, extIx, cmdIx) VALUES(?, 0, ?)";
rc1 = s3_prepare_v2_noom(psx->dbShell, zSql, -1, &pStmt, 0);
stmt_holder(pStmt);
rc2 = s3_exec_noom(psx->dbShell, "BEGIN TRANSACTION", 0, 0, &zErr);
if( rc1!=SQLITE_OK || rc2!=SQLITE_OK ){
rc = SQLITE_ERROR;
}else{
assert(sei.numDotCommands>0);
for( ic=0; ic<sei.numDotCommands; ++ic ){
DotCommand *pmc = sei.ppDotCommands[ic];
const char *zName = pmc->pMethods->name(pmc);
sqlite3_reset(pStmt);
shell_check_nomem(sqlite3_bind_text(pStmt, 1, zName, -1, 0));
sqlite3_bind_int(pStmt, 2, ic);
rc = s3_step_noom(pStmt);
if( rc!=SQLITE_DONE ){
sqlite3_exec(psx->dbShell, "ABORT", 0, 0, 0);
break;
}
}
if( rc!=SQLITE_DONE ){
rc = SQLITE_ERROR;
zSql = "ABORT";
}else{
rc = SQLITE_OK;
zSql = "COMMIT";
}
rc2 = s3_exec_noom(psx->dbShell, zSql, 0, 0, &zErr);
if( SQLITE_OK==rc ){
/* Transfer just-built ShExtInfo to ShellInState use and ownership. */
psi->pShxLoaded[psi->numExtLoaded++] = sei;
arh_sei.pAny = 0;
sqlite3_enable_load_extension(psx->dbShell, 1);
psi->bDbDispatch = 1;
}
}
RESOURCE_FREE(mark);
return rc;
}
/* Call one loaded extension's destructors, in reverse order of their
* objects' creation.
*/
static void run_one_shext_dtors(ShExtInfo *psei){
int j;
if( psei->ppDotCommands!=0 ){
for( j=psei->numDotCommands; j>0; --j ){
DotCommand *pmc = psei->ppDotCommands[j-1];
if( pmc->pMethods->destruct!=0 ) pmc->pMethods->destruct(pmc);
}
}
if( psei->ppExportHandlers!=0 ){
for( j=psei->numExportHandlers; j>0; --j ){
ExportHandler *peh = psei->ppExportHandlers[j-1];
if( peh->pMethods->destruct!=0 ) peh->pMethods->destruct(peh);
}
}
if( psei->ppImportHandlers!=0 ){
for( j=psei->numImportHandlers; j>0; --j ){
ImportHandler *pih = psei->ppImportHandlers[j-1];
if( pih->pMethods->destruct!=0 ) pih->pMethods->destruct(pih);
}
}
if( psei->extDtor!=0 ){
psei->extDtor(psei->pvExtObj);
}
}
/* Call all existent loaded extension destructors, in reverse order of their
* objects' creation, except for scripting support which is done last,
* then free the tracking dynamic arrays.
*/
static void free_all_shext_tracking(ShellInState *psi){
if( psi->pShxLoaded!=0 ){
int i = psi->numExtLoaded;
while( i>1 ){
ShExtInfo *psei = &psi->pShxLoaded[--i];
run_one_shext_dtors(psei);
free_ShExtInfo(psei);
if( psi->scriptXid!=0 && psi->scriptXid==psei->extId ){
assert(psi->script!=0);
if (psi->script->pMethods->destruct){
psi->script->pMethods->destruct(psi->script);
}
psi->script = 0;
psi->scriptXid = 0;
}
}
free_ShExtInfo(psi->pShxLoaded);
sqlite3_free(psi->pShxLoaded);
psi->pShxLoaded = 0;
psi->numExtLoaded = 0;
}
}
static DotCommand *command_by_index(ShellInState *psi, int extIx, int cmdIx){
assert(extIx>=0);
if( extIx>=0 && extIx<psi->numExtLoaded ){
ShExtInfo *psei = & psi->pShxLoaded[extIx];
if( cmdIx>=0 && cmdIx<psei->numDotCommands ){
return psei->ppDotCommands[cmdIx];
}
}
return 0;
}
static int load_shell_extension(ShellExState *psx, const char *zFile,
const char *zProc, char **pzErr,
int nLoadArgs, char **azLoadArgs){
ShellExtensionLink shxLink = {
sizeof(ShellExtensionLink),
&shellExtAPI,
psx, /* pSXS */
0, /* zErrMsg */
0, /* ExtensionId */
0, /* Extension destructor */
0, /* Extension data ref */
nLoadArgs, azLoadArgs /* like-named members */
}; //extDtor(pvExtObj)
ShellInState *psi = ISS(psx);
/* save script support state for possible fallback if load fails */
ScriptSupport *pssSave = psi->script;
ExtensionId ssiSave = psi->scriptXid;
int rc;
if( pzErr ) *pzErr = 0;
if( psx->dbShell==0 || ISS(psx)->numExtLoaded==0 ){
rc = begin_db_dispatch(psx);
if( rc!=SQLITE_OK ) return rc;
assert(ISS(psx)->numExtLoaded==1 && psx->dbShell!=0);
}
psi->ixExtPending = psi->numExtLoaded;
sqlite3_create_function(psx->dbShell, "shext_pointer", 1,
SQLITE_DIRECTONLY|SQLITE_UTF8,
&shxLink, shell_linkage, 0, 0);
rc = sqlite3_load_extension(psx->dbShell, zFile, zProc, &shxLink.zErrMsg);
sqlite3_create_function(psx->dbShell, "shext_pointer", 1,
SQLITE_DIRECTONLY|SQLITE_UTF8,
0, 0, 0, 0); /* unregister */
if( pzErr!=0 ) *pzErr = shxLink.zErrMsg;
if( rc==SQLITE_OK ){
/* Keep extension's id and destructor for later disposal. */
ShExtInfo *psei = pending_ext_info(psi);
if( psei->extId!=0 && psei->extId!=shxLink.eid ) rc = SQLITE_MISUSE;
psei->extId = shxLink.eid;
psei->extDtor = shxLink.extensionDestruct;
psei->pvExtObj = shxLink.pvExtensionObject;
}else{
/* Release all resources extension might have registered before failing. */
if( psi->ixExtPending < psi->numExtLoaded ){
run_one_shext_dtors(psi->pShxLoaded+psi->ixExtPending);
free_ShExtInfo(psi->pShxLoaded+psi->ixExtPending);
--psi->numExtLoaded;
}
/* And make it unwind any scripting linkage it might have setup. */
if( psi->script!=0 ) psi->script->pMethods->destruct(psi->script);
psi->script = pssSave;
psi->scriptXid = ssiSave;
}
psi->ixExtPending = 0;
if( rc!=SQLITE_OK ){
if( rc==SQLITE_MISUSE && pzErr!=0 ){
*pzErr = smprintf("extension id mismatch %z\n", *pzErr);
}
rc = SQLITE_ERROR;
}
return rc;
}
#endif
/* Dot-command implementation functions are defined in this section.
COMMENT Define dot-commands and provide for their dispatch and .help text.
COMMENT These should be kept in command name order for coding convenience
COMMENT except where dot-commands share implementation. (The ordering
COMMENT required for dispatch and help text is effected regardless.) The
COMMENT effect of this configuration can be seen in generated output or by
COMMENT executing tool/mkshellc.tcl --parameters (or --details or --help).
COMMENT Generally, this section defines dispatchable functions inline and
COMMENT causes collection of command_table entry initializers, to be later
COMMENT emitted by a macro invocation. (See EMIT_DOTCMD_INIT further on.)
** All dispatchable dot-command execute functions have this signature:
static int someCommand(char *azArg[], int nArg, ShellExState *p, char **pzErr);
*/
DISPATCH_CONFIG[
RETURN_TYPE=DotCmdRC
STORAGE_CLASS=static
ARGS_SIGNATURE=char *$arg4\[\], int $arg5, ShellExState *$arg6, char **$arg7
DISPATCH_ENTRY={ "$cmd", ${cmd}Command, $arg1, $arg2, $arg3 },
DOTCMD_INIT={ DOT_CMD_INFO(${cmd}, $arg1,$arg2,$arg3), {<HT0>, <HT1>}, 0 },
CMD_CAPTURE_RE=^\s*{\s*"(\w+)"
DISPATCHEE_NAME=${cmd}Command
DC_ARG1_DEFAULT=[string length $cmd]
DC_ARG2_DEFAULT=0
DC_ARG3_DEFAULT=0
DC_ARG4_DEFAULT=azArg
DC_ARG5_DEFAULT=nArg
DC_ARG6_DEFAULT=p
DC_ARG7_DEFAULT=pzErr
DC_ARG_COUNT=8
];
CONDITION_COMMAND(seeargs !defined(SHELL_OMIT_SEEARGS));
/*****************
* The .seeargs command
*/
COLLECT_HELP_TEXT[
",seeargs Echo arguments suffixed with |",
];
DISPATCHABLE_COMMAND( seeargs ? 0 0 azArg nArg p ){
int ia = 0;
for (ia=1; ia<nArg; ++ia)
raw_printf(ISS(p)->out, "%s%s", azArg[ia], (ia==nArg-1)? "|\n" : "|");
return DCR_Ok;
}
CONDITION_COMMAND(archive ARCHIVE_ENABLE && !defined(SQLITE_SHELL_FIDDLE));
/*****************
* The .archive command
*/
COLLECT_HELP_TEXT[
".archive ... Manage SQL archives",
" Each command must have exactly one of the following options:",
" -c, --create Create a new archive",
" -u, --update Add or update files with changed mtime",
" -i, --insert Like -u but always add even if unchanged",
" -r, --remove Remove files from archive",
" -t, --list List contents of archive",
" -x, --extract Extract files from archive",
" Optional arguments:",
" -v, --verbose Print each filename as it is processed",
" -f FILE, --file FILE Use archive FILE (default is current db)",
" -a FILE, --append FILE Open FILE using the apndvfs VFS",
" -C DIR, --directory DIR Read/extract files from directory DIR",
" -g, --glob Use glob matching for names in archive",
" -n, --dryrun Show the SQL that would have occurred",
" Examples:",
" .ar -cf ARCHIVE foo bar # Create ARCHIVE from files foo and bar",
" .ar -tf ARCHIVE # List members of ARCHIVE",
" .ar -xvf ARCHIVE # Verbosely extract files from ARCHIVE",
" See also:",
" http://sqlite.org/cli.html#sqlite_archive_support",
];
DISPATCHABLE_COMMAND( archive ? 2 0 azArg nArg p ){
open_db(p, 0);
if( ISS(p)->bSafeMode ) return DCR_AbortError;
return arDotCommand(p, 0, azArg, nArg);
}
/*****************
* The .auth command
*/
CONDITION_COMMAND(auth !defined(SQLITE_OMIT_AUTHORIZATION));
COLLECT_HELP_TEXT[
".auth ON|OFF Show authorizer callbacks",
];
DISPATCHABLE_COMMAND( auth 3 2 2 azArg nArg p ){
open_db(p, 0);
if( booleanValue(azArg[1]) ){
sqlite3_set_authorizer(DBX(p), shellAuth, p);
}else if( ISS(p)->bSafeModeFuture ){
sqlite3_set_authorizer(DBX(p), safeModeAuth, p);
}else{
sqlite3_set_authorizer(DBX(p), 0, 0);
}
return DCR_Ok;
}
/*****************
* The .backup and .save commands (aliases for each other)
* These defer to writeDb in the dispatch table, so are not here.
*/
CONDITION_COMMAND(backup !defined(SQLITE_SHELL_FIDDLE));
CONDITION_COMMAND(save !defined(SQLITE_SHELL_FIDDLE) );
COLLECT_HELP_TEXT[
".backup ?DB? FILE Backup DB (default \"main\") to FILE",
" Options:",
" --append Use the appendvfs",
" --async Write the FILE without journal and fsync()",
".save ?DB? FILE Write DB (default \"main\") to FILE",
" Options:",
" --append Use the appendvfs",
" --async Write the FILE without journal and fsync()",
];
DISPATCHABLE_COMMAND( backup 4 2 5 ){
return writeDb( azArg, nArg, p, pzErr);
}
DISPATCHABLE_COMMAND( save 3 2 5 ){
return writeDb( azArg, nArg, p, pzErr);
}
/*****************
* The .bail command
*/
COLLECT_HELP_TEXT[
".bail on|off Stop after hitting an error. Default OFF",
];
DISPATCHABLE_COMMAND( bail 3 2 2 ){
bail_on_error = booleanValue(azArg[1]);
return DCR_Ok;
}
CONDITION_COMMAND(cd !defined(SQLITE_SHELL_FIDDLE));
/*****************
* The .binary and .cd commands
*/
COLLECT_HELP_TEXT[
".binary on|off Turn binary output on or off. Default OFF",
".cd DIRECTORY Change the working directory to DIRECTORY",
];
DISPATCHABLE_COMMAND( binary 3 2 2 ){
if( booleanValue(azArg[1]) ){
setBinaryMode(ISS(p)->out, 1);
}else{
setTextMode(ISS(p)->out, 1);
}
return DCR_Ok;
}
DISPATCHABLE_COMMAND( cd ? 2 2 ){
int rc=0;
if( ISS(p)->bSafeMode ) return DCR_AbortError;
else{
#if defined(_WIN32) || defined(WIN32)
wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
shell_check_ooms(z);
rc = (z)? !SetCurrentDirectoryW(z) : 1;
sqlite3_free(z);
#else
rc = chdir(azArg[1]);
#endif
}
if( rc ){
utf8_printf(STD_ERR, "Cannot change to directory \"%s\"\n", azArg[1]);
rc = 1;
}
return DCR_Ok|rc;
}
/* The ".breakpoint" command causes a call to the no-op routine named
* test_breakpoint(). It is undocumented.
*/
COLLECT_HELP_TEXT[
",breakpoint calls test_breakpoint(). (a debugging aid)",
];
DISPATCHABLE_COMMAND( breakpoint 3 1 1 ){
test_breakpoint();
return DCR_Ok;
}
CONDITION_COMMAND(check !defined(SQLITE_SHELL_FIDDLE));
CONDITION_COMMAND(clone !defined(SQLITE_SHELL_FIDDLE));
/*****************
* The .changes, .check, .clone and .connection commands
*/
COLLECT_HELP_TEXT[
".changes on|off Show number of rows changed by SQL",
",check GLOB Fail if output since .testcase does not match",
".clone NEWDB Clone data into NEWDB from the existing database",
".connection [close] [#] Open or close an auxiliary database connection",
];
DISPATCHABLE_COMMAND( changes 3 2 2 ){
setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
return DCR_Ok;
}
DISPATCHABLE_COMMAND( check 3 0 0 ){
/* Cancel output redirection, if it is currently set (by .testcase)
** Then read the content of the testcase-out.txt file and compare against
** azArg[1]. If there are differences, report an error and exit.
*/
char *zRes = 0;
DotCmdRC rv = DCR_Ok;
output_reset(ISS(p));
if( nArg!=2 ){
return DCR_ArgWrong;
}else if( (zRes = readFile("testcase-out.txt", 0))==0 ){
*pzErr = smprintf("Error: cannot read 'testcase-out.txt'\n");
rv = DCR_Return;
}else if( testcase_glob(azArg[1],zRes)==0 ){
*pzErr =
smprintf("testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n",
ISS(p)->zTestcase, azArg[1], zRes);
rv = DCR_Error;
}else{
utf8_printf(STD_OUT, "testcase-%s ok\n", ISS(p)->zTestcase);
ISS(p)->nCheck++;
}
sqlite3_free(zRes);
return (zRes==0)? DCR_Abort : rv;
}
DISPATCHABLE_COMMAND( clone ? 2 2 ){
if( ISS(p)->bSafeMode ) return DCR_AbortError;
tryToClone(p, azArg[1]);
return DCR_Ok;
}
DISPATCHABLE_COMMAND( connection ? 1 4 ){
ShellInState *psi = ISS(p);
if( nArg==1 ){
/* List available connections */
int i;
for(i=0; i<ArraySize(psi->aAuxDb); i++){
const char *zFile = psi->aAuxDb[i].zDbFilename;
if( psi->aAuxDb[i].db==0 && psi->pAuxDb!=&psi->aAuxDb[i] ){
zFile = "(not open)";
}else if( zFile==0 ){
zFile = "(memory)";
}else if( zFile[0]==0 ){
zFile = "(temporary-file)";
}
if( psi->pAuxDb == &psi->aAuxDb[i] ){
utf8_printf(STD_OUT, "ACTIVE %d: %s\n", i, zFile);
}else if( psi->aAuxDb[i].db!=0 ){
utf8_printf(STD_OUT, " %d: %s\n", i, zFile);
}
}
}else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){
int i = azArg[1][0] - '0';
if( psi->pAuxDb != &psi->aAuxDb[i] && i>=0 && i<ArraySize(psi->aAuxDb) ){
psi->pAuxDb->db = DBX(p);
psi->pAuxDb = &psi->aAuxDb[i];
#if SHELL_DYNAMIC_EXTENSION
if( DBX(p)!=0 ) notify_subscribers(psi, NK_DbUserVanishing, DBX(p));
#endif
globalDb = DBX(p) = psi->pAuxDb->db;
#if SHELL_DYNAMIC_EXTENSION
if( DBX(p)!=0 ) notify_subscribers(psi, NK_DbUserAppeared, DBX(p));
#endif
psi->pAuxDb->db = 0;
}
}else if( nArg==3 && cli_strcmp(azArg[1], "close")==0
&& IsDigit(azArg[2][0]) && azArg[2][1]==0 ){
int i = azArg[2][0] - '0';
if( i<0 || i>=ArraySize(psi->aAuxDb) ){
/* No-op */
}else if( psi->pAuxDb == &psi->aAuxDb[i] ){
raw_printf(STD_ERR, "cannot close the active database connection\n");
return DCR_Error;
}else if( psi->aAuxDb[i].db ){
session_close_all(psi, i);
close_db(psi,psi->aAuxDb[i].db);
psi->aAuxDb[i].db = 0;
}
}else{
return DCR_ArgWrong;
}
return DCR_Ok;
}
CONDITION_COMMAND(dbinfo SQLITE_SHELL_HAVE_RECOVER);
/*****************
* The .databases, .dbconfig and .dbinfo commands
*/
COLLECT_HELP_TEXT[
".databases List names and files of attached databases",
".dbconfig ?op? ?val? List or change sqlite3_db_config() options",
".dbinfo ?DB? Show status information about the database",
];
/* Allow garbage arguments on this, to be ignored. */
DISPATCHABLE_COMMAND( databases 2 1 0 ){
int rc;
char **azName = 0;
int nName = 0;
sqlite3_stmt *pStmt = 0;
sqlite3 *db = open_db(p, 0);
rc = s3_prepare_v2_noom(db, "PRAGMA database_list", -1, &pStmt, 0);
stmt_holder(pStmt);
if( rc || pStmt==0 ){
*pzErr = smprintf("%s\n", sqlite3_errmsg(db));
rc = 1;
}else{
while( s3_step_noom(pStmt)==SQLITE_ROW ){
int eTxn, bRdonly;
const char *zSchema = (const char *)sqlite3_column_text(pStmt,1);
const char *zFile = (const char*)sqlite3_column_text(pStmt,2);
if( zSchema==0 || zFile==0 ) continue;
eTxn = sqlite3_txn_state(db, zSchema);
bRdonly = sqlite3_db_readonly(db, zSchema);
utf8_printf(ISS(p)->out, "%s: %s %s%s\n",
zSchema,
zFile[0] ? zFile : "\"\"",
bRdonly ? "r/o" : "r/w",
eTxn==SQLITE_TXN_NONE ? "" :
eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
}
}
release_holder();
return DCR_Ok|(rc!=0);
}
DISPATCHABLE_COMMAND( dbconfig 3 1 3 ){
static const struct DbConfigChoices {
const char *zName;
int op;
} aDbConfig[] = {
{ "defensive", SQLITE_DBCONFIG_DEFENSIVE },
{ "dqs_ddl", SQLITE_DBCONFIG_DQS_DDL },
{ "dqs_dml", SQLITE_DBCONFIG_DQS_DML },
{ "enable_fkey", SQLITE_DBCONFIG_ENABLE_FKEY },
{ "enable_qpsg", SQLITE_DBCONFIG_ENABLE_QPSG },
{ "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER },
{ "enable_view", SQLITE_DBCONFIG_ENABLE_VIEW },
{ "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER },
{ "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE },
{ "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT },
{ "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION },
{ "no_ckpt_on_close", SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE },
{ "reset_database", SQLITE_DBCONFIG_RESET_DATABASE },
{ "reverse_scanorder", SQLITE_DBCONFIG_REVERSE_SCANORDER },
{ "stmt_scanstatus", SQLITE_DBCONFIG_STMT_SCANSTATUS },
{ "trigger_eqp", SQLITE_DBCONFIG_TRIGGER_EQP },
{ "trusted_schema", SQLITE_DBCONFIG_TRUSTED_SCHEMA },
{ "writable_schema", SQLITE_DBCONFIG_WRITABLE_SCHEMA },
};
int ii, v;
open_db(p, 0);
for(ii=0; ii<ArraySize(aDbConfig); ii++){
if( nArg>1 && cli_strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue;
if( nArg>=3 ){
sqlite3_db_config(DBX(p), aDbConfig[ii].op, booleanValue(azArg[2]), 0);
}
sqlite3_db_config(DBX(p), aDbConfig[ii].op, -1, &v);
utf8_printf(ISS(p)->out, "%19s %s\n",
aDbConfig[ii].zName, v ? "on" : "off");
if( nArg>1 ) break;
}
if( nArg>1 && ii==ArraySize(aDbConfig) ){
*pzErr = smprintf("Error: unknown dbconfig \"%s\"\n"
"Enter \".dbconfig\" with no arguments for a list\n",
azArg[1]);
return DCR_ArgWrong;
}
return DCR_Ok;
}
DISPATCHABLE_COMMAND( dbinfo 3 1 2 ){
return shell_dbinfo_command(p, nArg, azArg);
}
/*****************
* The .dump, .echo and .eqp commands
*/
COLLECT_HELP_TEXT[
".dump ?OBJECTS? Render database content as SQL",
" Options:",
" --data-only Output only INSERT statements",
" --newlines Allow unescaped newline characters in output",
" --nosys Omit system tables (ex: \"sqlite_stat1\")",
" --preserve-rowids Include ROWID values in the output",
" --schema SCHEMA Dump table(s) from given SCHEMA",
" OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump",
" Additional LIKE patterns can be given in subsequent arguments",
".echo on|off Turn command echo on or off",
".eqp on|off|full|... Enable or disable automatic EXPLAIN QUERY PLAN",
" Other Modes:",
#ifdef SQLITE_DEBUG
" test Show raw EXPLAIN QUERY PLAN output",
" trace Like \"full\" but enable \"PRAGMA vdbe_trace\"",
#endif
" trigger Like \"full\" but also show trigger bytecode",
];
DISPATCHABLE_COMMAND( dump ? 1 2 ){
ShellInState *psi = ISS(p);
char *zLike = 0;
char *zSchema = "main";
char *zSql;
int i;
int savedShowHeader = psi->showHeader;
int savedShellFlags = psi->shellFlgs;
sstr_ptr_holder(&zLike);
ShellClearFlag(p,
SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo
|SHFLG_DumpDataOnly|SHFLG_DumpNoSys);
for(i=1; i<nArg; i++){
if( azArg[i][0]=='-' ){
const char *z = azArg[i]+1;
if( z[0]=='-' ) z++;
if( cli_strcmp(z,"preserve-rowids")==0 ){
#ifdef SQLITE_OMIT_VIRTUALTABLE
*pzErr = smprintf("The --preserve-rowids option is not compatible"
" with SQLITE_OMIT_VIRTUALTABLE\n");
release_holder();
return DCR_ArgWrong;
#else
ShellSetFlag(p, SHFLG_PreserveRowid);
#endif
}else{
if( cli_strcmp(z,"newlines")==0 ){
ShellSetFlag(p, SHFLG_Newlines);
}else if( cli_strcmp(z,"data-only")==0 ){
ShellSetFlag(p, SHFLG_DumpDataOnly);
}else if( cli_strcmp(z,"nosys")==0 ){
ShellSetFlag(p, SHFLG_DumpNoSys);
}else if( cli_strcmp(z,"schema")==0 && ++i<nArg ){
zSchema = azArg[i];
}else{
*pzErr = smprintf("Unknown option \"%s\" on \".dump\"\n", azArg[i]);
release_holder();
return DCR_ArgWrong;
}
}
}else{
/* azArg[i] contains a LIKE pattern. This ".dump" request should
** only dump data for tables for which either the table name matches
** the LIKE pattern, or the table appears to be a shadow table of
** a virtual table for which the name matches the LIKE pattern.
*/
char *zExpr = smprintf(
"name LIKE %Q ESCAPE '\\' OR EXISTS ("
" SELECT 1 FROM %w.sqlite_schema WHERE "
" name LIKE %Q ESCAPE '\\' AND"
" sql LIKE 'CREATE VIRTUAL TABLE%%' AND"
" substr(o.name, 1, length(name)+1) == (name||'_')"
")", azArg[i], zSchema, azArg[i]
);
shell_check_ooms(zExpr);
if( zLike ){
zLike = smprintf("%z OR %z", zLike, zExpr);
}else{
zLike = zExpr;
}
}
}
open_db(p, 0);
if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
/* When playing back a "dump", the content might appear in an order
** which causes immediate foreign key constraints to be violated.
** So disable foreign-key constraint enforcement to prevent problems. */
raw_printf(psi->out, "PRAGMA foreign_keys=OFF;\n");
raw_printf(psi->out, "BEGIN TRANSACTION;\n");
}
psi->writableSchema = 0;
psi->showHeader = 0;
/* Set writable_schema=ON since doing so forces SQLite to initialize
** as much of the schema as it can even if the sqlite_schema table is
** corrupt. */
sqlite3_exec(DBX(p), "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
psi->nErr = 0;
if( zLike==0 ) zLike = smprintf("true");
zSql = smprintf("SELECT name, type, sql FROM %w.sqlite_schema AS o "
"WHERE (%s) AND type=='table' AND sql NOT NULL"
" ORDER BY tbl_name='sqlite_sequence', rowid",
zSchema, zLike);
shell_check_ooms(zSql);
sstr_ptr_holder(&zSql);
run_schema_dump_query(psi,zSql);
if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
sqlite3_free(zSql);
zSql = smprintf(
"SELECT sql FROM sqlite_schema AS o "
"WHERE (%s) AND sql NOT NULL"
" AND type IN ('index','trigger','view')",
zLike
);
run_table_dump_query(psi, zSql);
}
release_holder(); /* zSql */
if( psi->writableSchema ){
raw_printf(psi->out, "PRAGMA writable_schema=OFF;\n");
psi->writableSchema = 0;
}
sqlite3_exec(DBX(p), "PRAGMA writable_schema=OFF;", 0, 0, 0);
sqlite3_exec(DBX(p), "RELEASE dump;", 0, 0, 0);
if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
raw_printf(psi->out, psi->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n");
}
psi->showHeader = savedShowHeader;
psi->shellFlgs = savedShellFlags;
release_holder(); /* zLike */
return DCR_Ok;
}
DISPATCHABLE_COMMAND( echo ? 2 2 ){
setOrClearFlag(p, SHFLG_Echo, azArg[1]);
return DCR_Ok;
}
DISPATCHABLE_COMMAND( eqp ? 0 0 ){
ShellInState *psi = ISS(p);
if( nArg==2 ){
psi->autoEQPtest = 0;
if( psi->autoEQPtrace ){
if( DBX(p) ) sqlite3_exec(DBX(p), "PRAGMA vdbe_trace=OFF;", 0, 0, 0);
psi->autoEQPtrace = 0;
}
if( cli_strcmp(azArg[1],"full")==0 ){
psi->autoEQP = AUTOEQP_full;
}else if( cli_strcmp(azArg[1],"trigger")==0 ){
psi->autoEQP = AUTOEQP_trigger;
#ifdef SQLITE_DEBUG
}else if( cli_strcmp(azArg[1],"test")==0 ){
psi->autoEQP = AUTOEQP_on;
psi->autoEQPtest = 1;
}else if( cli_strcmp(azArg[1],"trace")==0 ){
psi->autoEQP = AUTOEQP_full;
psi->autoEQPtrace = 1;
open_db(p, 0);
sqlite3_exec(DBX(p), "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0);
sqlite3_exec(DBX(p), "PRAGMA vdbe_trace=ON;", 0, 0, 0);
#endif
}else{
psi->autoEQP = (u8)booleanValue(azArg[1]);
}
}else{
return DCR_ArgWrong;
}
return DCR_Ok;
}
CONDITION_COMMAND(cease !defined(SQLITE_SHELL_FIDDLE));
CONDITION_COMMAND(exit !defined(SQLITE_SHELL_FIDDLE));
CONDITION_COMMAND(quit !defined(SQLITE_SHELL_FIDDLE));
/*****************
* The .cease, .exit and .quit commands
* These are together so that their differing effects are apparent.
*/
CONDITION_COMMAND(cease defined(SHELL_CEASE));
COLLECT_HELP_TEXT[
".cease ?CODE? Cease shell operation, with optional return code",
" Return code defaults to 0, otherwise is limited to non-signal values",
".exit ?CODE? Exit shell program, maybe with return-code CODE",
" Exit immediately if CODE != 0, else functions as \"quit this input\"",
".quit Stop interpreting input stream, done if primary.",
];
DISPATCHABLE_COMMAND( cease 4 1 2 ){
/* .cease effects an exit, always. Only the exit code is variable. */
int rc = 0;
if( nArg>1 ){
rc = (int)integerValue(azArg[1]);
if( rc>0x7f ) rc = 0x7f;
}
p->shellAbruptExit = 0x100|rc;
return DCR_Exit;
}
DISPATCHABLE_COMMAND( exit 3 1 0 ){
/* .exit acts like .quit with no argument or a zero argument,
* only returning. With a non-zero argument, it effects an exit. */
int rc;
if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ){
rc &= 0xff; /* Mimic effect of legacy call to exit(). */
#ifdef SHELL_EXIT_EXITS_PROCESS
terminate_actions();
exit(rc);
#else
p->shellAbruptExit = 0x100|rc;
#endif
}
return DCR_Return;
}
DISPATCHABLE_COMMAND( quit 1 1 0 ){
/* .quit would be more aptly named .return, as it does nothing more. */
return DCR_Return;
}
/*****************
* The .expert and .explain commands
*/
CONDITION_COMMAND( expert !defined(SQLITE_OMIT_VIRTUALTABLE) );
COLLECT_HELP_TEXT[
".expert Suggest indexes for queries",
".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto",
];
DISPATCHABLE_COMMAND( expert ? 1 1 ){
ShellInState *psi = ISS(p);
int rv = DCR_Ok;
char *zErr = 0;
int i;
int iSample = 0;
if( psi->bSafeMode ) return DCR_AbortError;
assert( psi->expert.pExpert==0 );
memset(&psi->expert, 0, sizeof(ExpertInfo));
open_db(p, 0);
for(i=1; i<nArg; i++){
char *z = azArg[i];
int n;
if( z[0]=='-' && z[1]=='-' ) z++;
n = strlen30(z);
if( n>=2 && 0==cli_strncmp(z, "-verbose", n) ){
psi->expert.bVerbose = 1;
}
else if( n>=2 && 0==cli_strncmp(z, "-sample", n) ){
if( i==(nArg-1) ){
return DCR_Unpaired|i;
}else{
iSample = (int)integerValue(azArg[++i]);
if( iSample<0 || iSample>100 ){
*pzErr = smprintf("value out of range: %s\n", azArg[i]);
return DCR_ArgWrong|i;
}
}
}
else{
return DCR_Unknown|i;
}
}
psi->expert.pExpert = sqlite3_expert_new(DBI(psi), &zErr);
if( psi->expert.pExpert==0 ){
*pzErr = smprintf("sqlite3_expert_new: %s\n",
zErr ? zErr : "out of memory");
return DCR_Error;
}else{
sqlite3_expert_config(psi->expert.pExpert, EXPERT_CONFIG_SAMPLE, iSample);
}
return DCR_Ok;
}
DISPATCHABLE_COMMAND( explain ? 1 2 ){
/* The ".explain" command is automatic now. It is largely
** pointless, retained purely for backwards compatibility */
ShellInState *psi = ISS(p);
int val = 1;
if( nArg>1 ){
if( cli_strcmp(azArg[1],"auto")==0 ){
val = 99;
}else{
val = booleanValue(azArg[1]);
}
}
if( val==1 && psi->mode!=MODE_Explain ){
psi->normalMode = psi->mode;
psi->mode = MODE_Explain;
psi->autoExplain = 0;
}else if( val==0 ){
if( psi->mode==MODE_Explain ) psi->mode = psi->normalMode;
psi->autoExplain = 0;
}else if( val==99 ){
if( psi->mode==MODE_Explain ) psi->mode = psi->normalMode;
psi->autoExplain = 1;
}
return DCR_Ok;
}
/*****************
* The .excel, .once and .output commands
* These share much implementation, so they stick together.
*/
CONDITION_COMMAND(excel !defined(SQLITE_SHELL_FIDDLE));
CONDITION_COMMAND(once !defined(SQLITE_SHELL_FIDDLE));
CONDITION_COMMAND(output !defined(SQLITE_SHELL_FIDDLE));
COLLECT_HELP_TEXT[
".excel Display the output of next command in spreadsheet",
" --bom Prefix the file with a UTF8 byte-order mark",
".once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE",
" If FILE begins with '|' then open it as a command to be piped into.",
" Options:",
" --bom Prefix output with a UTF8 byte-order mark",
" -e Send output to the system text editor",
" -x Send output as CSV to a spreadsheet (same as \".excel\")",
".output ?FILE? Send output to FILE or stdout if FILE is omitted",
" If FILE begins with '|' then open it as a command to be piped into.",
" Options:",
" --bom Prefix output with a UTF8 byte-order mark",
" -e Send output to the system text editor",
" -x Send output as CSV to a spreadsheet (same as \".excel\")",
];
#ifndef SQLITE_SHELL_FIDDLE
/* Shared implementation of .excel, .once and .output */
static DotCmdRC outputRedirs(char *azArg[], int nArg,
ShellInState *psi, char **pzErr,
int bOnce, int eMode){
/* bOnce => 0: .output, 1: .once, 2: .excel */
/* eMode => 'x' for excel, else 0 */
int rc = 0;
char *zFile = 0;
u8 bTxtMode = 0;
u8 bPutBOM = 0;
int i;
static unsigned const char zBOM[4] = {0xef,0xbb,0xbf,0};
sstr_ptr_holder(&zFile);
if( psi->bSafeMode ) return DCR_AbortError;
for(i=1; i<nArg; i++){
char *z = azArg[i];
if( z[0]=='-' ){
if( z[1]=='-' ) z++;
if( cli_strcmp(z,"-bom")==0 ){
bPutBOM = 1;
}else if( bOnce!=2 && cli_strcmp(z,"-x")==0 ){
eMode = 'x'; /* spreadsheet */
}else if( bOnce!=2 && cli_strcmp(z,"-e")==0 ){
eMode = 'e'; /* text editor */
}else{
return DCR_Unknown|i;
}
}else if( zFile==0 && eMode!='e' && eMode!='x' ){
zFile = smprintf("%s", z);
shell_check_ooms(zFile);
if( zFile[0]=='|' ){
while( i+1<nArg ){
zFile = smprintf("%z %s", zFile, azArg[++i]);
shell_check_ooms(zFile);
}
break;
}
}else{
release_holder();
return DCR_TooMany|i;
}
}
if( zFile==0 ){
zFile = smprintf("stdout");
shell_check_ooms(zFile);
}
if( bOnce ){
psi->outCount = 2;
}else{
psi->outCount = 0;
}
output_reset(psi);
#ifndef SQLITE_NOHAVE_SYSTEM
if( eMode=='e' || eMode=='x' ){
psi->doXdgOpen = 1;
outputModePush(psi);
if( eMode=='x' ){
/* spreadsheet mode. Output as CSV. */
newTempFile(psi, "csv");
psi->shellFlgs &= ~SHFLG_Echo;
psi->mode = MODE_Csv;
sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator, SEP_Comma);
sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, SEP_CrLf);
}else{
/* text editor mode */
newTempFile(psi, "txt");
bTxtMode = 1;
}
sqlite3_free(zFile);
zFile = smprintf("%s", psi->zTempFile);
}
#endif /* SQLITE_NOHAVE_SYSTEM */
shell_check_ooms(zFile);
if( zFile[0]=='|' ){
#ifdef SQLITE_OMIT_POPEN
*pzErr = smprintf("pipes are not supported in this OS\n");
rc = 1;
psi->out = STD_OUT;
#else
psi->out = popen(zFile + 1, "w");
if( psi->out==0 ){
*pzErr = smprintf("cannot open pipe \"%s\"\n", zFile + 1);
psi->out = STD_OUT;
rc = 1;
}else{
if( bPutBOM ) fwrite(zBOM, 1, 3, psi->out);
sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile);
}
#endif
}else{
psi->out = output_file_open(zFile, bTxtMode);
if( psi->out==0 ){
if( cli_strcmp(zFile,"off")!=0 ){
*pzErr = smprintf("cannot write to \"%s\"\n", zFile);
}
psi->out = STD_OUT;
rc = 1;
} else {
if( bPutBOM ) fwrite(zBOM, 1, 3, psi->out);
sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile);
}
}
release_holder();
return DCR_Ok|rc;
}
#endif /* !defined(SQLITE_SHELL_FIDDLE)*/
DISPATCHABLE_COMMAND( excel ? 1 2 ){
return outputRedirs(azArg, nArg, ISS(p), pzErr, 2, 'x');
}
DISPATCHABLE_COMMAND( once ? 1 6 ){
return outputRedirs(azArg, nArg, ISS(p), pzErr, 1, 0);
}
DISPATCHABLE_COMMAND( output ? 1 6 ){
return outputRedirs(azArg, nArg, ISS(p), pzErr, 0, 0);
}
/*****************
* The .filectrl and fullschema commands
*/
COLLECT_HELP_TEXT[
".filectrl CMD ... Run various sqlite3_file_control() operations",
" --schema SCHEMA Use SCHEMA instead of \"main\"",
" --help Show CMD details",
".fullschema ?--indent? Show schema and the content of sqlite_stat tables",
];
DISPATCHABLE_COMMAND( filectrl ? 2 0 ){
static const struct {
const char *zCtrlName; /* Name of a test-control option */
int ctrlCode; /* Integer code for that option */
const char *zUsage; /* Usage notes */
} aCtrl[] = {
{ "chunk_size", SQLITE_FCNTL_CHUNK_SIZE, "SIZE" },
{ "data_version", SQLITE_FCNTL_DATA_VERSION, "" },
{ "has_moved", SQLITE_FCNTL_HAS_MOVED, "" },
{ "lock_timeout", SQLITE_FCNTL_LOCK_TIMEOUT, "MILLISEC" },
{ "persist_wal", SQLITE_FCNTL_PERSIST_WAL, "[BOOLEAN]" },
/* { "pragma", SQLITE_FCNTL_PRAGMA, "NAME ARG" },*/
{ "psow", SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]" },
{ "reserve_bytes", SQLITE_FCNTL_RESERVE_BYTES, "[N]" },
{ "size_limit", SQLITE_FCNTL_SIZE_LIMIT, "[LIMIT]" },
{ "tempfilename", SQLITE_FCNTL_TEMPFILENAME, "" },
/* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY, "COUNT DELAY" },*/
};
ShellInState *psi = ISS(p);
int filectrl = -1;
int iCtrl = -1;
sqlite3_int64 iRes = 0; /* Integer result to display if rc2==1 */
int isOk = 0; /* 0: usage 1: %lld 2: no-result */
int n2, i;
const char *zCmd = 0;
const char *zSchema = 0;
open_db(p, 0);
zCmd = nArg>=2 ? azArg[1] : "help";
if( zCmd[0]=='-'
&& (cli_strcmp(zCmd,"--schema")==0 || cli_strcmp(zCmd,"-schema")==0)
&& nArg>=4
){
zSchema = azArg[2];
for(i=3; i<nArg; i++) azArg[i-2] = azArg[i];
nArg -= 2;
zCmd = azArg[1];
}
/* The argument can optionally begin with "-" or "--" */
if( zCmd[0]=='-' && zCmd[1] ){
zCmd++;
if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
}
/* --help lists all file-controls */
if( cli_strcmp(zCmd,"help")==0 ){
utf8_printf(psi->out, "Available file-controls:\n");
for(i=0; i<ArraySize(aCtrl); i++){
utf8_printf(psi->out, " .filectrl %s %s\n",
aCtrl[i].zCtrlName, aCtrl[i].zUsage);
}
return DCR_Error;
}
/* Convert filectrl text option to value. Allow any
** unique prefix of the option name, or a numerical value. */
n2 = strlen30(zCmd);
for(i=0; i<ArraySize(aCtrl); i++){
if( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
if( filectrl<0 ){
filectrl = aCtrl[i].ctrlCode;
iCtrl = i;
}else{
*pzErr = smprintf("ambiguous file-control: \"%s\"\n"
"Use \".filectrl --help\" for help\n", zCmd);
return DCR_ArgWrong;
}
}
}
if( filectrl<0 ){
*pzErr = smprintf("unknown file-control: %s\n"
"Use \".filectrl --help\" for help\n", zCmd);
return DCR_ArgWrong;
}else{
switch(filectrl){
case SQLITE_FCNTL_SIZE_LIMIT: {
if( nArg!=2 && nArg!=3 ) break;
iRes = nArg==3 ? integerValue(azArg[2]) : -1;
sqlite3_file_control(DBX(p), zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes);
isOk = 1;
break;
}
case SQLITE_FCNTL_LOCK_TIMEOUT:
case SQLITE_FCNTL_CHUNK_SIZE: {
int x;
if( nArg!=3 ) break;
x = (int)integerValue(azArg[2]);
sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
isOk = 2;
break;
}
case SQLITE_FCNTL_PERSIST_WAL:
case SQLITE_FCNTL_POWERSAFE_OVERWRITE: {
int x;
if( nArg!=2 && nArg!=3 ) break;
x = nArg==3 ? booleanValue(azArg[2]) : -1;
sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
iRes = x;
isOk = 1;
break;
}
case SQLITE_FCNTL_DATA_VERSION:
case SQLITE_FCNTL_HAS_MOVED: {
int x;
if( nArg!=2 ) break;
sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
iRes = x;
isOk = 1;
break;
}
case SQLITE_FCNTL_TEMPFILENAME: {
char *z = 0;
if( nArg!=2 ) break;
sqlite3_file_control(DBX(p), zSchema, filectrl, &z);
if( z ){
utf8_printf(psi->out, "%s\n", z);
sqlite3_free(z);
}
isOk = 2;
break;
}
case SQLITE_FCNTL_RESERVE_BYTES: {
int x;
if( nArg>=3 ){
x = atoi(azArg[2]);
sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
}
x = -1;
sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
utf8_printf(psi->out,"%d\n", x);
isOk = 2;
break;
}
}
}
if( isOk==0 && iCtrl>=0 ){
*pzErr = smprintf("Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
return DCR_CmdErred;
}else if( isOk==1 ){
char zBuf[21];
sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes);
raw_printf(psi->out, "%s\n", zBuf);
}
return DCR_Ok;
}
static void modePopper(ShellInState *psi){
outputModePop(psi);
}
DISPATCHABLE_COMMAND( fullschema ? 1 2 ){
int rc;
int doStats = 0;
ShellInState *psi = ISS(p);
u8 useMode = MODE_Semi;
AnyResourceHolder arh = {psi, (GenericFreer)modePopper};
if( nArg==2 && optionMatch(azArg[1], "indent") ){
useMode = MODE_Pretty;
nArg = 1;
}
if( nArg!=1 ){
return DCR_TooMany|1;
}
outputModePush(psi); /* Can fail to return due to OOM. */
any_ref_holder(&arh);
psi->showHeader = 0;
psi->cMode = psi->mode = useMode;
open_db(p, 0);
rc = s3_exec_noom(DBX(p),
"SELECT sql FROM"
" (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x"
" FROM sqlite_schema UNION ALL"
" SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) "
"WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' "
"ORDER BY x",
callback, p, 0
);
if( rc==SQLITE_OK ){
sqlite3_stmt *pStmt;
rc = s3_prepare_v2_noom(p->dbUser,
"SELECT rowid FROM sqlite_schema"
" WHERE name GLOB 'sqlite_stat[134]'",
-1, &pStmt, 0);
stmt_holder(pStmt);
doStats = s3_step_noom(pStmt)==SQLITE_ROW;
release_holder();
}
if( doStats==0 ){
raw_printf(psi->out, "/* No STAT tables available */\n");
}else{
const char *zOldDestTable = p->zDestTable;
raw_printf(psi->out, "ANALYZE sqlite_schema;\n");
psi->cMode = psi->mode = MODE_Insert;
p->zDestTable = "sqlite_stat1";
shell_exec(p, "SELECT * FROM sqlite_stat1", 0);
p->zDestTable = "sqlite_stat4";
shell_exec(p, "SELECT * FROM sqlite_stat4", 0);
raw_printf(psi->out, "ANALYZE sqlite_schema;\n");
p->zDestTable = zOldDestTable;
}
release_holder(); /* Restore shell state */
return rc > 0;
}
/*****************
* The .headers command
*/
COLLECT_HELP_TEXT[
".headers on|off Turn display of headers on or off",
];
DISPATCHABLE_COMMAND( headers 6 2 2 ){
ISS(p)->showHeader = booleanValue(azArg[1]);
ISS(p)->shellFlgs |= SHFLG_HeaderSet;
return DCR_Ok;
}
/*****************
* The .help command
*/
/* This literal's value AND address are used for help's workings. */
static const char *zHelpAll = "-all";
COLLECT_HELP_TEXT[
".help ?PATTERN?|?-all? Show help for PATTERN or everything, or summarize",
" Repeat -all to see undocumented commands",
];
DISPATCHABLE_COMMAND( help 3 1 3 ){
const char *zPat = 0;
FILE *out = ISS(p)->out;
if( nArg>1 ){
char *z = azArg[1];
if( (nArg==2 && azArg[1][0]=='0' && azArg[1][1]==0)
|| (nArg==3 && cli_strcmp(z, zHelpAll)==0
&& cli_strcmp(azArg[2], zHelpAll)==0) ){
/* Show the undocumented command help */
zPat = zHelpAll;
}else if( cli_strcmp(z,"-a")==0 || optionMatch(z, "all") ){
zPat = "";
}else{
zPat = z;
}
}
if( showHelp(out, zPat, p)==0 && nArg>1 ){
utf8_printf(out, "Nothing matches '%s'\n", azArg[1]);
}
/* Help pleas never fail! */
return DCR_Ok;
}
CONDITION_COMMAND(import !defined(SQLITE_SHELL_FIDDLE));
/*****************
* The .import command
*/
COLLECT_HELP_TEXT[
".import FILE TABLE Import data from FILE into TABLE",
" Options:",
" --ascii Use \\037 and \\036 as column and row separators",
" --csv Use , and \\n as column and row separators",
" --skip N Skip the first N rows of input",
" --schema S Target table to be S.TABLE",
" -v \"Verbose\" - increase auxiliary output",
" Notes:",
" * If TABLE does not exist, it is created. The first row of input",
" determines the column names.",
" * If neither --csv or --ascii are used, the input mode is derived",
" from the \".mode\" output mode",
" * If FILE begins with \"|\" then it is a command that generates the",
" input text.",
];
DISPATCHABLE_COMMAND( import ? 3 7 ){
char *zTable = 0; /* Insert data into this table */
char *zSchema = 0; /* within this schema (may default to "main") */
char *zFile = 0; /* Name of file to extra content from */
sqlite3_stmt *pStmt = NULL; /* A statement */
int nCol; /* Number of columns in the table */
int nByte; /* Number of bytes in an SQL string */
int i, j; /* Loop counters */
int needCommit; /* True to COMMIT or ROLLBACK at end */
int nSep; /* Number of bytes in psi->colSeparator[] */
char *zSql = 0; /* An SQL statement */
char *zFullTabName = 0; /* Table name with schema if applicable */
ImportCtx sCtx = {0}; /* Reader context */
AnyResourceHolder arh = { &sCtx, (GenericFreer)import_cleanup };
char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
int eVerbose = 0; /* Larger for more console output */
int nSkip = 0; /* Initial lines to skip */
int useOutputMode = 1; /* Use output mode to determine separators */
FILE *out = ISS(p)->out; /* output stream */
char *zCreate = 0; /* CREATE TABLE statement text */
ShellInState *psi = ISS(p);
ResourceMark mark = holder_mark();
int rc = 0;
if(psi->bSafeMode) return DCR_AbortError;
memset(&sCtx, 0, sizeof(sCtx));
if( psi->mode==MODE_Ascii ){
xRead = ascii_read_one_field;
}else{
xRead = csv_read_one_field;
}
for(i=1; i<nArg; i++){
char *z = azArg[i];
if( z[0]=='-' && z[1]=='-' ) z++;
if( z[0]!='-' ){
if( zFile==0 ){
zFile = z;
}else if( zTable==0 ){
zTable = z;
}else{
return DCR_TooMany|i;
}
}else if( cli_strcmp(z,"-v")==0 ){
eVerbose++;
}else if( cli_strcmp(z,"-schema")==0 && i<nArg-1 ){
zSchema = azArg[++i];
}else if( cli_strcmp(z,"-skip")==0 && i<nArg-1 ){
nSkip = integerValue(azArg[++i]);
}else if( cli_strcmp(z,"-ascii")==0 ){
sCtx.cColSep = SEP_Unit[0];
sCtx.cRowSep = SEP_Record[0];
xRead = ascii_read_one_field;
useOutputMode = 0;
}else if( cli_strcmp(z,"-csv")==0 ){
sCtx.cColSep = ',';
sCtx.cRowSep = '\n';
xRead = csv_read_one_field;
useOutputMode = 0;
}else{
return DCR_Unknown|i;
}
}
if( zTable==0 ){
*pzErr = smprintf("missing %s argument.\n", zFile==0 ? "FILE" : "TABLE");
return DCR_Missing;
}
open_db(p, 0);
if( useOutputMode ){
const char *zYap = 0;
/* If neither the --csv or --ascii options are specified, then set
** the column and row separator characters from the output mode. */
nSep = strlen30(psi->colSeparator);
if( nSep==0 ){
zYap = "non-null column separator required for import";
}
if( nSep>1 ){
zYap = "multi-character or multi-byte column separators"
" not allowed for import";
}
nSep = strlen30(psi->rowSeparator);
if( nSep==0 ){
zYap = "non-null row separator required for import";
}
if( zYap!=0 ){
*pzErr = smprintf("%s\n", zYap);
return DCR_Error;
}
if( nSep==2 && psi->mode==MODE_Csv
&& cli_strcmp(psi->rowSeparator,SEP_CrLf)==0 ){
/* When importing CSV (only), if the row separator is set to the
** default output row separator, change it to the default input
** row separator. This avoids having to maintain different input
** and output row separators. */
sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, SEP_Row);
nSep = strlen30(psi->rowSeparator);
}
if( nSep>1 ){
*pzErr
= smprintf("multi-character row separators not allowed for import\n");
return DCR_Error;
}
sCtx.cColSep = (u8)psi->colSeparator[0];
sCtx.cRowSep = (u8)psi->rowSeparator[0];
}
sCtx.zFile = zFile;
sCtx.nLine = 1;
if( sCtx.zFile[0]=='|' ){
#ifdef SQLITE_OMIT_POPEN
*pzErr = smprintf("pipes are not supported in this OS\n");
return DCR_Error;
#else
sCtx.in = popen(sCtx.zFile+1, "r");
sCtx.zFile = "<pipe>";
sCtx.xCloser = pclose;
#endif
}else{
sCtx.in = fopen(sCtx.zFile, "rb");
sCtx.xCloser = fclose;
}
if( sCtx.in==0 ){
*pzErr = smprintf("cannot open \"%s\"\n", zFile);
return DCR_Error;
}
/* Here and below, resources must be freed before exit. */
any_ref_holder(&arh);
sCtx.z = sqlite3_malloc64(120);
shell_check_ooms(sCtx.z);
if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
char zSep[2];
zSep[1] = 0;
zSep[0] = sCtx.cColSep;
utf8_printf(out, "Column separator ");
output_c_string(out, zSep);
utf8_printf(out, ", row separator ");
zSep[0] = sCtx.cRowSep;
output_c_string(out, zSep);
utf8_printf(out, "\n");
}
while( (nSkip--)>0 ){
while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
}
if( zSchema!=0 ){
zFullTabName = smprintf("\"%w\".\"%w\"", zSchema, zTable);
}else{
zFullTabName = smprintf("\"%w\"", zTable);
}
shell_check_ooms(zFullTabName);
sstr_ptr_holder(&zFullTabName);
zSql = smprintf("SELECT * FROM %s", zFullTabName);
shell_check_ooms(zSql);
sstr_ptr_holder(&zSql);
nByte = strlen30(zSql);
rc = s3_prepare_v2_noom(DBX(p), zSql, -1, &pStmt, 0);
stmt_ptr_holder(&pStmt);
import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */
if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(DBX(p)))==0 ){
zCreate = smprintf("CREATE TABLE %s", zFullTabName);
sqlite3 *dbCols = 0;
char *zRenames = 0;
char *zColDefs;
shell_check_ooms(zCreate);
sstr_ptr_holder(&zCreate); /* +1 */
sstr_ptr_holder(&zRenames); /* +2 */
sstr_ptr_holder(&zColDefs); /* +3 */
conn_ptr_holder(&dbCols);
while( xRead(&sCtx) ){
zAutoColumn(sCtx.z, &dbCols, 0);
if( sCtx.cTerm!=sCtx.cColSep ) break;
}
zColDefs = zAutoColumn(0, &dbCols, &zRenames);
if( zRenames!=0 ){
FILE *fh = INSOURCE_IS_INTERACTIVE(psi->pInSource)? out : STD_ERR;
utf8_printf(fh, "Columns renamed during .import %s due to duplicates:\n"
"%s\n", sCtx.zFile, zRenames);
}
assert(dbCols==0);
drop_holder(); /* dbCols */
if( zColDefs==0 ){
*pzErr = smprintf("%s: empty file\n", sCtx.zFile);
import_fail: /* entry from outer blocks */
RESOURCE_FREE(mark);
return DCR_Error;
}
zCreate = smprintf("%z%z\n", zCreate, zColDefs);
zColDefs = 0;
shell_check_ooms(zCreate);
if( eVerbose>=1 ){
utf8_printf(out, "%s\n", zCreate);
}
rc = s3_exec_noom(DBX(p), zCreate, 0, 0, 0);
if( rc ){
*pzErr = smprintf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(DBX(p)));
goto import_fail;
}
rc = s3_prepare_v2_noom(DBX(p), zSql, -1, &pStmt, 0);
}
if( rc ){
*pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
goto import_fail;
}
nCol = sqlite3_column_count(pStmt);
sqlite3_finalize(pStmt);
pStmt = 0;
if( nCol==0 ) return DCR_Ok; /* no columns, no error */
sqlite3_free(zSql);
zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 );
shell_check_ooms(zSql);
sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName);
j = strlen30(zSql);
for(i=1; i<nCol; i++){
zSql[j++] = ',';
zSql[j++] = '?';
}
zSql[j++] = ')';
zSql[j] = 0;
if( eVerbose>=2 ){
utf8_printf(psi->out, "Insert using: %s\n", zSql);
}
rc = s3_prepare_v2_noom(DBX(p), zSql, -1, &pStmt, 0);
if( rc ){
*pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
goto import_fail;
}
needCommit = sqlite3_get_autocommit(DBX(p));
if( needCommit ) sqlite3_exec(DBX(p), "BEGIN", 0, 0, 0);
do{
int startLine = sCtx.nLine;
for(i=0; i<nCol; i++){
char *z = xRead(&sCtx);
/*
** Did we reach end-of-file before finding any columns?
** If so, stop instead of NULL filling the remaining columns.
*/
if( z==0 && i==0 ) break;
/*
** Did we reach end-of-file OR end-of-line before finding any
** columns in ASCII mode? If so, stop instead of NULL filling
** the remaining columns.
*/
if( psi->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
utf8_printf(STD_ERR, "%s:%d: expected %d columns but found %d - "
"filling the rest with NULL\n",
sCtx.zFile, startLine, nCol, i+1);
i += 2;
while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; }
}
}
if( sCtx.cTerm==sCtx.cColSep ){
do{
xRead(&sCtx);
i++;
}while( sCtx.cTerm==sCtx.cColSep );
utf8_printf(STD_ERR, "%s:%d: expected %d columns but found %d - "
"extras ignored\n",
sCtx.zFile, startLine, nCol, i);
}
if( i>=nCol ){
sqlite3_step(pStmt);
rc = sqlite3_reset(pStmt);
if( rc!=SQLITE_OK ){
utf8_printf(STD_ERR, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
startLine, sqlite3_errmsg(DBX(p)));
sCtx.nErr++;
}else{
sCtx.nRow++;
}
}
}while( sCtx.cTerm!=EOF );
if( needCommit ) sqlite3_exec(DBX(p), "COMMIT", 0, 0, 0);
if( eVerbose>0 ){
utf8_printf(out,
"Added %d rows with %d errors using %d lines of input\n",
sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
}
RESOURCE_FREE(mark);
return DCR_Ok|(sCtx.nErr>0);
}
/*****************
* The .keyword command
*/
CONDITION_COMMAND( keyword !defined(NO_KEYWORD_COMMAND) );
COLLECT_HELP_TEXT[
".keyword ?KW? List keywords, or say whether KW is one.",
];
DISPATCHABLE_COMMAND( keyword ? 1 2 ){
FILE *out = ISS(p)->out;
if( nArg<2 ){
int i = 0;
int nk = sqlite3_keyword_count();
int nCol = 0;
int szKW;
while( i<nk ){
const char *zKW = 0;
if( SQLITE_OK==sqlite3_keyword_name(i++, &zKW, &szKW) ){
char kwBuf[50];
if( szKW < sizeof(kwBuf) ){
const char *zSep = " ";
if( (nCol += (1+szKW))>75){
zSep = "\n";
nCol = 0;
}
memcpy(kwBuf, zKW, szKW);
kwBuf[szKW] = 0;
utf8_printf(out, "%s%s", kwBuf, zSep);
}
}
}
if( nCol>0 ) utf8_printf(out, "\n");
}else{
int szKW = strlen30(azArg[1]);
int isKeyword = sqlite3_keyword_check(azArg[1], szKW);
utf8_printf(out, "%s is%s a keyword\n",
azArg[1], (isKeyword)? "" : " not");
}
return DCR_Ok;
}
/*****************
* The .imposter, .iotrace, .limit, .lint and .log commands
*/
#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE)
# define LOAD_ENABLE 1
#else
# define LOAD_ENABLE 0
#endif
CONDITION_COMMAND( imposter !defined(SQLITE_OMIT_TEST_CONTROL) );
CONDITION_COMMAND( iotrace defined(SQLITE_ENABLE_IOTRACE) );
CONDITION_COMMAND( load LOAD_ENABLE );
COLLECT_HELP_TEXT[
",imposter INDEX TABLE Create imposter table TABLE on index INDEX",
",iotrace FILE Enable I/O diagnostic logging to FILE",
".limit ?LIMIT? ?VAL? Display or change the value of an SQLITE_LIMIT",
".lint OPTIONS Report potential schema issues.",
" Options:",
" fkey-indexes Find missing foreign key indexes",
];
COLLECT_HELP_TEXT[
#if !defined(SQLITE_SHELL_FIDDLE)
".log FILE|on|off Turn logging on or off. FILE can be stderr/stdout",
#else
".log on|off Turn logging on or off.",
#endif
];
DISPATCHABLE_COMMAND( imposter ? 3 3 ){
int rc = 0;
char *zSql = 0;
char *zCollist = 0;
sqlite3_stmt *pStmt = 0;
sqlite3 *db;
int tnum = 0;
int isWO = 0; /* True if making an imposter of a WITHOUT ROWID table */
int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */
int i;
ResourceMark mark = holder_mark();
if( !ShellHasFlag(p,SHFLG_TestingMode) ){
utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n",
"imposter");
return DCR_Error;
}
if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){
*pzErr = smprintf("Usage: .imposter INDEX IMPOSTER\n"
" .imposter off\n");
/* Also allowed, but not documented:
**
** .imposter TABLE IMPOSTER
**
** where TABLE is a WITHOUT ROWID table. In that case, the
** imposter is another WITHOUT ROWID table with the columns in
** storage order. */
return DCR_SayUsage;
}
db = open_db(p, 0);
if( nArg==2 ){
sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 1);
return DCR_Ok;
}
sstr_ptr_holder(&zSql);
zSql = smprintf("SELECT rootpage, 0 FROM sqlite_schema"
" WHERE name='%q' AND type='index'"
"UNION ALL "
"SELECT rootpage, 1 FROM sqlite_schema"
" WHERE name='%q' AND type='table'"
" AND sql LIKE '%%without%%rowid%%'",
azArg[1], azArg[1]);
rc = s3_prep_noom_free(db, &zSql, &pStmt);
if( rc!=SQLITE_OK ){
release_holder();
return DCR_Error;
}
stmt_ptr_holder(&pStmt);
if( s3_step_noom(pStmt)==SQLITE_ROW ){
tnum = sqlite3_column_int(pStmt, 0);
isWO = sqlite3_column_int(pStmt, 1);
}
zSql = smprintf("PRAGMA index_xinfo='%q'", azArg[1]);
sqlite3_finalize(pStmt);
pStmt = 0;
rc = s3_prep_noom_free(db, &zSql, &pStmt);
i = 0;
sstr_ptr_holder(&zCollist);
while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
char zLabel[20];
const char *zCol = (const char*)sqlite3_column_text(pStmt,2);
i++;
if( zCol==0 ){
if( sqlite3_column_int(pStmt,1)==-1 ){
zCol = "_ROWID_";
}else{
sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i);
zCol = zLabel;
}
}
if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){
lenPK = (int)strlen(zCollist);
}
if( zCollist==0 ){
zCollist = smprintf("\"%w\"", zCol);
}else{
zCollist = smprintf("%z,\"%w\"", zCollist, zCol);
}
}
if( i==0 || tnum==0 ){
*pzErr = smprintf("no such index: \"%s\"\n", azArg[1]);
RESOURCE_FREE(mark);
return DCR_Error;
}
if( lenPK==0 ) lenPK = 100000;
zSql = smprintf("CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))"
"WITHOUT ROWID", azArg[2], zCollist, lenPK, zCollist);
shell_check_ooms(zSql);
rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 1, tnum);
if( rc==SQLITE_OK ){
rc = s3_exec_noom(db, zSql, 0, 0, 0);
sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 0);
if( rc ){
*pzErr = smprintf("Error in [%s]: %s\n", zSql, sqlite3_errmsg(db));
}else{
utf8_printf(STD_OUT, "%s;\n", zSql);
raw_printf(STD_OUT, "WARNING: "
"writing to an imposter table will corrupt the \"%s\" %s!\n",
azArg[1], isWO ? "table" : "index"
);
}
}else{
*pzErr = smprintf("SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
}
RESOURCE_FREE(mark);
return DCR_Ok|(rc != 0);
}
DISPATCHABLE_COMMAND( iotrace ? 2 2 ){
SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...);
if( iotrace && iotrace!=STD_OUT ) fclose(iotrace);
iotrace = 0;
if( nArg<2 ){
sqlite3IoTrace = 0;
}else if( cli_strcmp(azArg[1], "-")==0 ){
sqlite3IoTrace = iotracePrintf;
iotrace = STD_OUT;
}else{
iotrace = fopen(azArg[1], "w");
if( iotrace==0 ){
*pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
sqlite3IoTrace = 0;
return DCR_Error;
}else{
sqlite3IoTrace = iotracePrintf;
}
}
return DCR_Ok;
}
/*****************
* The .limits and .load commands
*/
COLLECT_HELP_TEXT[
",limits ?LIMIT_NAME? Display limit selected by its name, or all limits",
".load FILE ?ENTRY? Load a SQLite extension library",
" If ENTRY is provided, the entry point \"sqlite_ENTRY_init\" is called.",
" Otherwise, the entry point name is derived from the FILE's name.",
];
DISPATCHABLE_COMMAND( limits 5 1 3 ){
static const struct {
const char *zLimitName; /* Name of a limit */
int limitCode; /* Integer code for that limit */
} aLimit[] = {
{ "length", SQLITE_LIMIT_LENGTH },
{ "sql_length", SQLITE_LIMIT_SQL_LENGTH },
{ "column", SQLITE_LIMIT_COLUMN },
{ "expr_depth", SQLITE_LIMIT_EXPR_DEPTH },
{ "compound_select", SQLITE_LIMIT_COMPOUND_SELECT },
{ "vdbe_op", SQLITE_LIMIT_VDBE_OP },
{ "function_arg", SQLITE_LIMIT_FUNCTION_ARG },
{ "attached", SQLITE_LIMIT_ATTACHED },
{ "like_pattern_length", SQLITE_LIMIT_LIKE_PATTERN_LENGTH },
{ "variable_number", SQLITE_LIMIT_VARIABLE_NUMBER },
{ "trigger_depth", SQLITE_LIMIT_TRIGGER_DEPTH },
{ "worker_threads", SQLITE_LIMIT_WORKER_THREADS },
};
int i, n2;
open_db(p, 0);
if( nArg==1 ){
for(i=0; i<ArraySize(aLimit); i++){
fprintf(STD_OUT, "%20s %d\n", aLimit[i].zLimitName,
sqlite3_limit(DBX(p), aLimit[i].limitCode, -1));
}
}else if( nArg>3 ){
return DCR_TooMany;
}else{
int iLimit = -1;
n2 = strlen30(azArg[1]);
for(i=0; i<ArraySize(aLimit); i++){
if( sqlite3_strnicmp(aLimit[i].zLimitName, azArg[1], n2)==0 ){
if( iLimit<0 ){
iLimit = i;
}else{
*pzErr = smprintf("ambiguous limit: \"%s\"\n", azArg[1]);
return DCR_Error;
}
}
}
if( iLimit<0 ){
*pzErr = smprintf("unknown limit: \"%s\"\n"
"enter \".limits\" with no arguments for a list.\n",
azArg[1]);
return DCR_ArgWrong;
}
if( nArg==3 ){
sqlite3_limit(DBX(p), aLimit[iLimit].limitCode,
(int)integerValue(azArg[2]));
}
fprintf(STD_OUT, "%20s %d\n", aLimit[iLimit].zLimitName,
sqlite3_limit(DBX(p), aLimit[iLimit].limitCode, -1));
}
return DCR_Ok;
}
DISPATCHABLE_COMMAND( lint 3 1 0 ){
sqlite3 *db; /* Database handle to query "main" db of */
FILE *out = ISS(p)->out; /* Stream to write non-error output to */
int bVerbose = 0; /* If -verbose is present */
int bGroupByParent = 0; /* If -groupbyparent is present */
int i; /* To iterate through azArg[] */
const char *zIndent = ""; /* How much to indent CREATE INDEX by */
int rc; /* Return code */
sqlite3_stmt *pSql = 0; /* Compiled version of SQL statement below */
ResourceMark mark = holder_mark();
i = (nArg>=2 ? strlen30(azArg[1]) : 0);
if( i==0 || 0!=sqlite3_strnicmp(azArg[1], "fkey-indexes", i) ){
*pzErr = smprintf
("Usage %s sub-command ?switches...?\n"
"Where sub-commands are:\n"
" fkey-indexes\n", azArg[0]);
return DCR_SayUsage;
}
db = open_db(p, 0);
/*
** This SELECT statement returns one row for each foreign key constraint
** in the schema of the main database. The column values are:
**
** 0. The text of an SQL statement similar to:
**
** "EXPLAIN QUERY PLAN SELECT 1 FROM child_table WHERE child_key=?"
**
** This SELECT is similar to the one that the foreign keys implementation
** needs to run internally on child tables. If there is an index that can
** be used to optimize this query, then it can also be used by the FK
** implementation to optimize DELETE or UPDATE statements on the parent
** table.
**
** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by
** the EXPLAIN QUERY PLAN command matches this pattern, then the schema
** contains an index that can be used to optimize the query.
**
** 2. Human readable text that describes the child table and columns. e.g.
**
** "child_table(child_key1, child_key2)"
**
** 3. Human readable text that describes the parent table and columns. e.g.
**
** "parent_table(parent_key1, parent_key2)"
**
** 4. A full CREATE INDEX statement for an index that could be used to
** optimize DELETE or UPDATE statements on the parent table. e.g.
**
** "CREATE INDEX child_table_child_key ON child_table(child_key)"
**
** 5. The name of the parent table.
**
** These six values are used by the C logic below to generate the report.
*/
const char *zSql =
"SELECT "
" 'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '"
" || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' "
" || fkey_collate_clause("
" f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')"
", "
" 'SEARCH ' || s.name || ' USING COVERING INDEX*('"
" || group_concat('*=?', ' AND ') || ')'"
", "
" s.name || '(' || group_concat(f.[from], ', ') || ')'"
", "
" f.[table] || '(' || group_concat(COALESCE(f.[to], p.[name])) || ')'"
", "
" 'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))"
" || ' ON ' || quote(s.name) || '('"
" || group_concat(quote(f.[from]) ||"
" fkey_collate_clause("
" f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')"
" || ');'"
", "
" f.[table] "
"FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f "
"LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) "
"GROUP BY s.name, f.id "
"ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)"
;
const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)";
for(i=2; i<nArg; i++){
int n = strlen30(azArg[i]);
if( n>1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){
bVerbose = 1;
}
else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){
bGroupByParent = 1;
zIndent = " ";
}
else{
raw_printf(STD_ERR, "Usage: %s %s ?-verbose? ?-groupbyparent?\n",
azArg[0], azArg[1]
);
return DCR_Unknown|i;
}
}
/* Register the fkey_collate_clause() SQL function */
rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8,
0, shellFkeyCollateClause, 0, 0
);
if( rc==SQLITE_OK ){
rc = s3_prepare_v2_noom(db, zSql, -1, &pSql, 0);
}
/* Track resources after here. */
stmt_ptr_holder(&pSql);
if( rc==SQLITE_OK ){
sqlite3_bind_int(pSql, 1, bGroupByParent);
}
if( rc==SQLITE_OK ){
char *zPrev = 0;
sqlite3_stmt *pExplain = 0;
sstr_ptr_holder(&zPrev);
stmt_ptr_holder(&pExplain);
while( SQLITE_ROW==s3_step_noom(pSql) ){
int res = -1;
const char *zEQP = (const char*)sqlite3_column_text(pSql, 0);
const char *zGlob = (const char*)sqlite3_column_text(pSql, 1);
const char *zFrom = (const char*)sqlite3_column_text(pSql, 2);
const char *zTarget = (const char*)sqlite3_column_text(pSql, 3);
const char *zCI = (const char*)sqlite3_column_text(pSql, 4);
const char *zParent = (const char*)sqlite3_column_text(pSql, 5);
if( zEQP==0 || zGlob==0 ) continue;
rc = s3_prepare_v2_noom(db, zEQP, -1, &pExplain, 0);
if( rc!=SQLITE_OK ) break;
if( SQLITE_ROW==s3_step_noom(pExplain) ){
const char *zPlan = (const char*)sqlite3_column_text(pExplain, 3);
res = zPlan!=0 && ( 0==sqlite3_strglob(zGlob, zPlan)
|| 0==sqlite3_strglob(zGlobIPK, zPlan));
}
rc = sqlite3_finalize(pExplain);
pExplain = 0;
if( rc!=SQLITE_OK ) break;
if( res<0 ){
raw_printf(STD_ERR, "Error: internal error");
break;
}else{
if( bGroupByParent
&& (bVerbose || res==0)
&& (zPrev==0 || sqlite3_stricmp(zParent, zPrev))
){
raw_printf(out, "-- Parent table %s\n", zParent);
sqlite3_free(zPrev);
zPrev = smprintf("%s", zParent);
shell_check_ooms(zPrev);
}
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);
}
}
}
if( rc!=SQLITE_OK ){
*pzErr = smprintf("%s\n", sqlite3_errmsg(db));
}else{
rc = sqlite3_finalize(pSql);
pSql = 0;
if( rc!=SQLITE_OK ) *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
}
}else{
*pzErr = smprintf("%s\n", sqlite3_errmsg(db));
}
RESOURCE_FREE(mark);
return DCR_Ok|(rc!=0);
}
DISPATCHABLE_COMMAND( load ? 2 3 ){
const char *zFile = 0, *zProc = 0;
int ai = 1, rc;
if( ISS(p)->bSafeMode ) return DCR_AbortError;
if( nArg<2 || azArg[1][0]==0 ){
/* Must have a non-empty FILE. (Will not load self.) */
return DCR_SayUsage;
}
while( ai<nArg ){
const char *zA = azArg[ai++];
if( zFile==0 ) zFile = zA;
else if( zProc==0 ) zProc = zA;
else return DCR_TooMany|ai;
}
rc = sqlite3_load_extension(open_db(p, 0), zFile, zProc, pzErr);
return DCR_Ok|(rc!=SQLITE_OK);
}
DISPATCHABLE_COMMAND( log ? 2 2 ){
const char *zFile = azArg[1];
int bOn = cli_strcmp(zFile,"on")==0;
int bOff = cli_strcmp(zFile,"off")==0;
#if defined(SQLITE_SHELL_FIDDLE)
if( !bOn && !bOff ) return DCR_SayUsage;
#else
if( ISS(p)->bSafeMode && !bOn && !bOff ) return DCR_AbortError;
#endif
output_file_close(ISS(p)->pLog);
if( bOff ){
ISS(p)->pLog = 0;
return DCR_Ok;
}
if( bOn ) zFile = "stdout";
ISS(p)->pLog = output_file_open(zFile, 0);
return DCR_Ok|(ISS(p)->pLog==0);
}
static void effectMode(ShellInState *psi, u8 modeRequest, u8 modeNominal){
/* Effect the specified mode change. */
const char *zColSep = 0, *zRowSep = 0;
assert(modeNominal!=MODE_COUNT_OF);
switch( modeRequest ){
case MODE_Line:
zRowSep = SEP_Row;
break;
case MODE_Column:
if( (psi->shellFlgs & SHFLG_HeaderSet)==0 ){
psi->showHeader = 1;
}
zRowSep = SEP_Row;
break;
case MODE_List:
zColSep = SEP_Column;
zRowSep = SEP_Row;
break;
case MODE_Html:
break;
case MODE_Tcl:
zColSep = SEP_Space;
zRowSep = SEP_Row;
break;
case MODE_Csv:
zColSep = SEP_Comma;
zRowSep = SEP_CrLf;
break;
case MODE_Tab:
zColSep = SEP_Tab;
break;
case MODE_Insert:
break;
case MODE_Quote:
zColSep = SEP_Comma;
zRowSep = SEP_Row;
break;
case MODE_Ascii:
zColSep = SEP_Unit;
zRowSep = SEP_Record;
break;
case MODE_Markdown:
/* fall-thru */
case MODE_Table:
/* fall-thru */
case MODE_Box:
break;
case MODE_Count:
/* fall-thru */
case MODE_Off:
/* fall-thru */
case MODE_Json:
break;
case MODE_Explain: case MODE_Semi: case MODE_EQP: case MODE_Pretty: default:
/* Modes used internally, not settable by .mode command. */
return;
}
if( zRowSep!=0 ){
sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, zRowSep);
}
if( zColSep!=0 ){
sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator, zColSep);
}
psi->mode = modeNominal;
}
/*****************
* The .mode command
*/
COLLECT_HELP_TEXT[
".mode MODE ?OPTIONS? Set output mode",
" MODE is one of:",
" ascii Columns/rows delimited by 0x1F and 0x1E",
" box Tables using unicode box-drawing characters",
" csv Comma-separated values",
" column Output in columns. (See .width)",
" html HTML <table> code",
" insert SQL insert statements for TABLE",
" json Results in a JSON array",
" line One value per line",
" list Values delimited by \"|\"",
" markdown Markdown table format",
" qbox Shorthand for \"box --wrap 60 --quote\"",
" quote Escape answers as for SQL",
" table ASCII-art table",
" tabs Tab-separated values",
" tcl TCL list elements",
" OPTIONS: (for columnar modes or insert mode):",
" --wrap N Wrap output lines to no longer than N characters",
" --wordwrap B Wrap or not at word boundaries per B (on/off)",
" --ww Shorthand for \"--wordwrap 1\"",
" --quote Quote output text as SQL literals",
" --noquote Do not quote output text",
" TABLE The name of SQL table used for \"insert\" mode",
];
DISPATCHABLE_COMMAND( mode ? 1 0 ){
ShellInState *psi = ISS(p);
const char *zTabname = 0;
const char *zArg;
int aix;
u8 foundMode = MODE_COUNT_OF, setMode = MODE_COUNT_OF;
ColModeOpts cmOpts = ColModeOpts_default;
for(aix=1; aix<nArg; aix++){
zArg = azArg[aix];
if( optionMatch(zArg,"wrap") && aix+1<nArg ){
cmOpts.iWrap = integerValue(azArg[++aix]);
}else if( optionMatch(zArg,"ww") ){
cmOpts.bWordWrap = 1;
}else if( optionMatch(zArg,"wordwrap") && aix+1<nArg ){
cmOpts.bWordWrap = (u8)booleanValue(azArg[++aix]);
}else if( optionMatch(zArg,"quote") ){
cmOpts.bQuote = 1;
}else if( optionMatch(zArg,"noquote") ){
cmOpts.bQuote = 0;
}else{
/* Not a known option. Check for known mode, or possibly a table name. */
if( foundMode==MODE_Insert && zTabname==0 ){
zTabname = zArg;
}else if( *zArg=='-' ){
goto flag_unknown;
}else{
int im, nza = strlen30(zArg);
int isPlural = (nza>0 && zArg[nza-1]=='s');
if( foundMode!=MODE_COUNT_OF ) goto mode_badarg;
for( im=0; im<MODE_COUNT_OF; ++im ){
int nz = nza - (modeDescr[im].bMayPluralize && isPlural);
if( modeDescr[im].bUserBlocked ) continue;
if( cli_strncmp(zArg,modeDescr[im].zModeName,nz)==0 ){
if( nz<nza && nz!=strlen30(modeDescr[im].zModeName) ) continue;
foundMode = (u8)im;
setMode = modeDescr[im].iAliasFor;
break;
}
}
if( cli_strcmp(zArg, "qbox")==0 ){
ColModeOpts cmo = ColModeOpts_default_qbox;
foundMode = setMode = MODE_Box;
cmOpts = cmo;
}else if( im==MODE_COUNT_OF ) goto mode_unknown;
}
}
} /* Arg loop */
if( foundMode==MODE_COUNT_OF ){
FILE *out = psi->out;
const char *zMode;
#if SHELL_DATAIO_EXT
char *zTell = 0;
int mrc;
zMode = psi->pActiveExporter->pMethods->name(psi->pActiveExporter);
mrc = psi->pActiveExporter->pMethods->config(psi->pActiveExporter,0,&zTell);
if( zTell!=0 ){
raw_printf(out, "current output mode: %s %s\n", zMode, zTell);
sqlite3_free(zTell);
}else{
raw_printf(out, "current output mode: %s\n", zMode);
}
#else
int i = psi->mode;
assert(i>=0 && i<MODE_COUNT_OF);
zMode = modeDescr[i].zModeName;
/* Mode not specified. Show present mode (and toss any options set.) */
if( MODE_IS_COLUMNAR(psi->mode) ){
raw_printf
(out, "current output mode: %s --wrap %d --wordwrap %s --%squote\n",
zMode, psi->cmOpts.iWrap,
psi->cmOpts.bWordWrap ? "on" : "off",
psi->cmOpts.bQuote ? "" : "no");
}else{
raw_printf(out, "current output mode: %.*s\n", nms, zMode);
}
#endif
}else{
effectMode(psi, foundMode, setMode);
if( MODE_IS_COLUMNAR(setMode) ){
psi->cmOpts = cmOpts;
#if SHELL_DATAIO_EXT
psi->pActiveExporter = psi->pColumnarExporter;
#endif
}else{
#if SHELL_DATAIO_EXT
psi->pActiveExporter = psi->pFreeformExporter;
#endif
if( setMode==MODE_Insert ){
set_table_name(p, zTabname ? zTabname : "table");
}
}
}
psi->cMode = psi->mode;
return DCR_Ok;
flag_unknown:
*pzErr = smprintf("Unknown .mode option: %s\nValid options:\n%s",
zArg,
" --noquote\n"
" --quote\n"
" --wordwrap on/off\n"
" --wrap N\n"
" --ww\n");
return DCR_Unknown|aix;
mode_unknown:
*pzErr = smprintf("Mode should be one of:\n"
" ascii box column csv html insert json line\n"
" list markdown qbox quote table tabs tcl\n");
return DCR_Unknown|aix;
mode_badarg:
*pzErr = smprintf("Invalid .mode argument: %s\n", zArg);
return DCR_ArgWrong|aix;
}
/*****************
* The .oomfake command
*/
CONDITION_COMMAND(oomfake defined(SQLITE_DEBUG));
COLLECT_HELP_TEXT[
",oomfake [how_soon] Set how soon or whether to simulate OOM condition",
];
DISPATCHABLE_COMMAND( oomfake ? 1 2 azArg nArg p ){
if( nArg>1 ){
int oomf = (int)integerValue(azArg[1]);
fake_oom_countdown = oomf;
}
else raw_printf(ISS(p)->out, "OOM sim in %d allocations\n",
fake_oom_countdown);
return DCR_Ok;
}
/* Note that .open is (partially) available in WASM builds but is
** currently only intended to be used by the fiddle tool, not
** end users, so is "undocumented." */
#ifdef SQLITE_SHELL_FIDDLE
# define HOPEN ",open"
#else
# define HOPEN ".open"
#endif
/*****************
* The .nonce, .nullvalue and .open commands
*/
CONDITION_COMMAND(nonce !defined(SQLITE_SHELL_FIDDLE));
COLLECT_HELP_TEXT[
".open ?OPTIONS? ?FILE? Close existing database and reopen FILE",
" Options:",
" --append Use appendvfs to append database to the end of FILE",
#ifndef SQLITE_OMIT_DESERIALIZE
" --deserialize Load into memory using sqlite3_deserialize()",
" --hexdb Load the output of \"dbtotxt\" as an in-memory db",
" --maxsize N Maximum size for --hexdb or --deserialized database",
#endif
" --new Initialize FILE to an empty database",
" --nofollow Do not follow symbolic links",
" --readonly Open FILE readonly",
" --zip FILE is a ZIP archive",
".nonce STRING Suspend safe mode for one command if nonce matches",
".nullvalue STRING Use STRING in place of NULL values",
];
DISPATCHABLE_COMMAND( open 3 1 0 ){
ShellInState *psi = ISS(p);
const char *zFN = 0; /* Pointer to constant filename */
char *zNewFilename = 0; /* Name of the database file to open */
int iName = 1; /* Index in azArg[] of the filename */
int newFlag = 0; /* True to delete file before opening */
u8 openMode = SHELL_OPEN_UNSPEC;
int openFlags = 0;
sqlite3_int64 szMax = 0;
int rc = 0;
/* Check for command-line arguments */
for(iName=1; iName<nArg; iName++){
const char *z = azArg[iName];
#ifndef SQLITE_SHELL_FIDDLE
if( optionMatch(z,"new") ){
newFlag = 1;
# ifdef SQLITE_HAVE_ZLIB
}else if( optionMatch(z, "zip") ){
openMode = SHELL_OPEN_ZIPFILE;
# endif
}else if( optionMatch(z, "append") ){
openMode = SHELL_OPEN_APPENDVFS;
}else if( optionMatch(z, "readonly") ){
openMode = SHELL_OPEN_READONLY;
}else if( optionMatch(z, "nofollow") ){
openFlags |= SQLITE_OPEN_NOFOLLOW;
# ifndef SQLITE_OMIT_DESERIALIZE
}else if( optionMatch(z, "deserialize") ){
openMode = SHELL_OPEN_DESERIALIZE;
}else if( optionMatch(z, "hexdb") ){
openMode = SHELL_OPEN_HEXDB;
}else if( optionMatch(z, "maxsize") && iName+1<nArg ){
szMax = integerValue(azArg[++iName]);
# endif /* SQLITE_OMIT_DESERIALIZE */
}else
#endif /* !defined(SQLITE_SHELL_FIDDLE) */
if( z[0]=='-' ){
return DCR_Unknown|iName;
}else if( zFN ){
*pzErr = smprintf("extra argument: \"%s\"\n", z);
return DCR_TooMany;
}else{
zFN = z;
}
}
/* Close the existing database */
session_close_all(psi, -1);
close_db(psi,DBX(p));
DBX(p) = 0;
psi->pAuxDb->zDbFilename = 0;
sqlite3_free(psi->pAuxDb->zFreeOnClose);
psi->pAuxDb->zFreeOnClose = 0;
psi->openMode = openMode;
psi->openFlags = 0;
psi->szMax = 0;
/* If a filename is specified, try to open it first */
if( zFN || psi->openMode==SHELL_OPEN_HEXDB ){
if( newFlag && zFN && !psi->bSafeMode ) shellDeleteFile(zFN);
#ifndef SQLITE_SHELL_FIDDLE
if( psi->bSafeMode
&& psi->openMode!=SHELL_OPEN_HEXDB
&& zFN
&& cli_strcmp(zFN,":memory:")!=0
){
*pzErr = smprintf("cannot open database files in safe mode");
return DCR_AbortError;
}
#else
/* WASM mode has its own sandboxed pseudo-filesystem. */
#endif
if( zFN ){
zNewFilename = smprintf("%s", zFN);
shell_check_ooms(zNewFilename);
}else{
zNewFilename = 0;
}
psi->pAuxDb->zDbFilename = zNewFilename;
psi->openFlags = openFlags;
psi->szMax = szMax;
open_db(p, OPEN_DB_KEEPALIVE);
if( DBX(p)==0 ){
*pzErr = smprintf("cannot open '%z'\n", zNewFilename);
rc = 1;
}else{
psi->pAuxDb->zFreeOnClose = zNewFilename;
}
}
if( DBX(p)==0 ){
/* As a fall-back open a TEMP database */
psi->pAuxDb->zDbFilename = 0;
open_db(p, 0);
}
return DCR_Ok|(rc!=0);
}
DISPATCHABLE_COMMAND( nonce ? 2 2 ){
ShellInState *psi = ISS(p);
if( psi->zNonce==0 || cli_strcmp(azArg[1],psi->zNonce)!=0 ){
raw_printf(STD_ERR, "line %d: incorrect nonce: \"%s\"\n",
psi->pInSource->lineno, azArg[1]);
p->shellAbruptExit = 0x102;
return DCR_Abort;
}
/* Suspend safe mode for 1 dot-command after this. */
psi->bSafeModeFuture = 2;
return DCR_Ok;
}
DISPATCHABLE_COMMAND( nullvalue ? 2 2 ){
sqlite3_snprintf(sizeof(ISS(p)->nullValue), ISS(p)->nullValue, "%.*s",
(int)ArraySize(ISS(p)->nullValue)-1, azArg[1]);
return DCR_Ok;
}
/* Helper functions for .parameter and .vars commands
*/
struct keyval_row { char * value; int uses; int hits; };
static int kv_find_callback(void *pData, int nc, char **pV, char **pC){
assert(nc>=1);
assert(cli_strcmp(pC[0],"value")==0);
struct keyval_row *pParam = (struct keyval_row *)pData;
if( pParam->value!=0 ) sqlite3_free( pParam->value );
pParam->value = smprintf("%s", pV[0]); /* source owned by statement */
if( nc>1 ) pParam->uses = (int)integerValue(pV[1]);
++pParam->hits;
return 0;
}
static void append_in_clause(sqlite3_str *pStr,
const char **azBeg, const char **azLim);
static void append_glob_terms(sqlite3_str *pStr, const char *zColName,
const char **azBeg, const char **azLim);
static char *find_home_dir(int clearFlag);
/* Create a home-relative pathname from ~ prefixed path.
* Return it, or 0 for any error.
* Caller must sqlite3_free() it.
*/
static char *home_based_path( const char *zPath ){
char *zHome = find_home_dir(0);
char *zErr = 0;
assert( zPath[0]=='~' );
if( zHome==0 ){
zErr = "Cannot find home directory.";
}else if( zPath[0]==0 || (zPath[1]!='/'
#if defined(_WIN32) || defined(WIN32)
&& zPath[1]!='\\'
#endif
) ){
zErr = "Malformed pathname";
}else{
return smprintf("%s%s", zHome, zPath+1);
}
utf8_printf(STD_ERR, "Error: %s\n", zErr);
return 0;
}
/* Transfer selected parameters between two parameter tables, for save/load.
* Argument bSaveNotLoad determines transfer direction and other actions.
* If it is true, the store DB will be created if not existent, and its
* table for keeping parameters will be created. Or failure is returned.
* If it is false, the store DB will be opened for read and its presumed
* table for keeping parameters will be read. Or failure is returned.
*
* Arguments azNames and nNames reference the ?NAMES? save/load arguments.
* If it is an empty list, all parameters will be saved or loaded.
* Otherwise, only the named parameters are transferred, if they exist.
* It is not an error to specify a name that cannot be transferred
* because it does not exist in the source table.
*
* Returns are SQLITE_OK for success, or other codes for failure.
*/
static int kv_xfr_table(sqlite3 *db, const char *zStoreDbName,
int bSaveNotLoad, ParamTableUse ptu,
const char *azNames[], int nNames){
char *zSql = 0; /* to be sqlite3_free()'ed */
sqlite3_str *sbCopy = 0;
sqlite3 *dbStore = 0;
const char *zHere = 0;
const char *zThere = SH_KV_STORE_SNAME;
const char *zTo;
const char *zFrom;
int rc = 0;
int openFlags = (bSaveNotLoad)
? SQLITE_OPEN_URI|SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE
: SQLITE_OPEN_READONLY;
switch( ptu ){
case PTU_Binding: zHere = PARAM_TABLE_SNAME; break;
case PTU_Script: zHere = SHVAR_TABLE_SNAME; break;
default: assert(0); return 1;
}
zTo = (bSaveNotLoad)? zThere : zHere;
zFrom = (bSaveNotLoad)? zHere : zThere;
/* Ensure store DB can be opened and/or created appropriately. */
rc = shell_check_nomem(sqlite3_open_v2(zStoreDbName,&dbStore,openFlags,0));
if( rc!=SQLITE_OK ){
utf8_printf(STD_ERR, "Error: Cannot %s key/value store DB %s\n",
bSaveNotLoad? "open/create" : "read", zStoreDbName);
return rc;
}
/* Ensure it has the key/value store table, or handle its absence. */
assert(dbStore!=0);
conn_ptr_holder(&dbStore);
if( sqlite3_table_column_metadata
(dbStore, "main", SH_KV_STORE_NAME, 0, 0, 0, 0, 0, 0)!=SQLITE_OK ){
if( !bSaveNotLoad ){
utf8_printf(STD_ERR, "Error: No key/value pairs ever stored in DB %s\n",
zStoreDbName);
rc = 1;
}else{
/* The saved parameters table is not there yet; create it. */
const char *zCT =
"CREATE TABLE IF NOT EXISTS "SH_KV_STORE_NAME"(\n"
" key TEXT PRIMARY KEY,\n"
" value,\n"
" uses INT\n"
") WITHOUT ROWID;";
rc = s3_exec_noom(dbStore, zCT, 0, 0, 0);
if( rc!=SQLITE_OK ){
utf8_printf(STD_ERR, "Cannot create table %s. Nothing saved.", zThere);
}
}
}
release_holder();
assert(dbStore==0);
if( rc!=0 ) return rc;
zSql = smprintf("ATTACH %Q AS %s;", zStoreDbName, SH_KV_STORE_SCHEMA);
shell_check_ooms(zSql);
sstr_ptr_holder(&zSql);
rc = sqlite3_exec(db, zSql, 0, 0, 0);
release_holder();
if( rc!=SQLITE_OK ) return rc;
sbCopy = sqlite3_str_new(db);
sqst_ptr_holder(&sbCopy);
sqlite3_str_appendf
(sbCopy, "INSERT OR REPLACE INTO %s(key,value,uses)"
"SELECT key, value, uses FROM %s WHERE key ", zTo, zFrom);
append_in_clause(sbCopy, azNames, azNames+nNames);
zSql = sqlite3_str_finish(sbCopy);
drop_holder();
shell_check_ooms(zSql);
sstr_ptr_holder(&zSql);
rc = sqlite3_exec(db, zSql, 0, 0, 0);
release_holder();
s3_exec_noom(db, "DETACH "SH_KV_STORE_SCHEMA";", 0, 0, 0);
return rc;
}
/* Default locations of key/value DBs for .parameters and .vars save/load. */
static const char *zDefaultParamStore = "~/sqlite_params.sdb";
static const char *zDefaultVarStore = "~/sqlite_vars.sdb";
/* Possibly generate a derived path from input spec, with defaulting
* and conversion of leading (or only) tilde as home directory.
* The above-set default is used for zSpec NULL, "" or "~".
* When return is 0, there is an error; what needs doing cannot be done.
* The return must eventually be sqlite3_free()'ed.
*/
static char *kv_store_path(const char *zSpec, ParamTableUse ptu){
if( zSpec==0 || zSpec[0]==0 || cli_strcmp(zSpec,"~")==0 ){
const char *zDef;
switch( ptu ){
case PTU_Binding: zDef = zDefaultParamStore; break;
case PTU_Script: zDef = zDefaultVarStore; break;
default: return 0;
}
return home_based_path(zDef);
}else if ( zSpec[0]=='~' ){
return home_based_path(zSpec);
}
return smprintf("%s", zSpec);
}
/* Load some or all key/value pairs. Arguments are "load FILE ?NAMES?". */
static int kv_pairs_load(sqlite3 *db, ParamTableUse ptu,
const char *azArg[], int nArg){
char *zStore = kv_store_path((nArg>1)? azArg[1] : 0, ptu);
if( zStore==0 ){
utf8_printf(STD_ERR, "Cannot form parameter load path. Nothing loaded.\n");
return DCR_Error;
}else{
const char **pzFirst = (nArg>2)? azArg+2 : 0;
int nNames = (nArg>2)? nArg-2 : 0;
int rc;
sstr_holder(zStore);
rc = kv_xfr_table(db, zStore, 0, ptu, pzFirst, nNames);
release_holder();
return rc;
}
}
/* Save some or all parameters. Arguments are "save FILE ?NAMES?". */
static int kv_pairs_save(sqlite3 *db, ParamTableUse ptu,
const char *azArg[], int nArg){
char *zStore = kv_store_path((nArg>1)? azArg[1] : 0, ptu);
if( zStore==0 ){
utf8_printf(STD_ERR, "Cannot form parameter save path. Nothing saved.\n");
return DCR_Error;
}else{
const char **pzFirst = (nArg>2)? azArg+2 : 0;
int nNames = (nArg>2)? nArg-2 : 0;
int rc;
sstr_holder(zStore);
rc = kv_xfr_table(db, zStore, 1, ptu, pzFirst, nNames);
release_holder();
return rc;
}
}
#ifndef SQLITE_NOHAVE_SYSTEM
/* Remove leading comment line of form "-- tag\n" from a by-ref string.
* The *pz_nt argument is dynamically allocated (ala sqlite3_malloc() and is
* replaced if the removal is done. This is a helper for 2nd next function.
*/
static void remove_name_tag(char **pz_nt, char *tag){
int tagLen;
char *zUntag;
assert(pz_nt!=0 && tag!=0);
if( *pz_nt==0 || strstr(*pz_nt, "-- ")!=*pz_nt ) return;
if( strstr((*pz_nt)+3, tag)!=(*pz_nt)+3 ) return;
tagLen = strlen30(tag)+3;
if( (*pz_nt)[tagLen] != '\n' ) return;
zUntag = smprintf("%s", *pz_nt+tagLen+1);
shell_check_ooms(zUntag);
sqlite3_free(*pz_nt);
*pz_nt = zUntag;
}
/*
* Edit one named value in the parameters or shell variables table.
* If it does not yet exist, create it. If eval is true, the value
* is treated as a bare expression, otherwise it is a text value.
* The "uses" argument sets the 3rd column in the selected table,
* and serves to select which of the two tables is modified.
*
* During editing, the 1st line of the text shows the variable name.
* If that is left as-is, it is removed after editing is done.
*/
static int edit_one_kvalue(sqlite3 *db, char *name, int eval,
ParamTableUse uses, const char * zEd, char **pzErr){
struct keyval_row kvRow = {0,0,0};
int rc;
char * zVal = 0;
const char *zTab = 0;
char * zSql = 0;
const char *zFmt;
char * zEntrySql = 0;
sstr_ptr_holder(&zVal); /* +1 */
switch( uses ){
case PTU_Script: zTab = SHVAR_TABLE_SNAME; break;
case PTU_Binding: zTab = PARAM_TABLE_SNAME; break;
default: assert(0);
}
if( eval ){
zSql = smprintf("SELECT value, uses FROM %s WHERE key=%Q"
" AND uses IN (%d, "SPTU_Entry") ORDER BY uses",
zTab, name, uses);
}else{
zSql = smprintf("SELECT value, uses FROM %s "
"WHERE key=%Q AND uses=%d", zTab, name, uses);
}
shell_check_ooms(zSql);
sstr_ptr_holder(&kvRow.value); /* +2 */
sstr_holder(zSql); /* +3 */
s3_exec_noom(db, zSql, kv_find_callback, &kvRow, 0);
release_holder(); /* zSql =2 */
assert(kvRow.hits<3);
if( kvRow.hits>=1 ){
/* Editing an existing value of same kind. */
zVal = kvRow.value;
drop_holder(); /* kvRow.value =1 */
zSql = smprintf("SELECT edit('-- %s\n'||%Q, %Q)", name, zVal, zEd);
shell_check_ooms(zSql);
sstr_holder(zSql);
zVal = db_text(db, zSql, 0);
release_holder(); /* zSql =1 */
remove_name_tag(&zVal, name);
zFmt = (!eval)? "UPDATE %s SET value=%Q WHERE key=%Q AND uses=%d"
: "UPDATE %s SET value=(SELECT %s) WHERE key=%Q AND uses=%d";
zSql = smprintf(zFmt, zTab, zVal, name, uses);
}else{
/* Editing a new value of same kind. */
assert(kvRow.value==0 || kvRow.uses!=uses);
drop_holder(); /* kvRow.value =1 */
zSql = smprintf("SELECT edit('-- %s\n', %Q)", name, zEd);
shell_check_ooms(zSql);
sstr_holder(zSql);
zVal = db_text(db, zSql, 1);
release_holder(); /* zSql =1 */
remove_name_tag(&zVal, name);
zFmt = (!eval)? "INSERT INTO %s(key,value,uses) VALUES (%Q,%Q,%d)"
: "INSERT INTO %s(key,value,uses) VALUES(%Q,(SELECT %s LIMIT 1),%d)";
zSql = smprintf(zFmt, zTab, name, zVal, uses);
}
shell_check_ooms(zSql);
sstr_holder(zSql); /* +2 */
if( eval ){
zEntrySql = smprintf("INSERT OR REPLACE INTO %s(key,value,uses)"
" VALUES(%Q,%Q,"SPTU_Entry")", zTab, name,zVal);
}else{
zEntrySql = smprintf("DELETE FROM %s WHERE key=%Q AND uses="SPTU_Entry,
zTab, name);
}
if( zEntrySql ){
sqlite3_exec(db, zEntrySql, 0, 0, 0);
sqlite3_free(zEntrySql);
}
rc = sqlite3_exec(db, zSql, 0, 0, 0);
if( rc!=SQLITE_OK && pzErr!=0 ){
if( eval ){
*pzErr = smprintf("Cannot evaluate SELECT %s\n(Fails with %s)\n",
zVal, sqlite3_errmsg(db));
}else{
*pzErr = smprintf("Cannot execute %s\n(Fails with %s)\n",
zSql, sqlite3_errmsg(db));
}
}
release_holders(2); /* =0 */
return rc!=SQLITE_OK;
}
#endif
/* Space-join values in an argument list. *valLim is not included. */
char *values_join( char **valBeg, char **valLim ){
char *z = 0;
const char *zSep = 0;
while( valBeg < valLim ){
z = smprintf("%z%s%s", z, zSep, *valBeg);
zSep = " ";
++valBeg;
}
return z;
}
#define INT_RE "((\\d+)|(0[xX][0-9a-fA-F]+))"
#define REAL_RE "((\\d+(\\.\\d+)?([eE][-+]?\\d{1,4})?)|(\\.\\d+))"
#define HEXP_RE "([0-9a-fA-F]{2,2})"
const char *param_set_literals[] = {
/* int */ "^[-+]?" INT_RE "$",
/* real */ "^[-+]?" REAL_RE "$",
/* blob */ "^[xX]'"HEXP_RE"*'$",
/* text */ "^'([^']|'')*'$"
};
#undef INT_RE
#undef REAL_RE
#undef HEXP_RE
/* Return an option character if it is single and prefixed by - or --,
* else return 0.
*/
static char option_char(char *zArg){
if( zArg[0]=='-' ){
++zArg;
if( zArg[0]=='-' ) ++zArg;
if( zArg[0]!=0 && zArg[1]==0 ) return zArg[0];
}
return 0;
}
/* Most of .vars set
* Return SQLITE_OK on success, else SQLITE_ERROR.
*/
static int shvar_set(sqlite3 *db, char *name, char **valBeg, char **valLim){
int rc = SQLITE_OK;
char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0;
sqlite3_stmt *pStmtSet = 0;
char *zValue = (zValGlom==0)? *valBeg : zValGlom;
char *zSql
= smprintf("REPLACE INTO "SHVAR_TABLE_SNAME"(key,value,uses)"
"VALUES(%Q,%Q,"SPTU_Script");", name, zValue);
sstr_holder(zValGlom);
rc = s3_prep_noom_free(db, &zSql, &pStmtSet);
assert(rc==SQLITE_OK);
stmt_holder(pStmtSet);
rc = s3_step_noom(pStmtSet);
rc = (SQLITE_DONE==rc)? SQLITE_OK : SQLITE_ERROR;
release_holders(2);
return rc;
}
/* Effect most of the .parameter set subcommand (per help text.)
* Return SQLITE_OK on success, else SQLITE_ERROR. Error
* explanation is placed in *pzErr, to be later sqlite3_free()'ed.
*/
static int param_set(sqlite3 *db, u8 bEval, char *name,
char **valBeg, char **valLim, char **pzErr){
char *zSql = 0;
char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0;
sqlite3_stmt *pStmtSet = 0;
/* Above objects are managed. */
const char **pSL;
u8 bLitMatch = 0;
int rc = SQLITE_OK, retries = 0;
char *zValue = (zValGlom==0)? *valBeg : zValGlom;
sstr_holder(zValGlom); /* +1 */
if( !bEval ){
/* No eval specified; see if the value matches a common literal form. */
zSql = smprintf("SELECT regexp($re,%Q)", zValue);
rc = s3_prep_noom_free(db, &zSql, &pStmtSet);
stmt_ptr_holder(&pStmtSet); /* +2 */
for( pSL=param_set_literals; pSL<PastArray(param_set_literals); ++pSL ){
sqlite3_reset(pStmtSet);
rc = sqlite3_bind_text(pStmtSet,1,*pSL,-1,SQLITE_STATIC);
shell_check_nomem(rc);
rc = shell_check_nomem(sqlite3_step(pStmtSet));
assert(rc==SQLITE_ROW);
if( 0!=(bLitMatch = sqlite3_column_int(pStmtSet, 0)) ) break;
}
release_holder(); /* =1 */
}
/* These possible conditions may exist, to be handled thus:
* 1. No evaluation was specified.
* Value must be a recognizable literal or will be treated as text.
* Success is assured (absent OOM.)
* 2. Evaluation was specified.
* Just do it and take whatever type results.
* Report failure if that occurs.
* In both cases, a PTU_Entry row is set to the input value.
*/
stmt_ptr_holder(&pStmtSet); /* +2 */
if( bEval || bLitMatch ){
zSql = smprintf
( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
" VALUES(%Q,(%s),"SPTU_Binding")", name, zValue );
}else{
zSql = smprintf
( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
"VALUES(%Q,%Q,"SPTU_Binding")", name, zValue );
}
rc = s3_prep_noom_free(db, &zSql, &pStmtSet);
if( rc!=SQLITE_OK ){
/* Reach here when value matches no literal and fails evaluation. */
sqlite3_finalize(pStmtSet);
pStmtSet = 0;
if( pzErr ){
*pzErr = smprintf("Parameter %s set as text.\nCompiling \"%s\" fails.\n"
"(%s)", name, zValue, sqlite3_errmsg(db));
}
zSql = smprintf
( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
"VALUES(%Q,%Q,"SPTU_Binding")", name, zValue );
rc = s3_prep_noom_free(db, &zSql, &pStmtSet);
assert(rc==SQLITE_OK);
}
zSql = smprintf("INSERT OR REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
" VALUES(%Q,%Q,"SPTU_Entry")", name, zValue);
if( zSql ){
sqlite3_exec(db, zSql, 0, 0, 0);
sqlite3_free(zSql);
}
sqlite3_step(pStmtSet);
release_holders(2);
return rc;
}
/* list or ls subcommand for .parameter and .vars dot-commands */
static void list_pov_entries(ShellExState *psx, ParamTableUse ptu, u8 bShort,
char **pzArgs, int nArg){
sqlite3_stmt *pStmt = 0;
sqlite3_str *sbList = 0;
char *zFromWhere = 0;
char *zSql = 0;
/* Above objects are managed. */
RESOURCE_MARK(mark);
sqlite3 *db;
int len = 0, rc;
const char *zTab;
switch( ptu ){
case PTU_Binding:
db = DBX(psx);
zTab = PARAM_TABLE_SNAME;
break;
case PTU_Script:
db = psx->dbShell;
zTab = SHVAR_TABLE_NAME;
break;
default: assert(0); return;
}
sbList = sqlite3_str_new(db);
sqst_holder(sbList);
sqlite3_str_appendf(sbList, "FROM ");
sqlite3_str_appendf(sbList, zTab);
sqlite3_str_appendf(sbList, " WHERE (uses=?1) AND ");
append_glob_terms(sbList, "key",
(const char **)pzArgs, (const char **)pzArgs+nArg);
shell_check_nomem(sqlite3_str_errcode(sbList));
zFromWhere = sqlite3_str_finish(sbList);
drop_holder();
shell_check_ooms(zFromWhere);
sstr_holder(zFromWhere); /* +1 */
zSql = smprintf("SELECT max(length(key)) %s", zFromWhere);
sstr_ptr_holder(&zSql);
rc = s3_prep_noom_free(db, &zSql, &pStmt);
stmt_ptr_holder(&pStmt);
if( rc==SQLITE_OK ){
sqlite3_bind_int(pStmt, 1, ptu);
if( s3_step_noom(pStmt)==SQLITE_ROW ){
len = sqlite3_column_int(pStmt, 0);
if( len>40 ) len = 40;
if( len<4 ) len = 4;
}
sqlite3_finalize(pStmt);
pStmt = 0;
}
if( len ){
FILE *out = ISS(psx)->out;
sqlite3_free(zSql);
zSql = 0;
if( !bShort ){
int nBindings = 0, nScripts = 0;
zSql = smprintf("SELECT key, uses,"
"CASE typeof(value)"
" WHEN 'text' THEN quote(value)"
" WHEN 'blob' THEN 'x'''||hex(value)||''''"
" ELSE value END"
" %s ORDER BY uses, key", zFromWhere);
rc = s3_prep_noom_free(db, &zSql, &pStmt);
sqlite3_bind_int(pStmt, 1, ptu);
while( rc==SQLITE_OK && s3_step_noom(pStmt)==SQLITE_ROW ){
ParamTableUse ptux = sqlite3_column_int(pStmt,1);
const char *zName = (const char*)sqlite3_column_text(pStmt,0);
const char *zValue = (const char*)sqlite3_column_text(pStmt,2);
if( !zName ) zName = "?";
if( !zValue ) zValue = "?";
switch( ptux ){
case PTU_Binding:
if( nBindings++ == 0 ){
utf8_printf(out, "Bindings:\n%-*s %s\n", len, "name", "value");
}
utf8_printf(out, "%-*s %s\n", len, zName, zValue);
break;
case PTU_Script:
if( nScripts++ == 0 ){
utf8_printf(out, "Scripts:\n%-*s %s\n", len, "name", "value");
}
utf8_printf(out, "%-*s %s\n", len, zName, zValue);
break;
default: break; /* Ignore */
}
}
}else{
int nc = 0, ncw = 78/(len+2);
zSql = smprintf("SELECT key %s ORDER BY key", zFromWhere);
rc = s3_prep_noom_free(db, &zSql, &pStmt);
sqlite3_bind_int(pStmt, 1, ptu);
while( rc==SQLITE_OK && s3_step_noom(pStmt)==SQLITE_ROW ){
utf8_printf(out, "%s %-*s", ((++nc%ncw==0)? "\n" : ""),
len, sqlite3_column_text(pStmt,0));
}
if( nc>0 ) utf8_printf(out, "\n");
}
}
RESOURCE_FREE(mark);
}
/* Append an OR'ed series of GLOB terms comparing a given column
* name to a series of patterns. Result is an appended expression.
* For an empty pattern series, expression is true for non-NULL.
*/
static void append_glob_terms(sqlite3_str *pStr, const char *zColName,
const char **azBeg, const char **azLim){
if( azBeg==azLim ) sqlite3_str_appendf(pStr, "%s NOT NULL", zColName);
else{
char *zSep = "(";
while( azBeg<azLim ){
sqlite3_str_appendf(pStr, "%sglob(%Q,%s)", zSep, *azBeg, zColName);
zSep = " OR ";
++azBeg;
}
sqlite3_str_appendf(pStr, ")");
}
}
/* Append either an IN clause or an always true test to some SQL.
*
* An empty IN list is the same as always true (for non-NULL LHS)
* for this clause, which assumes a trailing LHS operand and space.
* If that is not the right result, guard the call against it.
* This is used for ".parameter mumble ?NAMES?" options,
* where a missing list means all the qualifying entries.
*
* The empty list may be signified by azBeg and azLim both 0.
*/
static void append_in_clause(sqlite3_str *pStr,
const char **azBeg, const char **azLim){
if( azBeg==azLim ) sqlite3_str_appendf(pStr, "NOT NULL");
else{
char cSep = '(';
sqlite3_str_appendf(pStr, "IN");
while( azBeg<azLim ){
sqlite3_str_appendf(pStr, "%c%Q", cSep, *azBeg);
cSep = ',';
++azBeg;
}
sqlite3_str_appendf(pStr, ")");
}
}
/*****************
* The .parameter command
*/
COLLECT_HELP_TEXT[
".parameter CMD ... Manage per-DB SQL parameter bindings table",
" clear ?NAMES? Erase all or only given named parameters",
#ifndef SQLITE_NOHAVE_SYSTEM
" edit ?OPT? ?NAME? ... Use edit() to create or alter parameter(s)",
" OPT may be -t to use edited value(s) as text or -e to evaluate it,",
" or -m or -r to edit missing or referenced parameters from last query.",
#endif
" init Initialize TEMP table for bindings and scripts",
" list ?PATTERNS? List current DB parameters table binding values",
" Alternatively, to list just some or all names: ls ?PATTERNS?",
" load ?FILE? ?NAMES? Load some or all named parameters from FILE",
" If FILE missing, empty or '~', it defaults to ~/sqlite_params.sdb",
" save ?FILE? ?NAMES? Save some or all named parameters into FILE",
" If FILE missing, empty or '~', it defaults to ~/sqlite_params.sdb",
" set ?OPT? NAME VALUE Give SQL parameter NAME a value of VALUE",
" NAME must begin with one of $,:,@,? for bindings, VALUE is the space-",
" joined arguments. OPT may -e to evaluate VALUE as a SQL expression.",
" unset ?NAMES? Remove named parameter(s) from parameters table",
];
DISPATCHABLE_COMMAND( parameter 2 2 0 ){
int rc = 0;
char *zErr = 0;
sqlite3 *db = open_db(p,0);
/* .parameter clear and .parameter unset ?NAMES?
** Delete some or all parameters from the TEMP table that holds them.
** Without any arguments, clear deletes them all and unset does nothing.
*/
if( cli_strcmp(azArg[1],"clear")==0 || cli_strcmp(azArg[1],"unset")==0 ){
if( param_table_exists(db) && (nArg>2 || azArg[1][0]=='c') ){
sqlite3_str *sbZap = sqlite3_str_new(db);
char *zSql = 0;
sqlite3_str_appendf
(sbZap, "DELETE FROM "PARAM_TABLE_SNAME" WHERE key ");
append_in_clause(sbZap,
(const char **)&azArg[2], (const char **)&azArg[nArg]);
zSql = sqlite3_str_finish(sbZap);
shell_check_ooms(zSql);
sstr_holder(zSql);
sqlite3_exec(db, zSql, 0, 0, 0);
release_holder();
}
}else
#ifndef SQLITE_NOHAVE_SYSTEM
/* .parameter edit ?NAMES?
** Edit the named parameters. Any that do not exist are created.
*/
if( cli_strcmp(azArg[1],"edit")==0 ){
ShellInState *psi = ISS(p);
int ia = 2;
int eval = 0;
int reffed = 0;
int missing = 0;
int edSet;
if( !INSOURCE_IS_INTERACTIVE(psi->pInSource) ){
utf8_printf(STD_ERR, "Error: "
".parameter edit can only be used interactively.\n");
return DCR_Error;
}
param_table_init(db);
edSet = attempt_editor_set(psi, azArg[0], (nArg>2)? azArg[2] : 0 );
if( edSet < 0 ) return DCR_Error;
else ia += edSet;
/* Future: Allow an option whereby new value can be evaluated
* the way that .parameter set ... does.
*/
while( ia < nArg ){
ParamTableUse ptu;
char *zA = azArg[ia];
char cf = (zA[0]=='-')? zA[1] : 0;
if( cf!=0 && zA[2]==0 ){
++ia;
switch( cf ){
case 'e': eval = 1; continue;
case 't': eval = 0; continue;
case 'm': missing = 1; reffed = 0; continue;
case 'r': reffed = 1; missing = 0; continue;
default:
utf8_printf(STD_ERR, "Error: bad .parameter name: %s\n", zA);
return DCR_Error;
}
}
ptu = classify_param_name(zA);
if( ptu!=PTU_Binding ){
utf8_printf(STD_ERR, "Error: "
"%s cannot be a binding parameter name.\n", zA);
return DCR_Error;
}
rc = edit_one_kvalue(db, zA, eval, ptu, psi->zEditor, &zErr);
++ia;
if( zErr!=0 ){
utf8_printf(STD_ERR, "%s", zErr);
sqlite3_free(zErr);
zErr = 0;
}
if( rc!=0 ) return DCR_Error;
}
if( reffed|missing ){
char acSqlCount[] = "SELECT count(*) FROM "PARAM_TABLE_SNAME
" WHERE uses="SPTU_Ref" AND value<=0";
const char *zSqlName = "SELECT key FROM "PARAM_TABLE_SNAME
" WHERE uses="SPTU_Ref" AND value<=%d ORDER BY rowid"
" LIMIT 1 OFFSET %d";
int nve, vix;
acSqlCount[sizeof(acSqlCount)-2] = (reffed)? '1' : '0';
nve = db_int(db, acSqlCount);
for( vix=0; vix<nve; ++vix ){
char *zQV = smprintf(zSqlName, (int)reffed, vix);
char *zVar = 0;
sstr_holder(zQV);
sstr_ptr_holder(&zVar);
shell_check_ooms(zQV);
zVar = db_text(db, zQV, 0);
rc = edit_one_kvalue(db, zVar, eval, PTU_Binding, psi->zEditor, &zErr);
release_holders(2);
if( zErr!=0 ){
utf8_printf(STD_ERR, "%s", zErr);
sqlite3_free(zErr);
zErr = 0;
}
if( rc!=0 ) return DCR_Error;
}
/* Edit */
}
}else
#endif
/* .parameter init
** Make sure the TEMP table used to hold bind parameters exists.
** Create it if necessary.
*/
if( nArg==2 && cli_strcmp(azArg[1],"init")==0 ){
param_table_init(db);
}else
/* .parameter list|ls
** List all or selected bind parameters.
** list displays names, values and uses.
** ls displays just the names.
*/
if( nArg>=2 && ((cli_strcmp(azArg[1],"list")==0)
|| (cli_strcmp(azArg[1],"ls")==0)) ){
list_pov_entries(p, PTU_Binding, azArg[1][1]=='s', azArg+2, nArg-2);
}else
/* .parameter load
** Load all or named parameters from specified or default (DB) file.
*/
if( cli_strcmp(azArg[1],"load")==0 ){
param_table_init(db);
rc = kv_pairs_load(db, PTU_Binding, (const char **)azArg+1, nArg-1);
}else
/* .parameter save
** Save all or named parameters into specified or default (DB) file.
*/
if( cli_strcmp(azArg[1],"save")==0 ){
rc = kv_pairs_save(db, PTU_Binding, (const char **)azArg+1, nArg-1);
}else
/* .parameter set NAME VALUE
** Set or reset a bind parameter. NAME should be the full parameter
** name exactly as it appears in the query. (ex: $abc, @def). The
** VALUE can be in either SQL literal notation, or if not it will be
** understood to be a text string.
*/
if( nArg>=4 && cli_strcmp(azArg[1],"set")==0 ){
int inv = 2;
u8 bEval = 0;
char cOpt;
ParamTableUse ptu;
while( inv<nArg && (cOpt = option_char(azArg[inv]))!=0 ){
if( cOpt!='e' ) goto param_fail;
bEval = 1;
++inv;
}
if( nArg-inv < 2 ) goto param_fail;
ptu = classify_param_name(azArg[inv]);
if( ptu!=PTU_Binding ){
utf8_printf(STD_ERR,
"Error: %s is not a usable parameter name.\n", azArg[inv]);
rc = 1;
}else{
param_table_init(db);
rc = param_set(db, bEval, azArg[inv], &azArg[inv+1], &azArg[nArg], &zErr);
if( rc!=SQLITE_OK || zErr ){
utf8_printf(STD_ERR, "%s: %s\n", (rc)? "Error" : "Warning",
(zErr)? zErr : sqlite3_errmsg(db));
sqlite3_free(zErr);
}
}
}else
{ /* If no command name and arg count matches, show a syntax error */
param_fail:
showHelp(ISS(p)->out, "parameter", p);
return DCR_CmdErred;
}
return DCR_Ok | (rc!=0);
}
/*****************
* The .print, .progress and .prompt commands
*/
CONDITION_COMMAND( progress !defined(SQLITE_OMIT_PROGRESS_CALLBACK) );
COLLECT_HELP_TEXT[
".print STRING... Print literal STRING",
".progress N Invoke progress handler after every N opcodes",
" --limit N Interrupt after N progress callbacks",
" --once Do no more than one progress interrupt",
" --quiet|-q No output except at interrupts",
" --reset Reset the count for each input and interrupt",
".prompt MAIN CONTINUE Replace the standard prompts",
];
DISPATCHABLE_COMMAND( print 3 1 0 ){
int i;
for(i=1; i<nArg; i++){
utf8_printf(ISS(p)->out, "%s%s", azArg[i], (i==nArg-1)? "\n" : " ");
}
return DCR_Ok;
}
DISPATCHABLE_COMMAND( progress 3 2 0 ){
ShellInState *psi = ISS(p);
int i;
int nn = 0;
psi->flgProgress = 0;
psi->mxProgress = 0;
psi->nProgress = 0;
for(i=1; i<nArg; i++){
const char *z = azArg[i];
if( z[0]=='-' ){
z++;
if( z[0]=='-' ) z++;
if( cli_strcmp(z,"quiet")==0 || cli_strcmp(z,"q")==0 ){
psi->flgProgress |= SHELL_PROGRESS_QUIET;
continue;
}
if( cli_strcmp(z,"reset")==0 ){
psi->flgProgress |= SHELL_PROGRESS_RESET;
continue;
}
if( cli_strcmp(z,"once")==0 ){
psi->flgProgress |= SHELL_PROGRESS_ONCE;
continue;
}
if( cli_strcmp(z,"limit")==0 ){
if( i+1>=nArg ){
*pzErr = smprintf("missing argument on --limit\n");
return DCR_Unpaired|i;
}else{
psi->mxProgress = (int)integerValue(azArg[++i]);
}
continue;
}
return DCR_Unknown|i;
}else{
nn = (int)integerValue(z);
}
}
open_db(p, 0);
sqlite3_progress_handler(DBX(p), nn, progress_handler, psi);
return DCR_Ok;
}
/* Allow too few arguments by tradition, (a form of no-op.) */
DISPATCHABLE_COMMAND( prompt ? 1 3 ){
if( nArg >= 2) {
SET_MAIN_PROMPT(azArg[1]);
}
if( nArg >= 3) {
SET_MORE_PROMPT(azArg[2]);
}
return DCR_Ok;
}
/*****************
* The .recover and .restore commands
*/
CONDITION_COMMAND( recover SQLITE_SHELL_HAVE_RECOVER );
CONDITION_COMMAND( restore !defined(SQLITE_SHELL_FIDDLE) );
COLLECT_HELP_TEXT[
".recover Recover as much data as possible from corrupt db.",
" --ignore-freelist Ignore pages that appear to be on db freelist",
" --lost-and-found TABLE Alternative name for the lost-and-found table",
" --no-rowids Do not attempt to recover rowid values",
" that are not also INTEGER PRIMARY KEYs",
".restore ?DB? FILE Restore content of DB (default \"main\") from FILE",
];
/*
** This command is invoked to recover data from the database. A script
** to construct a new database containing all recovered data is output
** on stream pState->out.
*/
DISPATCHABLE_COMMAND( recover ? 1 7 ){
int rc = SQLITE_OK;
const char *zRecoveryDb = ""; /* Name of "recovery" database. Debug only */
const char *zLAF = "lost_and_found";
int bFreelist = 1; /* 0 if --ignore-freelist is specified */
int bRowids = 1; /* 0 if --no-rowids */
sqlite3_recover *pr = 0;
int i = 0;
FILE *out = ISS(p)->out;
sqlite3 *db = open_db(p, 0);
for(i=1; i<nArg; i++){
char *z = azArg[i];
int n;
if( z[0]=='-' && z[1]=='-' ) z++;
n = strlen30(z);
if( n<=17 && memcmp("-ignore-freelist", z, n)==0 ){
bFreelist = 0;
}else
if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
/* This option determines the name of the ATTACH-ed database used
** internally by the recovery extension. The default is "" which
** means to use a temporary database that is automatically deleted
** when closed. This option is undocumented and might disappear at
** any moment. */
i++;
zRecoveryDb = azArg[i];
}else
if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
i++;
zLAF = azArg[i];
}else
if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
bRowids = 0;
}
else{
utf8_printf(stderr, "unexpected option: %s\n", azArg[i]);
showHelp(stderr, azArg[0], p);
return DCR_Error;
}
}
pr = sqlite3_recover_init_sql(db, "main", recoverSqlCb, (void*)p);
sqlite3_recover_config(pr, 789, (void*)zRecoveryDb); /* Debug use only */
sqlite3_recover_config(pr, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF);
sqlite3_recover_config(pr, SQLITE_RECOVER_ROWIDS, (void*)&bRowids);
sqlite3_recover_config(pr, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist);
sqlite3_recover_run(pr);
if( sqlite3_recover_errcode(pr)!=SQLITE_OK ){
const char *zErr = sqlite3_recover_errmsg(pr);
int errCode = sqlite3_recover_errcode(pr);
raw_printf(stderr, "sql error: %s (%d)\n", zErr, errCode);
}
rc = sqlite3_recover_finish(pr);
return (rc!=SQLITE_OK)? DCR_Error : DCR_Ok;
}
DISPATCHABLE_COMMAND( restore ? 2 3 ){
int rc;
const char *zSrcFile;
const char *zDb;
sqlite3 *pSrc = 0;
sqlite3_backup *pBackup;
AnyResourceHolder arh = { 0, (GenericFreer)sqlite3_backup_finish };
int nTimeout = 0;
if( ISS(p)->bSafeMode ) return DCR_AbortError;
if( nArg==2 ){
zSrcFile = azArg[1];
zDb = "main";
}else if( nArg==3 ){
zSrcFile = azArg[2];
zDb = azArg[1];
}else{
return DCR_TooMany;
}
rc = shell_check_nomem(sqlite3_open(zSrcFile, &pSrc));
conn_holder(pSrc);
if( rc!=SQLITE_OK ){
*pzErr = smprintf("cannot open \"%s\"\n", zSrcFile);
release_holder();
return DCR_Error;
}
open_db(p, 0);
pBackup = sqlite3_backup_init(DBX(p), zDb, pSrc, "main");
if( pBackup==0 ){
*pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
release_holder();
return DCR_Error;
}
arh.pAny = pBackup;
any_ref_holder(&arh);
while( (rc = shell_check_nomem(sqlite3_backup_step(pBackup,100)))==SQLITE_OK
|| rc==SQLITE_BUSY ){
if( rc==SQLITE_BUSY ){
if( nTimeout++ >= 3 ) break;
sqlite3_sleep(100);
}
}
if( rc==SQLITE_DONE ){
rc = 0;
}else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){
*pzErr = smprintf("source database is busy\n");
rc = 1;
}else{
*pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
rc = 1;
}
release_holders(2);
return DCR_Ok|rc;
}
/*****************
* The .scanstats and .schema commands
*/
COLLECT_HELP_TEXT[
".scanstats on|off|est Turn sqlite3_stmt_scanstatus() metrics on or off",
".schema ?PATTERN? Show the CREATE statements matching PATTERN",
" Options:",
" --indent Try to pretty-print the schema",
" --nosys Omit objects whose names start with \"sqlite_\"",
];
DISPATCHABLE_COMMAND( scanstats ? 2 2 ){
if( cli_strcmp(azArg[1], "est")==0 ){
ISS(p)->scanstatsOn = 2;
}else{
ISS(p)->scanstatsOn = (u8)booleanValue(azArg[1]);
}
open_db(p, 0);
sqlite3_db_config(DBX(p), SQLITE_DBCONFIG_STMT_SCANSTATUS,
ISS(p)->scanstatsOn, (int*)0);
#ifndef SQLITE_ENABLE_STMT_SCANSTATUS
raw_printf(STD_ERR, "Warning: .scanstats not available in this build.\n");
#endif
return DCR_Ok;
}
DISPATCHABLE_COMMAND( schema ? 1 2 ){
int rc = 0;
ShellText sSelect = {0};
ShellInState *psi = ISS(p);
u8 useMode = MODE_Semi;
AnyResourceHolder arh = {psi, (GenericFreer)modePopper};
RESOURCE_MARK(mark);
char *zErrMsg = 0;
const char *zDiv = "(";
const char *zName = 0;
int iSchema = 0;
int bDebug = 0;
int bNoSystemTabs = 0;
int ii;
open_db(p, 0);
initText(&sSelect);
for(ii=1; ii<nArg; ii++){
if( optionMatch(azArg[ii],"indent") ){
useMode = MODE_Pretty;
}else if( optionMatch(azArg[ii],"debug") ){
bDebug = 1;
}else if( optionMatch(azArg[ii],"nosys") ){
bNoSystemTabs = 1;
}else if( azArg[ii][0]=='-' ){
return DCR_Unknown|ii;
}else if( zName==0 ){
zName = azArg[ii];
}else{
return DCR_TooMany;
}
}
outputModePush(psi); /* May OOM fail. */
any_ref_holder(&arh);
psi->showHeader = 0;
psi->cMode = psi->mode = useMode;
if( zName!=0 ){
int isSchema = sqlite3_strlike(zName, "sqlite_master", '\\')==0
|| sqlite3_strlike(zName, "sqlite_schema", '\\')==0
|| sqlite3_strlike(zName,"sqlite_temp_master", '\\')==0
|| sqlite3_strlike(zName,"sqlite_temp_schema", '\\')==0;
if( isSchema ){
char *new_argv[2], *new_colv[2];
new_argv[0] = smprintf(
"CREATE TABLE %s (\n"
" type text,\n"
" name text,\n"
" tbl_name text,\n"
" rootpage integer,\n"
" sql text\n"
")", zName);
shell_check_ooms(new_argv[0]);
sstr_holder(new_argv[0]);
new_argv[1] = 0;
new_colv[0] = "sql";
new_colv[1] = 0;
callback(p, 1, new_argv, new_colv);
release_holder();
}
}
if( zDiv ){
sqlite3_stmt *pStmt = 0;
rc = s3_prepare_v2_noom(p->dbUser,
"SELECT name FROM pragma_database_list",
-1, &pStmt, 0);
stmt_ptr_holder(&pStmt);
if( rc ){
*pzErr = smprintf("%s\n", sqlite3_errmsg(p->dbUser));
RESOURCE_FREE(mark);
return DCR_Error;
}
text_ref_holder(&sSelect);
appendText(&sSelect, "SELECT sql FROM", 0);
iSchema = 0;
while( s3_step_noom(pStmt)==SQLITE_ROW ){
const char *zDb = (const char*)sqlite3_column_text(pStmt, 0);
char zScNum[30];
sqlite3_snprintf(sizeof(zScNum), zScNum, "%d", ++iSchema);
appendText(&sSelect, zDiv, 0);
zDiv = " UNION ALL ";
appendText(&sSelect, "SELECT shell_add_schema(sql,", 0);
if( sqlite3_stricmp(zDb, "main")!=0 ){
appendText(&sSelect, zDb, '\'');
}else{
appendText(&sSelect, "NULL", 0);
}
appendText(&sSelect, ",name) AS sql, type, tbl_name, name, rowid,", 0);
appendText(&sSelect, zScNum, 0);
appendText(&sSelect, " AS snum, ", 0);
appendText(&sSelect, zDb, '\'');
appendText(&sSelect, " AS sname FROM ", 0);
appendText(&sSelect, zDb, quoteChar(zDb));
appendText(&sSelect, ".sqlite_schema", 0);
}
sqlite3_finalize(pStmt);
pStmt = 0;
#ifndef SQLITE_OMIT_INTROSPECTION_PRAGMAS
if( zName ){
appendText(&sSelect,
" UNION ALL SELECT shell_module_schema(name),"
" 'table', name, name, name, 9e+99, 'main'"
" FROM pragma_module_list",
0);
}
#endif
appendText(&sSelect, ") WHERE ", 0);
if( zName ){
char *zQarg = smprintf("%Q", zName);
int bGlob;
shell_check_ooms(zQarg);
sstr_holder(zQarg);
bGlob = strchr(zName, '*') != 0 || strchr(zName, '?') != 0
|| strchr(zName, '[') != 0;
if( strchr(zName, '.') ){
appendText(&sSelect, "lower(printf('%s.%s',sname,tbl_name))", 0);
}else{
appendText(&sSelect, "lower(tbl_name)", 0);
}
appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0);
appendText(&sSelect, zQarg, 0);
if( !bGlob ){
appendText(&sSelect, " ESCAPE '\\' ", 0);
}
appendText(&sSelect, " AND ", 0);
release_holder();
}
if( bNoSystemTabs ){
appendText(&sSelect, "name NOT LIKE 'sqlite_%%' AND ", 0);
}
appendText(&sSelect, "sql IS NOT NULL"
" ORDER BY snum, rowid", 0);
if( bDebug ){
utf8_printf(psi->out, "SQL: %s;\n", sSelect.z);
}else{
rc = sqlite3_exec(p->dbUser, sSelect.z, callback, p, &zErrMsg);
}
}
RESOURCE_FREE(mark);
if( zErrMsg ){
*pzErr = zErrMsg;
return DCR_Error;
}else if( rc != SQLITE_OK ){
*pzErr = smprintf("Error: querying schema information\n");
return DCR_Error;
}else{
return DCR_Ok;
}
}
/*****************
* The .selecttrace, .separator, .session and .sha3sum commands
*/
CONDITION_COMMAND( session defined(SQLITE_ENABLE_SESSION) );
COLLECT_HELP_TEXT[
".separator COL ?ROW? Change the column and row separators",
".session ?NAME? CMD ... Create or control sessions",
" Subcommands:",
" attach TABLE Attach TABLE",
" changeset FILE Write a changeset into FILE",
" close Close one session",
" enable ?BOOLEAN? Set or query the enable bit",
" filter GLOB... Reject tables matching GLOBs",
" indirect ?BOOLEAN? Mark or query the indirect status",
" isempty Query whether the session is empty",
" list List currently open session names",
" open DB NAME Open a new session on DB",
" patchset FILE Write a patchset into FILE",
" If ?NAME? is omitted, the first defined session is used.",
".sha3sum ... Compute a SHA3 hash of database content",
" Options:",
" --schema Also hash the sqlite_schema table",
" --sha3-224 Use the sha3-224 algorithm",
" --sha3-256 Use the sha3-256 algorithm (default)",
" --sha3-384 Use the sha3-384 algorithm",
" --sha3-512 Use the sha3-512 algorithm",
" Any other argument is a LIKE pattern for tables to hash",
];
DISPATCHABLE_COMMAND( separator ? 2 3 ){
if( nArg>=2 ){
sqlite3_snprintf(sizeof(ISS(p)->colSeparator), ISS(p)->colSeparator,
"%.*s", (int)ArraySize(ISS(p)->colSeparator)-1, azArg[1]);
}
if( nArg>=3 ){
sqlite3_snprintf(sizeof(ISS(p)->rowSeparator), ISS(p)->rowSeparator,
"%.*s", (int)ArraySize(ISS(p)->rowSeparator)-1, azArg[2]);
}
return DCR_Ok;
}
DISPATCHABLE_COMMAND( session 3 2 0 ){
int rc = 0;
struct AuxDb *pAuxDb = ISS(p)->pAuxDb;
OpenSession *pSession = &pAuxDb->aSession[0];
FILE *out = ISS(p)->out;
char **azCmd = &azArg[1];
int iSes = 0;
int nCmd = nArg - 1;
int i;
open_db(p, 0);
if( nArg>=3 ){
for(iSes=0; iSes<pAuxDb->nSession; iSes++){
if( cli_strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break;
}
if( iSes<pAuxDb->nSession ){
pSession = &pAuxDb->aSession[iSes];
azCmd++;
nCmd--;
}else{
pSession = &pAuxDb->aSession[0];
iSes = 0;
}
}
/* .session attach TABLE
** Invoke the sqlite3session_attach() interface to attach a particular
** table so that it is never filtered.
*/
if( cli_strcmp(azCmd[0],"attach")==0 ){
if( nCmd!=2 ) goto session_syntax_error;
if( pSession->p==0 ){
session_not_open:
raw_printf(STD_ERR, "ERROR: No sessions are open\n");
}else{
rc = sqlite3session_attach(pSession->p, azCmd[1]);
if( rc ){
raw_printf(STD_ERR, "ERROR: sqlite3session_attach() returns %d\n", rc);
rc = 0;
}
}
}else
/* .session changeset FILE
** .session patchset FILE
** Write a changeset or patchset into a file. The file is overwritten.
*/
if( cli_strcmp(azCmd[0],"changeset")==0
|| cli_strcmp(azCmd[0],"patchset")==0 ){
FILE *cs_out = 0;
if( failIfSafeMode
(p, "cannot run \".session %s\" in safe mode", azCmd[0]) ){
return DCR_AbortError;
}else{
if( nCmd!=2 ) goto session_syntax_error;
if( pSession->p==0 ) goto session_not_open;
cs_out = fopen(azCmd[1], "wb");
if( cs_out==0 ){
*pzErr = smprintf("cannot open \"%s\" for writing\n", azCmd[1]);
rc = 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 ){
fprintf(out, "Error: error code %d (swallowed)\n", rc);
rc = 0;
}
if( pChng && fwrite(pChng, szChng, 1, cs_out)!=1 ){
raw_printf(STD_ERR, "ERROR: Failed to write entire %d-byte output\n",
szChng);
rc = 1;
}
sqlite3_free(pChng);
fclose(cs_out);
}
}
}else
/* .session close
** Close the identified session
*/
if( cli_strcmp(azCmd[0], "close")==0 ){
if( nCmd!=1 ) goto session_syntax_error;
if( pAuxDb->nSession ){
session_close(pSession);
pAuxDb->aSession[iSes] = pAuxDb->aSession[--pAuxDb->nSession];
}
}else
/* .session enable ?BOOLEAN?
** Query or set the enable flag
*/
if( cli_strcmp(azCmd[0], "enable")==0 ){
int ii;
if( nCmd>2 ) goto session_syntax_error;
ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
if( pAuxDb->nSession ){
ii = sqlite3session_enable(pSession->p, ii);
utf8_printf(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( cli_strcmp(azCmd[0], "filter")==0 ){
int ii, nByte;
if( nCmd<2 ) goto session_syntax_error;
if( pAuxDb->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 );
memset(pSession->azFilter, 0, nByte);
shell_check_ooms(pSession->azFilter);
for(ii=1; ii<nCmd; ii++){
pSession->azFilter[ii-1] = smprintf("%s", azCmd[ii]);
shell_check_ooms(pSession->azFilter[ii-1]);
}
pSession->nFilter = ii-1;
}
}else
/* .session indirect ?BOOLEAN?
** Query or set the indirect flag
*/
if( cli_strcmp(azCmd[0], "indirect")==0 ){
int ii;
if( nCmd>2 ) goto session_syntax_error;
ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
if( pAuxDb->nSession ){
ii = sqlite3session_indirect(pSession->p, ii);
utf8_printf(out, "session %s indirect flag = %d\n",
pSession->zName, ii);
}
}else
/* .session isempty
** Determine if the session is empty
*/
if( cli_strcmp(azCmd[0], "isempty")==0 ){
int ii;
if( nCmd!=1 ) goto session_syntax_error;
if( pAuxDb->nSession ){
ii = sqlite3session_isempty(pSession->p);
utf8_printf(out, "session %s isempty flag = %d\n",
pSession->zName, ii);
}
}else
/* .session list
** List all currently open sessions
*/
if( cli_strcmp(azCmd[0],"list")==0 ){
for(i=0; i<pAuxDb->nSession; i++){
utf8_printf(out, "%d %s\n", i, pAuxDb->aSession[i].zName);
}
}else
/* .session open DB NAME
** Open a new session called NAME on the attached database DB.
** DB is normally "main".
*/
if( cli_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<pAuxDb->nSession; i++){
if( cli_strcmp(pAuxDb->aSession[i].zName,zName)==0 ){
utf8_printf(STD_ERR, "Session \"%s\" already exists\n", zName);
return DCR_Error;
}
}
if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){
raw_printf
(STD_ERR, "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession));
return DCR_Error;
}
pSession = &pAuxDb->aSession[pAuxDb->nSession];
rc = sqlite3session_create(DBX(p), azCmd[1], &pSession->p);
if( rc ){
*pzErr = smprintf("Cannot open session: error code=%d\n", rc);
return DCR_Error;
}
pSession->nFilter = 0;
sqlite3session_table_filter(pSession->p, session_filter, pSession);
pAuxDb->nSession++;
zName = smprintf("%s", zName);
shell_check_ooms(zName);
pSession->zName = zName;
}else{
/* If no command name matches, show a syntax error */
session_syntax_error:
showHelp(out, "session", p);
return DCR_CmdErred;
}
return DCR_Ok|(rc!=0);
}
DISPATCHABLE_COMMAND( sha3sum 4 1 1 ){
const char *zLike = 0; /* Which table to checksum. 0 means everything */
int i; /* Loop counter */
int bSchema = 0; /* Also hash the schema */
int bSeparate = 0; /* Hash each table separately */
int iSize = 224; /* Hash algorithm to use */
int bDebug = 0; /* Only show the query that would have run */
sqlite3_stmt *pStmt; /* For querying tables names */
char *zSql; /* SQL to be run */
char *zSep; /* Separator */
ShellText sSql; /* Complete SQL for the query to run the hash */
ShellText sQuery; /* Set of queries used to read all content */
RESOURCE_MARK(mark);
open_db(p, 0);
for(i=1; i<nArg; i++){
const char *z = azArg[i];
if( z[0]=='-' ){
z++;
if( z[0]=='-' ) z++;
if( cli_strcmp(z,"schema")==0 ){
bSchema = 1;
}else
if( cli_strcmp(z,"sha3-224")==0 || cli_strcmp(z,"sha3-256")==0
|| cli_strcmp(z,"sha3-384")==0 || cli_strcmp(z,"sha3-512")==0
){
iSize = atoi(&z[5]);
}else
if( cli_strcmp(z,"debug")==0 ){
bDebug = 1;
}else
{
*pzErr = smprintf("Unknown option \"%s\" on \"%s\"\n",
azArg[i], azArg[0]);
return DCR_Unknown|i;
}
}else if( zLike ){
return DCR_TooMany;
}else{
zLike = z;
bSeparate = 1;
if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 1;
}
}
if( bSchema ){
zSql = "SELECT lower(name) AS tname FROM sqlite_schema"
" WHERE type='table' AND coalesce(rootpage,0)>1"
" UNION ALL SELECT 'sqlite_schema'"
" ORDER BY 1 collate nocase";
}else{
zSql = "SELECT lower(name) AS tname FROM sqlite_schema"
" WHERE type='table' AND coalesce(rootpage,0)>1"
" AND name NOT LIKE 'sqlite_%'"
" ORDER BY 1 collate nocase";
}
s3_prepare_v2_noom(DBX(p), zSql, -1, &pStmt, 0);
stmt_ptr_holder(&pStmt); /* +1 */
initText(&sQuery);
text_ref_holder(&sQuery); /* +2 */
initText(&sSql);
text_ref_holder(&sSql); /* +3 */
appendText(&sSql, "WITH [sha3sum$query](a,b) AS(",0);
zSep = "VALUES(";
while( SQLITE_ROW==s3_step_noom(pStmt) ){
const char *zTab = (const char*)sqlite3_column_text(pStmt,0);
if( zTab==0 ) continue;
if( zLike && sqlite3_strlike(zLike, zTab, 0)!=0 ) continue;
if( cli_strncmp(zTab, "sqlite_",7)!=0 ){
appendText(&sQuery,"SELECT * FROM ", 0);
appendText(&sQuery,zTab,'"');
appendText(&sQuery," NOT INDEXED;", 0);
}else if( cli_strcmp(zTab, "sqlite_schema")==0 ){
appendText(&sQuery,"SELECT type,name,tbl_name,sql FROM sqlite_schema"
" ORDER BY name;", 0);
}else if( cli_strcmp(zTab, "sqlite_sequence")==0 ){
appendText(&sQuery,"SELECT name,seq FROM sqlite_sequence"
" ORDER BY name;", 0);
}else if( cli_strcmp(zTab, "sqlite_stat1")==0 ){
appendText(&sQuery,"SELECT tbl,idx,stat FROM sqlite_stat1"
" ORDER BY tbl,idx;", 0);
}else if( cli_strcmp(zTab, "sqlite_stat4")==0 ){
appendText(&sQuery, "SELECT * FROM ", 0);
appendText(&sQuery, zTab, 0);
appendText(&sQuery, " ORDER BY tbl, idx, rowid;\n", 0);
}
appendText(&sSql, zSep, 0);
appendText(&sSql, sQuery.z, '\'');
sQuery.n = 0;
appendText(&sSql, ",", 0);
appendText(&sSql, zTab, '\'');
zSep = "),(";
}
if( bSeparate ){
zSql = smprintf(
"%s))"
" SELECT lower(hex(sha3_query(a,%d))) AS hash, b AS label"
" FROM [sha3sum$query]",
sSql.z, iSize);
}else{
zSql = smprintf(
"%s))"
" SELECT lower(hex(sha3_query(group_concat(a,''),%d))) AS hash"
" FROM [sha3sum$query]",
sSql.z, iSize);
}
release_holders(mark);
shell_check_ooms(zSql);
sstr_holder(zSql); /* +1 */
if( bDebug ){
utf8_printf(ISS(p)->out, "%s\n", zSql);
}else{
shell_exec(p, zSql, 0);
}
release_holder(); /* 0 */
#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) && !defined(SQLITE_OMIT_VIRTUALTABLE)
{
int lrc;
char *zRevText = /* Query for reversible to-blob-to-text check */
"SELECT lower(name) as tname FROM sqlite_schema\n"
"WHERE type='table' AND coalesce(rootpage,0)>1\n"
"AND name NOT LIKE 'sqlite_%%'%s\n"
"ORDER BY 1 collate nocase";
zRevText = smprintf(zRevText, zLike? " AND name LIKE $tspec" : "");
zRevText = smprintf(
/* lower-case query is first run, producing upper-case query. */
"with tabcols as materialized(\n"
"select tname, cname\n"
"from ("
" select printf('\"%%w\"',ss.tname) as tname,"
" printf('\"%%w\"',ti.name) as cname\n"
" from (%z) ss\n inner join pragma_table_info(tname) ti))\n"
"select 'SELECT total(bad_text_count) AS bad_text_count\n"
"FROM ('||group_concat(query, ' UNION ALL ')||')' as btc_query\n"
" from (select 'SELECT COUNT(*) AS bad_text_count\n"
"FROM '||tname||' WHERE '\n"
"||group_concat('CAST(CAST('||cname||' AS BLOB) AS TEXT)<>'||cname\n"
"|| ' AND typeof('||cname||')=''text'' ',\n"
"' OR ') as query, tname from tabcols group by tname)"
, zRevText);
if( bDebug ) utf8_printf(ISS(p)->out, "%s\n", zRevText);
lrc = s3_prep_noom_free(DBX(p), &zRevText, &pStmt);
if( lrc!=SQLITE_OK ){
RESOURCE_FREE(mark);
return DCR_Error;
}
stmt_holder(pStmt);
if( zLike ) sqlite3_bind_text(pStmt,1,zLike,-1,SQLITE_STATIC);
lrc = SQLITE_ROW==s3_step_noom(pStmt);
if( lrc ){
const char *zGenQuery = (char*)sqlite3_column_text(pStmt,0);
sqlite3_stmt *pCheckStmt;
lrc = s3_prepare_v2_noom(DBX(p), zGenQuery, -1, &pCheckStmt, 0);
if( bDebug ) utf8_printf(ISS(p)->out, "%s\n", zGenQuery);
stmt_holder(pCheckStmt);
if( SQLITE_OK!=lrc ){
RESOURCE_FREE(mark);
return DCR_Error;
}else{
if( SQLITE_ROW==sqlite3_step(pCheckStmt) ){
double countIrreversible = sqlite3_column_double(pCheckStmt, 0);
if( countIrreversible>0 ){
int sz = (int)(countIrreversible + 0.5);
utf8_printf(stderr,
"Digest includes %d invalidly encoded text field%s.\n",
sz, (sz>1)? "s": "");
}
}
}
}
}
#endif /* !defined(*_OMIT_SCHEMA_PRAGMAS) && !defined(*_OMIT_VIRTUALTABLE) */
RESOURCE_FREE(mark);
return DCR_Ok;
}
/*****************
* The .selftest* and .show commands
*/
CONDITION_COMMAND( selftest_bool defined(SQLITE_DEBUG) );
CONDITION_COMMAND( selftest_int defined(SQLITE_DEBUG) );
COLLECT_HELP_TEXT[
",selftest ?OPTIONS? Run tests defined in the SELFTEST table",
" Options:",
" --init Create a new SELFTEST table",
" -v Verbose output",
",selftest_bool ?ARGS? Show boolean values of ARGS as flag tokens",
",selftest_int ?ARGS? Show integer values of ARGS as integer tokens",
".show Show the current values for various settings",
];
DISPATCHABLE_COMMAND( selftest_bool 10 0 0 ){
int i, v;
for(i=1; i<nArg; i++){
v = booleanValue(azArg[i]);
utf8_printf(ISS(p)->out, "%s: %d 0x%x\n", azArg[i], v, v);
}
return DCR_Ok;
}
DISPATCHABLE_COMMAND( selftest_int 10 0 0 ){
int i; sqlite3_int64 v;
for(i=1; i<nArg; i++){
char zBuf[200];
v = integerValue(azArg[i]);
sqlite3_snprintf(sizeof(zBuf),zBuf,"%s: %lld 0x%llx\n", azArg[i],v,v);
utf8_printf(ISS(p)->out, "%s", zBuf);
}
return DCR_Ok;
}
DISPATCHABLE_COMMAND( selftest 4 0 0 ){
int rc = 0;
ShellInState *psi = ISS(p);
int bIsInit = 0; /* True to initialize the SELFTEST table */
int bVerbose = 0; /* Verbose output */
int bSelftestExists; /* True if SELFTEST already exists */
int i, k; /* Loop counters */
int nTest = 0; /* Number of tests runs */
int nErr = 0; /* Number of errors seen */
RESOURCE_MARK(mark);
ShellText str = {0}; /* Answer for a query */
sqlite3_stmt *pStmt = 0; /* Query against the SELFTEST table */
for(i=1; i<nArg; i++){
const char *z = azArg[i];
if( z[0]=='-' && z[1]=='-' ) z++;
if( cli_strcmp(z,"-init")==0 ){
bIsInit = 1;
}else
if( cli_strcmp(z,"-v")==0 ){
bVerbose++;
}else
{
*pzErr = smprintf
("Unknown option \"%s\" on \"%s\"\n"
"Should be one of: --init -v\n", azArg[i], azArg[0]);
return DCR_ArgWrong;
}
}
open_db(p,0);
if( sqlite3_table_column_metadata(DBX(p),"main","selftest",0,0,0,0,0,0)
!= SQLITE_OK ){
bSelftestExists = 0;
}else{
bSelftestExists = 1;
}
if( bIsInit ){
createSelftestTable(ISS(p));
bSelftestExists = 1;
}
initText(&str);
text_ref_holder(&str);
appendText(&str, "x", 0);
stmt_ptr_holder(&pStmt);
for(k=bSelftestExists; k>=0; k--){
if( k==1 ){
rc = s3_prepare_v2_noom(DBX(p),
"SELECT tno,op,cmd,ans FROM selftest ORDER BY tno",
-1, &pStmt, 0);
}else{
rc = s3_prepare_v2_noom(DBX(p),
"VALUES(0,'memo','Missing SELFTEST table - default checks only',''),"
" (1,'run','PRAGMA integrity_check','ok')",
-1, &pStmt, 0);
}
if( rc ){
*pzErr = smprintf("Error querying the selftest table\n");
RESOURCE_FREE(mark);
return DCR_Error;
}
for(i=1; s3_step_noom(pStmt)==SQLITE_ROW; i++){
int tno = sqlite3_column_int(pStmt, 0);
const char *zOp = (const char*)sqlite3_column_text(pStmt, 1);
const char *zSql = (const char*)sqlite3_column_text(pStmt, 2);
const char *zAns = (const char*)sqlite3_column_text(pStmt, 3);
if( zOp==0 || zSql==0 || zAns==0 ) continue;
k = 0;
if( bVerbose>0 ){
/* This unusually directed output is for test purposes. */
fprintf(STD_OUT, "%d: %s %s\n", tno, zOp, zSql);
}
if( cli_strcmp(zOp,"memo")==0 ){
utf8_printf(psi->out, "%s\n", zSql);
}else if( cli_strcmp(zOp,"run")==0 ){
char *zErrMsg = 0;
sstr_ptr_holder(&zErrMsg);
str.n = 0;
str.z[0] = 0;
rc = sqlite3_exec(DBX(p), zSql, captureOutputCallback, &str, &zErrMsg);
nTest++;
if( bVerbose ){
utf8_printf(psi->out, "Result: %s\n", str.z);
}
if( rc || zErrMsg ){
nErr++;
rc = 1;
utf8_printf(psi->out, "%d: error-code-%d: %s\n", tno, rc,
(zErrMsg)? zErrMsg : "");
}else if( cli_strcmp(zAns,str.z)!=0 ){
nErr++;
rc = 1;
utf8_printf(psi->out, "%d: Expected: [%s]\n", tno, zAns);
utf8_printf(psi->out, "%d: Got: [%s]\n", tno, str.z);
}
release_holder();
}else{
*pzErr = smprintf
("Unknown operation \"%s\" on selftest line %d\n", zOp, tno);
rc = 1;
break;
}
} /* End loop over rows of content from SELFTEST */
} /* End loop over k */
RESOURCE_FREE(mark);
utf8_printf(psi->out, "%d errors out of %d tests\n", nErr, nTest);
return rc > 0;
}
/*****************
* The .shell and .system commands
*/
#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
# define SHELLOUT_ENABLE 1
#else
# define SHELLOUT_ENABLE 0
#endif
CONDITION_COMMAND( shell SHELLOUT_ENABLE );
CONDITION_COMMAND( system SHELLOUT_ENABLE );
COLLECT_HELP_TEXT[
".shell CMD ARGS... Run CMD ARGS... in a system shell",
".system CMD ARGS... Run CMD ARGS... in a system shell",
];
#if SHELLOUT_ENABLE
static DotCmdRC shellOut(char *azArg[], int nArg,
ShellExState *psx, char **pzErr){
char *zCmd = 0;
int i, x;
if( ISS(psx)->bSafeMode ) return DCR_AbortError;
zCmd = smprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]);
for(i=2; i<nArg; i++){
zCmd = smprintf(strchr(azArg[i],' ')==0?"%z %s":"%z \"%s\"",
zCmd, azArg[i]);
}
shell_check_ooms(zCmd);
sstr_holder(zCmd);
x = system(zCmd);
release_holder();
if( x ) raw_printf(STD_ERR, "%s command returns %d\n", azArg[0], x);
return DCR_Ok;
}
#endif
DISPATCHABLE_COMMAND( shell ? 2 0 ){
return shellOut(azArg, nArg, p, pzErr);
}
DISPATCHABLE_COMMAND( system ? 2 0 ){
return shellOut(azArg, nArg, p, pzErr);
}
/*****************
* The .shxload and .shxopts commands
*/
CONDITION_COMMAND( shxload (SHELL_DYNAMIC_EXTENSION)!=0 );
CONDITION_COMMAND( shxopts (SHELL_EXTENSIONS)!=0 );
COLLECT_HELP_TEXT[
".shxload FILE ?OPTIONS? Load a CLI shell extension library",
" The first option may name the init function to be called upon load.",
" Otherwise, its name is derived from FILE. Either way, the entry point"
" \"sqlite_NAME_init\" is called. All options after \"--\" are passed to",
" the extension's init function in the ShellExtensionLink struct.",
".shxopts ?SIGNED_OPTS? Show or alter shell extension options",
" Run without arguments to see their self-descriptive names",
];
DISPATCHABLE_COMMAND( shxload 4 2 0 ){
const char *zFile = 0, *zProc = 0;
int ai = 1, rc;
char **pzExtArgs = 0;
if( ISS(p)->bSafeMode ) return DCR_AbortError;
while( ai<nArg ){
const char *zA = azArg[ai++];
if( cli_strcmp(zA, "--")==0 ){
pzExtArgs = azArg + ai;
break;
}
if( zFile==0 ) zFile = zA;
else if( zProc==0 ) zProc = zA;
}
if( zFile==0 ) return DCR_Missing;
if( pzExtArgs==0 ) pzExtArgs = azArg + ai;
open_db(p, 0);
rc = load_shell_extension(p, zFile, zProc, pzErr, nArg-ai, pzExtArgs);
return DCR_Ok|(rc!=SQLITE_OK);
}
DISPATCHABLE_COMMAND( shxopts 3 0 0 ){
static struct { const char *name; u8 mask; } shopts[] = {
#if SHELL_DYNAMIC_COMMANDS
{"dyn_cmds", 1<<SHEXT_DYNCMDS_BIT},
#endif
#if SHELL_EXTENDED_PARSING
{"parsing", 1<<SHEXT_PARSING_BIT},
#endif
#if SHELL_VARIABLE_EXPANSION
{"dot_vars", 1<<SHEXT_VAREXP_BIT},
#endif
{"all_opts", SHELL_ALL_EXTENSIONS}
};
const char *zMoan = 0, *zAbout = 0;
ShellInState *psi = ISS(p);
int ia, io;
if( nArg>1 ){
for( ia=1; ia<nArg; ++ia ){
char cs = azArg[ia][0];
if( cs!='+' && cs!='-' ){
zMoan = "arguments must have a sign prefix.";
zAbout = azArg[0];
goto moan_error;
}
for( io=0; io<ArraySize(shopts); ++io ){
if( cli_strcmp(azArg[ia]+1, shopts[io].name)==0 ){
if( cs=='+' ) psi->bExtendedDotCmds |= shopts[io].mask;
else psi->bExtendedDotCmds &= ~shopts[io].mask;
break;
}
}
if( io==ArraySize(shopts) ){
zAbout = azArg[ia];
zMoan = "is not a recognized option name";
goto moan_error;
}
}
}else{
raw_printf(psi->out,
" name value \"-shxopts set\"\n"
" -------- ----- ---------------\n");
for( io=0; io<ArraySize(shopts); ++io ){
unsigned m = shopts[io].mask;
unsigned v = ((psi->bExtendedDotCmds & m) == m)? 1 : 0;
raw_printf(psi->out,
" %9s %2d \"-shxopts 0x%02X\"\n",
shopts[io].name, v, m);
}
}
return DCR_Ok;
moan_error:
*pzErr = smprintf("Error: %s %s\n", zAbout, zMoan);
return DCR_CmdErred;
}
static void showWidths(ShellExState *p){
int i = 0;
while( i < p->numWidths ){
raw_printf(ISS(p)->out," %d",p->pSpecWidths[i++]);
}
raw_printf(ISS(p)->out,"\n");
}
DISPATCHABLE_COMMAND( show ? 1 1 ){
static const char *azBool[] = { "off", "on", "trigger", "full"};
const char *zOut;
ShellInState *psi = ISS(p);
FILE *out = psi->out;
utf8_printf(out, "%12.12s: %s\n","echo",
azBool[ShellHasFlag(p, SHFLG_Echo)]);
utf8_printf(out, "%12.12s: %s\n","eqp", azBool[psi->autoEQP&3]);
utf8_printf(out, "%12.12s: %s\n","explain",
psi->mode==MODE_Explain
? "on" : psi->autoExplain ? "auto" : "off");
utf8_printf(out,"%12.12s: %s\n","headers", azBool[psi->showHeader!=0]);
#if SHELL_DATAIO_EXT
{
char *zTell = 0;
int mrc;
zOut = psi->pActiveExporter->pMethods->name(psi->pActiveExporter);
mrc = psi->pActiveExporter->pMethods->config(psi->pActiveExporter,0,&zTell);
if( zTell!=0 ){
utf8_printf(out, "%12.12s: %s %s\n", "mode", zOut, zTell);
}else{
utf8_printf(out, "%12.12s: %s\n", "mode", zOut);
}
sqlite3_free(zTell);
}
#else
zOut = modeDescr[psi->mode].zModeName;
if( MODE_IS_COLUMNAR(psi->mode) ){
utf8_printf(out, "%12.12s: %s --wrap %d --wordwrap %s --%squote\n", "mode",
zOut, psi->cmOpts.iWrap,
psi->cmOpts.bWordWrap ? "on" : "off",
psi->cmOpts.bQuote ? "" : "no");
}else{
utf8_printf(out, "%12.12s: %s\n","mode", zOut);
}
#endif
utf8_printf(out, "%12.12s: ", "nullvalue");
output_c_string(out, psi->nullValue);
raw_printf(out, "\n");
utf8_printf(out,"%12.12s: %s\n","output",
strlen30(psi->outfile) ? psi->outfile : "stdout");
utf8_printf(out,"%12.12s: ", "colseparator");
output_c_string(out, psi->colSeparator);
raw_printf(out, "\n");
utf8_printf(out,"%12.12s: ", "rowseparator");
output_c_string(out, psi->rowSeparator);
raw_printf(out, "\n");
switch( psi->statsOn ){
case 0: zOut = "off"; break;
default: zOut = "on"; break;
case 2: zOut = "stmt"; break;
case 3: zOut = "vmstep"; break;
}
utf8_printf(out, "%12.12s: %s\n","stats", zOut);
utf8_printf(out, "%12.12s:", "width");
showWidths(p);
utf8_printf(out, "%12.12s: %s\n", "filename",
psi->pAuxDb->zDbFilename ? psi->pAuxDb->zDbFilename : "");
return DCR_Ok;
}
/*****************
* The .stats command
*/
COLLECT_HELP_TEXT[
".stats ?ARG? Show stats or turn stats on or off",
" off Turn off automatic stat display",
" on Turn on automatic stat display",
" stmt Show statement stats",
" vmstep Show the virtual machine step count only",
];
DISPATCHABLE_COMMAND( stats ? 0 0 ){
ShellInState *psi = ISS(p);
if( nArg==2 ){
if( cli_strcmp(azArg[1],"stmt")==0 ){
psi->statsOn = 2;
}else if( cli_strcmp(azArg[1],"vmstep")==0 ){
psi->statsOn = 3;
}else{
psi->statsOn = (u8)booleanValue(azArg[1]);
}
}else if( nArg==1 ){
display_stats(DBX(p), psi, 0);
}else{
*pzErr = smprintf("Usage: .stats ?on|off|stmt|vmstep?\n");
return DCR_SayUsage;
}
return DCR_Ok;
}
/*****************
* The .tables, .views, .indices and .indexes commands
* These are together because they share implementation or are aliases.
*/
COLLECT_HELP_TEXT[
".indexes ?TABLE? Show names of indexes",
" If TABLE is specified, only show indexes for",
" tables matching TABLE using the LIKE operator.",
];
static int showTableLike(char *azArg[], int nArg, ShellExState *p,
char **pzErr, char ot){
int rc;
sqlite3_stmt *pStmt = 0;
ShellText s = {0};
char **azResult = 0;
AnyResourceHolder arh = { 0, (GenericFreer)freeNameList };
int nRow, nAlloc;
int ii;
RESOURCE_MARK(mark);
initText(&s);
text_ref_holder(&s);
open_db(p, 0);
rc = sqlite3_prepare_v2(DBX(p), "PRAGMA database_list", -1, &pStmt, 0);
stmt_ptr_holder(&pStmt);
if( shell_check_nomem(rc) ){
db_err_bail:
RESOURCE_FREE(mark);
return shellDatabaseError(DBX(p));
}
if( nArg>2 && ot=='i' ){
/* 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. */
*pzErr = smprintf("Usage: .indexes ?LIKE-PATTERN?\n");
RESOURCE_FREE(mark);
return DCR_SayUsage;
}
for(ii=0; sqlite3_step(pStmt)==SQLITE_ROW; ii++){
const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1);
const char *zFilter = "";
const char *zSystem = " AND name NOT LIKE 'sqlite_%'";
if( zDbName==0 ) continue;
if( s.z && s.z[0] ) appendText(&s, " UNION ALL ", 0);
if( sqlite3_stricmp(zDbName, "main")==0 ){
appendText(&s, "SELECT name FROM ", 0);
}else{
appendText(&s, "SELECT ", 0);
appendText(&s, zDbName, '\'');
appendText(&s, "||'.'||name FROM ", 0);
}
appendText(&s, zDbName, '"');
appendText(&s, ".sqlite_schema ", 0);
switch (ot) {
case 'i':
zFilter = "'index'";
break;
#ifndef LEGACY_TABLES_LISTING
case 't':
zFilter = "'table'";
break;
case 'v':
zFilter = "'view'";
break;
#endif
case 's':
zSystem = " AND name LIKE 'sqlite_%'";
deliberate_fall_through;
case 'T':
zFilter = "'table','view'";
break;
default:
assert(0);
}
appendText(&s, " WHERE type IN(", 0);
appendText(&s, zFilter, 0);
appendText(&s, ") AND name LIKE ?1", 0);
appendText(&s, zSystem, 0);
}
rc = sqlite3_finalize(pStmt);
pStmt = 0;
if( rc==SQLITE_OK ){
appendText(&s, " ORDER BY 1", 0);
rc = s3_prepare_v2_noom(DBX(p), s.z, -1, &pStmt, 0);
}
if( rc ) goto db_err_bail;
/* Run the SQL statement prepared by the above block. Store the results
** as an array of nul-terminated strings in azResult[]. The 0th element
** of azResult[] is not used so that freeNameList() can free it. */
nRow = nAlloc = 0;
azResult = 0;
if( nArg>1 ){
sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT);
}else{
sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC);
}
any_ref_holder(&arh);
while( s3_step_noom(pStmt)==SQLITE_ROW ){
if( nRow+2 > nAlloc ){
char **azNew;
int n2 = nAlloc*2 + 10;
azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2);
shell_check_ooms(azNew);
/* Keep the object usable by freeNameList at all times. */
memset(azNew+nAlloc, 0, (n2-nAlloc)*sizeof(azResult[0]));
nAlloc = n2;
arh.pAny = azNew;
azResult = azNew;
}
++nRow;
azResult[nRow] = smprintf("%s", sqlite3_column_text(pStmt, 0));
shell_check_ooms(azResult[nRow]);
}
if( sqlite3_finalize(pStmt)!=SQLITE_OK ){
pStmt = 0;
goto db_err_bail;
}
pStmt = 0;
/* Pretty-print the contents of array azResult[] to the output */
if( rc==0 && nRow>0 ){
int len, maxlen = 0;
int i, j;
int nPrintCol, nPrintRow;
for(i=1; i<=nRow; i++){
len = strlen30(azResult[i]);
if( len>maxlen ) maxlen = len;
}
nPrintCol = 80/(maxlen+2);
if( nPrintCol<1 ) nPrintCol = 1;
nPrintRow = (nRow + nPrintCol - 1)/nPrintCol;
for(i=1; i<=nPrintRow; i++){
for(j=i; j<=nRow; j+=nPrintRow){
char *zSp = j<=nPrintRow ? "" : " ";
utf8_printf(ISS(p)->out, "%s%-*s", zSp, maxlen,
azResult[j] ? azResult[j]:"");
}
raw_printf(ISS(p)->out, "\n");
}
}
RESOURCE_FREE(mark);
return DCR_Ok;
}
COLLECT_HELP_TEXT[
#ifndef LEGACY_TABLES_LISTING
".tables ?FLAG? ?TVLIKE? List names of tables and/or views",
" FLAG may be -t, -v or -s to list only tables, views or system tables",
" TVLIKE may restrict the listing to names matching given LIKE pattern",
#else
".tables ?TABLE? List names of tables matching LIKE pattern TABLE",
#endif
];
DISPATCHABLE_COMMAND( tables 2 1 3 ){
char objType = 'T';
#ifndef LEGACY_TABLES_LISTING
if( nArg>1 && azArg[1][0]=='-' && azArg[1][1]!=0 && azArg[1][2]==0 ){
char c = azArg[1][1];
switch (c){
case 's':
case 't':
case 'v':
objType = c;
++azArg;
--nArg;
break;
default:
return DCR_Unknown|1;
}
}
#endif
return showTableLike(azArg, nArg, p, pzErr, objType);
}
DISPATCHABLE_COMMAND( indexes 3 1 2 ){
return showTableLike(azArg, nArg, p, pzErr, 'i');
}
DISPATCHABLE_COMMAND( indices 3 1 2 ){
return showTableLike(azArg, nArg, p, pzErr, 'i');
}
/*****************
* The .selecttrace, .treetrace and .wheretrace commands (undocumented)
*/
static DotCmdRC setTrace( char *azArg[], int nArg, ShellExState *psx, int ts ){
unsigned int x = nArg>1 ? (unsigned int)integerValue(azArg[1]) : ~0;
sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, ts, &x);
return DCR_Ok;
}
DISPATCHABLE_COMMAND( selecttrace 0 1 2 ){
return setTrace(azArg, nArg, p, 1);
}
DISPATCHABLE_COMMAND( treetrace 0 1 2 ){
return setTrace(azArg, nArg, p, 1);
}
DISPATCHABLE_COMMAND( wheretrace 0 1 2 ){
return setTrace(azArg, nArg, p, 3);
}
/*****************
* The .testcase, .testctrl, .timeout, .timer and .trace commands
*/
CONDITION_COMMAND( testcase !defined(SQLITE_SHELL_FIDDLE) );
CONDITION_COMMAND( testctrl !defined(SQLITE_UNTESTABLE) );
CONDITION_COMMAND( trace !defined(SQLITE_OMIT_TRACE) );
COLLECT_HELP_TEXT[
",testcase NAME Begin redirecting output to 'testcase-out.txt'",
",testctrl CMD ... Run various sqlite3_test_control() operations",
" Run \".testctrl\" with no arguments for details",
".timeout MS Try opening locked tables for MS milliseconds",
".timer on|off Turn SQL timer on or off",
".trace ?OPTIONS? Output each SQL statement as it is run",
" FILE Send output to FILE",
" stdout Send output to stdout",
" stderr Send output to stderr",
" off Disable tracing",
" --expanded Expand query parameters",
#ifdef SQLITE_ENABLE_NORMALIZE
" --normalized Normal the SQL statements",
#endif
" --plain Show SQL as it is input",
" --stmt Trace statement execution (SQLITE_TRACE_STMT)",
" --profile Profile statements (SQLITE_TRACE_PROFILE)",
" --row Trace each row (SQLITE_TRACE_ROW)",
" --close Trace connection close (SQLITE_TRACE_CLOSE)",
];
/* Begin redirecting output to the file "testcase-out.txt" */
DISPATCHABLE_COMMAND( testcase ? 0 0 ){
ShellInState *psi = ISS(p);
output_reset(psi);
psi->out = output_file_open("testcase-out.txt", 0);
if( psi->out==0 ){
raw_printf(STD_ERR, "Error: cannot open 'testcase-out.txt'\n");
}
if( nArg>=2 ){
sqlite3_snprintf(sizeof(psi->zTestcase), psi->zTestcase, "%s", azArg[1]);
}else{
sqlite3_snprintf(sizeof(psi->zTestcase), psi->zTestcase, "?");
}
return DCR_Ok;
}
DISPATCHABLE_COMMAND( testctrl ? 0 0 ){
FILE *out = ISS(p)->out;
static const struct {
const char *zCtrlName; /* Name of a test-control option */
int ctrlCode; /* Integer code for that option */
int unSafe; /* Not valid unless --unsafe-testing */
const char *zUsage; /* Usage notes */
} aCtrl[] = {
{"always", SQLITE_TESTCTRL_ALWAYS, 1, "BOOLEAN" },
{"assert", SQLITE_TESTCTRL_ASSERT, 1, "BOOLEAN" },
/*{"benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS,1, "" },*/
/*{"bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST, 1, "" },*/
{"byteorder", SQLITE_TESTCTRL_BYTEORDER, 0, "" },
{"extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,0,"BOOLEAN" },
/*{"fault_install", SQLITE_TESTCTRL_FAULT_INSTALL, 1,"" },*/
{"imposter", SQLITE_TESTCTRL_IMPOSTER,1,"SCHEMA ON/OFF ROOTPAGE"},
{"internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS,0,"" },
{"localtime_fault", SQLITE_TESTCTRL_LOCALTIME_FAULT,0,"BOOLEAN" },
{"never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT,1, "BOOLEAN" },
{"optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS,0,"DISABLE-MASK" },
#ifdef YYCOVERAGE
{"parser_coverage", SQLITE_TESTCTRL_PARSER_COVERAGE,0,"" },
#endif
{"pending_byte", SQLITE_TESTCTRL_PENDING_BYTE,0, "OFFSET " },
{"prng_restore", SQLITE_TESTCTRL_PRNG_RESTORE,0, "" },
{"prng_save", SQLITE_TESTCTRL_PRNG_SAVE, 0, "" },
{"prng_seed", SQLITE_TESTCTRL_PRNG_SEED, 0, "SEED ?db?" },
{"seek_count", SQLITE_TESTCTRL_SEEK_COUNT, 0, "" },
{"sorter_mmap", SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX" },
{"tune", SQLITE_TESTCTRL_TUNE, 1, "ID VALUE" },
};
int testctrl = -1;
int iCtrl = -1;
int rc2 = 0; /* 0: usage. 1: %d 2: %x 3: no-output */
int isOk = 0;
int i, n2;
const char *zCmd = 0;
open_db(p, 0);
zCmd = nArg>=2 ? azArg[1] : "help";
/* The argument can optionally begin with "-" or "--" */
if( zCmd[0]=='-' && zCmd[1] ){
zCmd++;
if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
}
/* --help lists all test-controls */
if( cli_strcmp(zCmd,"help")==0 ){
utf8_printf(out, "Available test-controls:\n");
for(i=0; i<ArraySize(aCtrl); i++){
if( aCtrl[i].unSafe && !ShellHasFlag(p,SHFLG_TestingMode) ) continue;
utf8_printf(out, " .testctrl %s %s\n",
aCtrl[i].zCtrlName, aCtrl[i].zUsage);
}
return DCR_CmdErred;
}
/* convert testctrl text option to value. allow any unique prefix
** of the option name, or a numerical value. */
n2 = strlen30(zCmd);
for(i=0; i<ArraySize(aCtrl); i++){
if( aCtrl[i].unSafe && !ShellHasFlag(p,SHFLG_TestingMode) ) continue;
if( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
if( testctrl<0 ){
testctrl = aCtrl[i].ctrlCode;
iCtrl = i;
}else{
*pzErr = smprintf
("Error: ambiguous test-control: \"%s\"\n"
"Use \".testctrl --help\" for help\n", zCmd);
return DCR_ArgWrong;
}
}
}
if( testctrl<0 ){
utf8_printf(STD_ERR,"Error: unknown test-control: %s\n"
"Use \".testctrl --help\" for help\n", zCmd);
}else{
switch(testctrl){
/* sqlite3_test_control(int, db, int) */
case SQLITE_TESTCTRL_OPTIMIZATIONS:
if( nArg==3 ){
unsigned int opt = (unsigned int)strtol(azArg[2], 0, 0);
rc2 = sqlite3_test_control(testctrl, DBX(p), opt);
isOk = 3;
}
break;
/* sqlite3_test_control(int) */
case SQLITE_TESTCTRL_PRNG_SAVE:
case SQLITE_TESTCTRL_PRNG_RESTORE:
case SQLITE_TESTCTRL_BYTEORDER:
if( nArg==2 ){
rc2 = sqlite3_test_control(testctrl);
isOk = testctrl==SQLITE_TESTCTRL_BYTEORDER ? 1 : 3;
}
break;
/* sqlite3_test_control(int, uint) */
case SQLITE_TESTCTRL_PENDING_BYTE:
if( nArg==3 ){
unsigned int opt = (unsigned int)integerValue(azArg[2]);
rc2 = sqlite3_test_control(testctrl, opt);
isOk = 3;
}
break;
/* sqlite3_test_control(int, int, sqlite3*) */
case SQLITE_TESTCTRL_PRNG_SEED:
if( nArg==3 || nArg==4 ){
int ii = (int)integerValue(azArg[2]);
sqlite3 *db;
if( ii==0 && cli_strcmp(azArg[2],"random")==0 ){
sqlite3_randomness(sizeof(ii),&ii);
fprintf(STD_OUT, "-- random seed: %d\n", ii);
}
if( nArg==3 ){
db = 0;
}else{
db = DBX(p);
/* Make sure the schema has been loaded */
sqlite3_table_column_metadata(db, 0, "x", 0, 0, 0, 0, 0, 0);
}
rc2 = sqlite3_test_control(testctrl, ii, db);
isOk = 3;
}
break;
/* sqlite3_test_control(int, int) */
case SQLITE_TESTCTRL_ASSERT:
case SQLITE_TESTCTRL_ALWAYS:
if( nArg==3 ){
int opt = booleanValue(azArg[2]);
rc2 = sqlite3_test_control(testctrl, opt);
isOk = 1;
}
break;
/* sqlite3_test_control(int, int) */
case SQLITE_TESTCTRL_LOCALTIME_FAULT:
case SQLITE_TESTCTRL_NEVER_CORRUPT:
if( nArg==3 ){
int opt = booleanValue(azArg[2]);
rc2 = sqlite3_test_control(testctrl, opt);
isOk = 3;
}
break;
/* sqlite3_test_control(sqlite3*) */
case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS:
rc2 = sqlite3_test_control(testctrl, DBX(p));
isOk = 3;
break;
case SQLITE_TESTCTRL_IMPOSTER:
if( nArg==5 ){
rc2 = sqlite3_test_control(testctrl, DBX(p),
azArg[2],
integerValue(azArg[3]),
integerValue(azArg[4]));
isOk = 3;
}
break;
case SQLITE_TESTCTRL_SEEK_COUNT: {
u64 x = 0;
rc2 = sqlite3_test_control(testctrl, DBX(p), &x);
utf8_printf(out, "%llu\n", x);
isOk = 3;
break;
}
#ifdef YYCOVERAGE
case SQLITE_TESTCTRL_PARSER_COVERAGE: {
if( nArg==2 ){
sqlite3_test_control(testctrl, out);
isOk = 3;
}
break;
}
#endif
#ifdef SQLITE_DEBUG
case SQLITE_TESTCTRL_TUNE: {
if( nArg==4 ){
int id = (int)integerValue(azArg[2]);
int val = (int)integerValue(azArg[3]);
sqlite3_test_control(testctrl, id, &val);
isOk = 3;
}else if( nArg==3 ){
int id = (int)integerValue(azArg[2]);
sqlite3_test_control(testctrl, -id, &rc2);
isOk = 1;
}else if( nArg==2 ){
int id = 1;
while(1){
int val = 0;
rc2 = sqlite3_test_control(testctrl, -id, &val);
if( rc2!=SQLITE_OK ) break;
if( id>1 ) utf8_printf(out, " ");
utf8_printf(out, "%d: %d", id, val);
id++;
}
if( id>1 ) utf8_printf(out, "\n");
isOk = 3;
}
break;
}
#endif
}
}
if( isOk==0 && iCtrl>=0 ){
utf8_printf(out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
return DCR_CmdErred;
}else if( isOk==1 ){
raw_printf(out, "%d\n", rc2);
}else if( isOk==2 ){
raw_printf(out, "0x%08x\n", rc2);
}
return DCR_Ok;
}
DISPATCHABLE_COMMAND( timeout 4 1 2 ){
open_db(p, 0);
sqlite3_busy_timeout(DBX(p), nArg>=2 ? (int)integerValue(azArg[1]) : 0);
return DCR_Ok;
}
DISPATCHABLE_COMMAND( timer ? 2 2 ){
enableTimer = booleanValue(azArg[1]);
if( enableTimer && !HAS_TIMER ){
raw_printf(STD_ERR, "Error: timer not available on this system.\n");
enableTimer = 0;
}
return DCR_Ok;
}
DISPATCHABLE_COMMAND( trace ? 0 0 ){
ShellInState *psi = ISS(p);
int mType = 0;
int jj;
open_db(p, 0);
for(jj=1; jj<nArg; jj++){
const char *z = azArg[jj];
if( z[0]=='-' ){
if( optionMatch(z, "expanded") ){
psi->eTraceType = SHELL_TRACE_EXPANDED;
}
#ifdef SQLITE_ENABLE_NORMALIZE
else if( optionMatch(z, "normalized") ){
psi->eTraceType = SHELL_TRACE_NORMALIZED;
}
#endif
else if( optionMatch(z, "plain") ){
psi->eTraceType = SHELL_TRACE_PLAIN;
}
else if( optionMatch(z, "profile") ){
mType |= SQLITE_TRACE_PROFILE;
}
else if( optionMatch(z, "row") ){
mType |= SQLITE_TRACE_ROW;
}
else if( optionMatch(z, "stmt") ){
mType |= SQLITE_TRACE_STMT;
}
else if( optionMatch(z, "close") ){
mType |= SQLITE_TRACE_CLOSE;
}
else {
return DCR_Unknown|jj;
}
}else{
output_file_close(psi->traceOut);
psi->traceOut = output_file_open(z, 0);
}
}
if( psi->traceOut==0 ){
sqlite3_trace_v2(DBX(p), 0, 0, 0);
}else{
if( mType==0 ) mType = SQLITE_TRACE_STMT;
sqlite3_trace_v2(DBX(p), mType, sql_trace_callback, psi);
}
return DCR_Ok;
}
/*****************
* The .unknown command (undocumented)
*/
COLLECT_HELP_TEXT[
",unknown ?ARGS? Handle attempt to use an unknown dot command",
" The invocation dispatcher calls this after replacing azArg[0] with the",
" mystery command name, leaving remaining arguments as originally passed.",
" An extension may override this to provide new dot commands dynamically.",
" This name and operation were inspired by a similar feature of TCL.",
];
DISPATCHABLE_COMMAND( unknown ? 1 0 ){
/* Dispatcher will call this for dot commands it cannot find. */
return DCR_Unknown|0;
}
/*****************
* The .unmodule command
*/
#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_VIRTUALTABLE)
# define UNMODULE_ENABLE 1
#else
# define UNMODULE_ENABLE 0
#endif
CONDITION_COMMAND( unmodule UNMODULE_ENABLE );
COLLECT_HELP_TEXT[
".unmodule NAME ... Unregister virtual table modules",
" --allexcept Unregister everything except those named",
];
DISPATCHABLE_COMMAND( unmodule ? 2 0 ){
int ii;
int lenOpt;
char *zOpt;
open_db(p, 0);
zOpt = azArg[1];
if( zOpt[0]=='-' && zOpt[1]=='-' && zOpt[2]!=0 ) zOpt++;
lenOpt = (int)strlen(zOpt);
if( lenOpt>=3 && cli_strncmp(zOpt, "-allexcept",lenOpt)==0 ){
assert( azArg[nArg]==0 );
sqlite3_drop_modules(DBX(p), nArg>2 ? (const char**)(azArg+2) : 0);
}else{
for(ii=1; ii<nArg; ii++){
sqlite3_create_module(DBX(p), azArg[ii], 0, 0);
}
}
return DCR_Ok;
}
/*****************
* The .user command
* Because there is no help text for .user, it does its own argument validation.
*/
CONDITION_COMMAND( user SQLITE_USER_AUTHENTICATION );
DISPATCHABLE_COMMAND( user ? 0 0 ){
int rc;
const char *usage
= "Usage: .user SUBCOMMAND ...\n"
"Subcommands are:\n"
" login USER PASSWORD\n"
" delete USER\n"
" add USER PASSWORD ISADMIN\n"
" edit USER PASSWORD ISADMIN\n"
;
if( nArg<2 ){
teach_fail:
*pzErr = smprintf(usage);
return DCR_SayUsage;
}
open_db(p, 0);
if( cli_strcmp(azArg[1],"login")==0 ){
if( nArg!=4 ){
goto teach_fail;
}
rc = sqlite3_user_authenticate(DBX(p), azArg[2], azArg[3],
strlen30(azArg[3]));
if( rc ){
*pzErr = smprintf(0,"Authentication failed for user %s\n", azArg[2]);
return DCR_Error;
}
}else if( cli_strcmp(azArg[1],"add")==0 ){
if( nArg!=5 ){
goto teach_fail;
}
rc = sqlite3_user_add(DBX(p), azArg[2], azArg[3], strlen30(azArg[3]),
booleanValue(azArg[4]));
if( rc ){
*pzErr = smprintf(0,"User-Add failed: %d\n", rc);
return DCR_Error;
}
}else if( cli_strcmp(azArg[1],"edit")==0 ){
if( nArg!=5 ){
goto teach_fail;
}
rc = sqlite3_user_change(DBX(p), azArg[2], azArg[3], strlen30(azArg[3]),
booleanValue(azArg[4]));
if( rc ){
*pzErr = smprintf(0,"User-Edit failed: %d\n", rc);
return DCR_Error;
}
}else if( cli_strcmp(azArg[1],"delete")==0 ){
if( nArg!=3 ){
goto teach_fail;
}
rc = sqlite3_user_delete(DBX(p), azArg[2]);
if( rc ){
*pzErr = smprintf(0,"User-Delete failed: %d\n", rc);
return DCR_Error;
}
}else{
goto teach_fail;
}
return DCR_Ok;
}
/*****************
* The .vars command
*/
COLLECT_HELP_TEXT[
".vars ?OPTIONS? ... Manipulate and display shell variables",
" clear ?NAMES? Erase all or only given named variables",
#ifndef SQLITE_NOHAVE_SYSTEM
" edit ?-e? NAME Use edit() to create or alter variable NAME",
" With a -e option, the edited value is evaluated as a SQL expression.",
#endif
" list ?PATTERNS? List shell variables table values",
" Alternatively, to list just some or all names: ls ?PATTERNS?",
" load ?FILE? ?NAMES? Load some or all named variables from FILE",
" If FILE missing, empty or '~', it defaults to ~/sqlite_vars.sdb",
" save ?FILE? ?NAMES? Save some or all named variables into FILE",
" If FILE missing, empty or '~', it defaults to ~/sqlite_vars.sdb",
" set NAME VALUE Give shell variable NAME a value of VALUE",
" NAME must begin with a letter to be executable by .x, Other leading",
" characters have special uses. VALUE is the space-joined arguments.",
" unset ?NAMES? Remove named variables(s) from variables table",
];
DISPATCHABLE_COMMAND( vars 2 1 0 ){
DotCmdRC rv = DCR_Ok;
char *zErr = 0;
sqlite3 *dbs = p->dbShell;
const char *zCmd = (nArg>1)? azArg[1] : "ls";
int rc = 0;
int ncCmd = strlen30(zCmd);
if( *zCmd>'e' && ncCmd<2 ) return DCR_Ambiguous|1;
#define SUBCMD(scn) (cli_strncmp(zCmd, scn, ncCmd)==0)
/* This could be done lazily, but with more code. */
if( (dbs && ensure_shvars_table(dbs)!=SQLITE_OK) ){
return DCR_Error;
}else{
if( ensure_shell_db(p)!=SQLITE_OK ) return DCR_Error;
dbs = p->dbShell;
assert(dbs!=0);
if( ensure_shvars_table(dbs)!=SQLITE_OK ) return DCR_Error;
}
/* .vars clear and .vars unset ?NAMES?
** Delete some or all key/value pairs from the shell variables table.
** Without any arguments, clear deletes them all and unset does nothing.
*/
if( SUBCMD("clear") || SUBCMD("unset") ){
if( (nArg>2 || zCmd[0]=='c') ){
sqlite3_str *sbZap = sqlite3_str_new(dbs);
char *zSql;
sqst_ptr_holder(&sbZap);
sqlite3_str_appendf
(sbZap, "DELETE FROM "SHVAR_TABLE_SNAME" WHERE key ");
append_in_clause(sbZap,
(const char **)&azArg[2], (const char **)&azArg[nArg]);
zSql = sqlite3_str_finish(sbZap);
drop_holder();
shell_check_ooms(zSql);
sstr_holder(zSql);
rc = sqlite3_exec(dbs, zSql, 0, 0, 0);
release_holder();
}
#ifndef SQLITE_NOHAVE_SYSTEM
}else if( SUBCMD("edit") ){
ShellInState *psi = ISS(p);
int ia = 2;
int eval = 0;
int edSet;
if( !INSOURCE_IS_INTERACTIVE(psi->pInSource) ){
utf8_printf(STD_ERR, "Error: "
".vars edit can only be used interactively.\n");
return DCR_Error;
}
edSet = attempt_editor_set(psi, azArg[0], (nArg>2)? azArg[2] : 0 );
if( edSet < 0 ) return DCR_Error;
else ia += edSet;
while( ia < nArg ){
ParamTableUse ptu;
char *zA = azArg[ia];
char cf = (zA[0]=='-')? zA[1] : 0;
if( cf!=0 && zA[2]==0 ){
++ia;
switch( cf ){
case 'e': eval = 1; continue;
case 't': eval = 0; continue;
default:
utf8_printf(STD_ERR, "Error: bad .vars edit option: %s\n", zA);
return DCR_Error;
}
}
ptu = classify_param_name(zA);
if( ptu!=PTU_Script ){
utf8_printf(STD_ERR,
"Error: %s cannot be a shell variable name.\n", zA);
return DCR_Error;
}
rc = edit_one_kvalue(dbs, zA, eval, ptu, psi->zEditor, &zErr);
++ia;
if( zErr!=0 ){
utf8_printf(STD_ERR, "%s", zErr);
sqlite3_free(zErr);
zErr = 0;
}
if( rc!=0 ) return DCR_Error;
}
#endif
}else if( SUBCMD("list") || SUBCMD("ls") ){
int nTailArgs = nArg - 1 - (nArg>1);
char **pzTailArgs = azArg + 1 + (nArg>1);
list_pov_entries(p, PTU_Script, zCmd[1]=='s', pzTailArgs, nTailArgs);
}else if( SUBCMD("load") ){
rc = kv_pairs_load(dbs, PTU_Script, (const char **)azArg+1, nArg-1);
}else if( SUBCMD("save") ){
rc = kv_pairs_save(dbs, PTU_Script, (const char **)azArg+1, nArg-1);
}else if( SUBCMD("set") ){
ParamTableUse ptu;
if( nArg<4 ) return DCR_Missing;
ptu = classify_param_name(azArg[2]);
if( ptu!=PTU_Script ){
utf8_printf(STD_ERR,
"Error: %s is not a valid shell variable name.\n", azArg[2]);
rc = 1;
}else{
rc = shvar_set(dbs, azArg[2], &azArg[3], &azArg[nArg]);
if( rc!=SQLITE_OK ){
utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(dbs));
rc = 1;
}
}
}else{
showHelp(ISS(p)->out, "vars", p);
return DCR_CmdErred;
}
return DCR_Ok | (rv!=0) | (rc!=0);
#undef SUBCMD
}
/*****************
* The .vfsinfo, .vfslist, .vfsname and .version commands
*/
COLLECT_HELP_TEXT[
".version Show source, library and compiler versions",
".vfsinfo ?AUX? Information about the top-level VFS",
".vfslist List all available VFSes",
".vfsname ?AUX? Print the name of the VFS stack",
];
DISPATCHABLE_COMMAND( version ? 1 1 ){
FILE *out = ISS(p)->out;
utf8_printf(out, "SQLite %s %s\n" /*extra-version-info*/,
sqlite3_libversion(), sqlite3_sourceid());
#if SQLITE_HAVE_ZLIB
utf8_printf(out, "zlib version %s\n", zlibVersion());
#endif
#define CTIMEOPT_VAL_(opt) #opt
#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt)
#if defined(__clang__) && defined(__clang_major__)
utf8_printf(out, "clang-" CTIMEOPT_VAL(__clang_major__) "."
CTIMEOPT_VAL(__clang_minor__) "."
CTIMEOPT_VAL(__clang_patchlevel__) "\n");
#elif defined(_MSC_VER)
utf8_printf(out, "msvc-" CTIMEOPT_VAL(_MSC_VER) "\n");
#elif defined(__GNUC__) && defined(__VERSION__)
utf8_printf(out, "gcc-" __VERSION__ "\n");
#endif
return DCR_Ok;
}
DISPATCHABLE_COMMAND( vfsinfo ? 1 2 ){
const char *zDbName = nArg==2 ? azArg[1] : "main";
sqlite3_vfs *pVfs = 0;
if( DBX(p) ){
sqlite3_file_control(DBX(p), zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs);
if( pVfs ){
FILE *out = ISS(p)->out;
utf8_printf(out, "vfs.zName = \"%s\"\n", pVfs->zName);
raw_printf(out, "vfs.iVersion = %d\n", pVfs->iVersion);
raw_printf(out, "vfs.szOsFile = %d\n", pVfs->szOsFile);
raw_printf(out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
}
}
return DCR_Ok;
}
DISPATCHABLE_COMMAND( vfslist ? 1 1 ){
sqlite3_vfs *pVfs;
sqlite3_vfs *pCurrent = 0;
FILE *out = ISS(p)->out;
if( DBX(p) ){
sqlite3_file_control(DBX(p), "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent);
}
for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){
utf8_printf(out, "vfs.zName = \"%s\"%s\n", pVfs->zName,
pVfs==pCurrent ? " <--- CURRENT" : "");
raw_printf(out, "vfs.iVersion = %d\n", pVfs->iVersion);
raw_printf(out, "vfs.szOsFile = %d\n", pVfs->szOsFile);
raw_printf(out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
if( pVfs->pNext ){
raw_printf(out, "-----------------------------------\n");
}
}
return DCR_Ok;
}
DISPATCHABLE_COMMAND( vfsname ? 0 0 ){
const char *zDbName = nArg==2 ? azArg[1] : "main";
char *zVfsName = 0;
if( DBX(p) ){
sqlite3_file_control(DBX(p), zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName);
if( zVfsName ){
utf8_printf(ISS(p)->out, "%s\n", zVfsName);
sqlite3_free(zVfsName);
}
}
return DCR_Ok;
}
/*****************
* The .width command
*/
static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths){
int j;
int *pSW = p->pSpecWidths;
p->numWidths = nWidths;
pSW = realloc(pSW, (nWidths+1)*sizeof(int)*2);;
shell_check_oomm(pSW);
p->pSpecWidths = pSW;
if( nWidths>0 ){
p->pHaveWidths = &p->pSpecWidths[nWidths];
for(j=0; j<nWidths; j++){
p->pSpecWidths[j] = (int)integerValue(azWidths[j]);
p->pHaveWidths[j] = 0;
}
}else p->pHaveWidths = p->pSpecWidths;
}
COLLECT_HELP_TEXT[
".width NUM1 NUM2 ... Set minimum column widths for columnar output",
" Negative values right-justify. A lone ? shows the current values.",
];
DISPATCHABLE_COMMAND( width ? 1 0 ){
if( nArg==2 && cli_strcmp(azArg[1],"?")==0 ) showWidths(p);
else setColumnWidths(p, azArg+1, nArg-1);
return DCR_Ok;
}
/*****************
* The .x, .read and .eval commands
* These are together because they share some function and implementation.
*/
CONDITION_COMMAND(read !defined(SQLITE_SHELL_FIDDLE));
COLLECT_HELP_TEXT[
".eval ?ARGS? Process each ARG's content as shell input.",
".read FILE Read input from FILE",
" If FILE begins with \"|\", it is a command that generates the input.",
".x ?OBJS or FLAGS? ... Excecute content of objects as shell input",
" FLAGS can be any of {-k -s -f} specifying what subsequent arguments are.",
" Arguments after -k are keys in a key/value table kept by the shell DB,",
" for which the object content to be executed is the corresponding value.",
" Arguments after -f name either files (or pipes), which are to be read",
" and the content executed. Input pipe names begin with '|'; the rest is",
" an OS-shell command which can be run by the OS shell to produce output.",
" Arguments after -s are strings, content of which is to be executed.",
" The default in effect for arguments prior to any FLAG is -k .",
" Arguments are executed in order until one fails.",
];
/* Return an allocated string with trailing whitespace trimmed except
* for a trailing newline. If empty (or OOM), return 0. Otherwise, the
* caller must eventually pass the return to sqlite3_free().
*/
static char *zPrepForEval(const char *zVal, int ntc){
int ixNewline = 0;
char c;
while( ntc>0 && IsSpace(c = zVal[ntc-1]) ){
if( c=='\n' ) ixNewline = ntc-1;
--ntc;
}
if( ntc>0 ){
/* The trailing newline (or some other placeholder) is important
* because one (or some other character) will likely be put in
* its place during process_input() line/group handling, along
* with a terminating NUL character. Without it, the NULL could
* land past the end of the allocation made just below.
*/
int nle = ixNewline>0;
return smprintf( "%.*s%s", ntc, zVal, &"\n"[nle] );
}else{
return 0;
}
}
/* Evaluate a string as input to the CLI.
* zName is the name to be given to the source for error reporting.
* Return usual dot command return codes as filtered by process_input().
* No provision is made for error emission because, presumably, that
* has been done by whatever dot commands or SQL execution is invoked.
*/
static DotCmdRC shellEvalText(char *zIn, const char *zName, ShellExState *psx){
DotCmdRC rv;
ShellInState *psi = ISS(psx);
InSource inRedir
= INSOURCE_STR_REDIR(zIn, zName, psi->pInSource);
AnyResourceHolder arh = { &psi->pInSource, (GenericFreer)finish_InSource };
psi->pInSource = &inRedir;
any_ref_holder(&arh);
rv = process_input(psi);
release_holder();
return rv;
}
DISPATCHABLE_COMMAND( eval 3 1 0 ){
DotCmdRC rv = DCR_Ok;
int ia = 1;
int nErrors = 0;
while( ia < nArg ){
char *zA = azArg[ia++];
int nc = strlen30(zA);
char *zSubmit = zPrepForEval(zA, nc);
if( zSubmit ){
char zName[] = "eval arg[999]";
sqlite3_snprintf(sizeof(zName), zName,"eval arg[%d]", ia-1);
rv = shellEvalText(zSubmit, zName, p);
sqlite3_free(zSubmit);
if( rv<DCR_ArgIxMask ){
nErrors += (rv & DCR_Error);
/* Handle DCR_Return, DCR_Exit or DCR_Abort */
if( rv>DCR_Error ) break;
}
}
}
rv |= (nErrors>0);
/* If error to be returned, indicate that complaining about it is done. */
return (rv==DCR_Error)? DCR_CmdErred : rv;
}
DISPATCHABLE_COMMAND( read 3 2 2 ){
DotCmdRC rc = DCR_Ok;
ShellInState *psi = ISS(p);
InSource inSourceRedir
= INSOURCE_FILE_REDIR(0, azArg[1], psi->pInSource);
if( psi->bSafeMode ) return DCR_AbortError;
if( azArg[1][0]=='|' ){
#ifdef SQLITE_OMIT_POPEN
*pzErr = smprintf("pipes are not supported in this OS\n");
rc = DCR_Error;
#else
if( (inSourceRedir.inFile = popen(azArg[1]+1, "r"))==0 ){
*pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
rc = DCR_Error;
}else{
inSourceRedir.closer.stream = pclose;
}
#endif
}else if( (inSourceRedir.inFile = openChrSource(azArg[1]))==0 ){
*pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
rc = DCR_Error;
}else{
inSourceRedir.closer.stream = fclose;
}
if( inSourceRedir.inFile!=0 ){
AnyResourceHolder arh = { &(psi->pInSource),(GenericFreer)finish_InSource };
psi->pInSource = &inSourceRedir;
any_ref_holder(&arh);
rc = process_input(psi);
/* If error(s) occurred during process, leave complaining to them. */
if( rc==DCR_Error ) rc = DCR_CmdErred;
release_holder();
}
return rc;
}
DISPATCHABLE_COMMAND( x ? 1 0 ){
int ia, nErrors = 0;
sqlite3_stmt *pStmt = 0;
sqlite3 *dbs = p->dbShell;
DotCmdRC rv = DCR_Ok;
enum { AsVar, AsString, AsFile } evalAs = AsVar;
for( ia=1; ia<nArg && nErrors==0; ++ia ){
char *zSubmit = 0;
const char *zOpt = azArg[ia];
if ( *zOpt == '-' ){
static const char *azOpts[] = { "k", "s", "f" };
int io = ArraySize(azOpts);
while( io > 0 ){
if( optionMatch(zOpt, azOpts[--io]) ){
evalAs = io;
zOpt = 0;
break;
}
}
if( zOpt==0 ) continue;
}
switch( evalAs ){
case AsVar:
if( pStmt==0 ){
int rc;
if( dbs==0 || !shvars_table_exists(dbs) ){
utf8_printf(STD_ERR,
"\".x vname\" can only be done after .var set ... .\n");
return DCR_Error;
}
rc = s3_prepare_v2_noom(dbs, "SELECT value FROM "SHVAR_TABLE_SNAME
" WHERE key=$1 AND uses="SPTU_Script,
-1, &pStmt, 0);
if( rc!=SQLITE_OK ){
utf8_printf(STD_ERR, SHVAR_TABLE_SNAME" is wrongly created.\n");
return DCR_Error;
}
}
if( isalpha(azArg[ia][0]) ){
int rc = sqlite3_reset(pStmt);
rc = sqlite3_bind_text(pStmt, 1, azArg[ia], -1, 0);
rc = sqlite3_step(pStmt);
if( rc==SQLITE_ROW ){
ShellInState *psi = ISS(p);
const unsigned char *zValue = sqlite3_column_text(pStmt, 0);
int nb = sqlite3_column_bytes(pStmt, 0);
zSubmit = zPrepForEval((const char *)zValue, nb);
sqlite3_reset(pStmt); /* End the script read to unlock DB. */
if( zSubmit ){
rv = shellEvalText(zSubmit, azArg[ia], p);
sqlite3_free(zSubmit);
}else{
continue; /* All white (or OOM), ignore. */
}
}else{
utf8_printf(STD_ERR,
"Skipping var '%s' (not set and executable.)\n",
azArg[ia]);
++nErrors;
}
}else{
utf8_printf(STD_ERR,
"Skipping badly named %s. Run \".help x\"\n", azArg[ia]);
++nErrors;
}
break;
case AsString:
{
zSubmit = zPrepForEval(zOpt, strlen30(zOpt));
if( zSubmit ){
char zName[] = "x arg[999]";
sqlite3_snprintf(sizeof(zName), zName,"x arg[%d]", ia);
rv = shellEvalText(zSubmit, zName, p);
sqlite3_free(zSubmit);
}
}
break;
case AsFile:
{
char *av[] = {"read", (char*)zOpt};
rv = readCommand(av, ArraySize(av), p, pzErr);
}
break;
}
if( rv<DCR_ArgIxMask ){
nErrors += (rv & DCR_Error);
/* Handle DCR_Return, DCR_Exit or DCR_Abort */
if( rv>DCR_Error ) break;
}else{
++nErrors;
rv = DCR_Error;
}
}
sqlite3_finalize(pStmt);
rv |= (nErrors>0);
/* If error to be returned, indicate that complaining about it is done. */
return (rv==DCR_Error)? DCR_CmdErred : rv;
}
/* End of published, standard dot-command implementation functions
COMMENT Build-time overrides of above dot-commands or new dot-commands may be
COMMENT incorporated into shell.c via: -it COMMAND_CUSTOMIZE=<customize source>
COMMENT where <customize source> names a file using the above methodology to
COMMENT define new or altered dot-commands and their help text.
*/
INCLUDE( COMMAND_CUSTOMIZE );
static void DotCommand_destruct(DotCommand *);
static const char * DotCommand_name(DotCommand *);
static const char * DotCommand_help(DotCommand *, const char *);
static DotCmdRC
DotCommand_argsCheck(DotCommand *, char **, int nArgs, char *azArgs[]);
static DotCmdRC
DotCommand_execute(DotCommand *, ShellExState *, char **, int, char *[]);
static VTABLE_NAME(DotCommand) dot_cmd_VtabBuiltIn = {
DotCommand_destruct,
DotCommand_name,
DotCommand_help,
DotCommand_argsCheck,
DotCommand_execute
};
/* Define and populate command dispatch table. */
static struct CommandInfo {
VTABLE_NAME(DotCommand) *mcVtabBuiltIn;
const char * cmdName;
DotCmdRC (*cmdDoer)(char *azArg[], int nArg,
ShellExState *, char **pzErr);
unsigned char minLen, minArgs, maxArgs;
const char *azHelp[2]; /* primary and secondary help text */
void * pCmdData;
} command_table[] = {
COMMENT Emit the dispatch table entries generated and collected above.
#define DOT_CMD_INFO(cmd, nlenMin, minArgs, maxArgs) \
&dot_cmd_VtabBuiltIn, #cmd, cmd ## Command, nlenMin, minArgs, maxArgs
EMIT_DOTCMD_INIT(2);
#undef DOT_CMD_INFO
{ 0, 0, 0, 0, (u8)~0, (u8)~0, {0,0}, 0 }
};
static unsigned numCommands
= sizeof(command_table)/sizeof(struct CommandInfo) - 1;
static DotCommand *builtInCommand(int ix){
if( ix<0 || (unsigned)ix>=numCommands ) return 0;
return (DotCommand *)&command_table[ix];
}
static void DotCommand_destruct(DotCommand *pMe){
UNUSED_PARAMETER(pMe);
}
static const char * DotCommand_name(DotCommand *pMe){
return ((struct CommandInfo *)pMe)->cmdName;
}
static const char * DotCommand_help(DotCommand *pMe, const char * zWhat){
struct CommandInfo *pci = (struct CommandInfo *)pMe;
if( zWhat==0 ) return pci->azHelp[0];
if( *zWhat==0 ) return pci->azHelp[1];
else return 0;
}
static DotCmdRC
DotCommand_argsCheck(DotCommand *pMe,
char **pzErrMsg, int nArgs, char *azArgs[]){
struct CommandInfo *pci = (struct CommandInfo *)pMe;
UNUSED_PARAMETER(azArgs);
if( pci->minArgs > nArgs ){
if( pzErrMsg ){
*pzErrMsg = smprintf("Too few arguments for \".%s\", need %d\n",
azArgs[0], pci->minArgs-1);
}
return DCR_TooFew;
}else if( pci->maxArgs > 0 && pci->maxArgs < nArgs ){
if( pzErrMsg ){
*pzErrMsg = smprintf("Too many arguments for \".%s\", over %d\n",
azArgs[0], pci->maxArgs-1);
}
return DCR_TooMany;
}else return DCR_Ok;
}
static DotCmdRC
DotCommand_execute(DotCommand *pMe, ShellExState *pssx,
char **pzErrMsg, int nArgs, char *azArgs[]){
return (((struct CommandInfo *)pMe)->cmdDoer)(azArgs, nArgs, pssx, pzErrMsg);
}
/*****************
** DotCommand iteration by name match, used by the .help dot-command.
** DotCommands, or improvised stand-ins, having matching names are produced
** in lexical order, with the iterator indicating which has been produced.
** If .zAdhocHelpName == 0, it is a regular DotCommand. Otherwise, the
** ".unknown" DotCommand is returned, whose help() method is to be used.
** Any returned CmdMatchIter must eventually be passed to freeCmdMatchIter().
*/
typedef struct CmdMatchIter {
ShellExState *psx;
/* 0 indicates prepared statement; non-0 is the glob pattern. */
const char *zPattern;
union {
DotCommand *pDotCmd;
#if SHELL_DYNAMIC_EXTENSION
sqlite3_stmt *stmt;
#endif
} cursor;
#if SHELL_DYNAMIC_EXTENSION
char *zAdhocHelpText; /* registered extension improvised help */
#endif
} CmdMatchIter;
/* Release resources held by the iterator and clear it. */
static void freeCmdMatchIter(CmdMatchIter *pMMI){
if( pMMI->zPattern!=0 ){
sqlite3_free((void *)pMMI->zPattern);
pMMI->zPattern = 0;
pMMI->cursor.pDotCmd = 0;
}
#if SHELL_DYNAMIC_EXTENSION
else{
sqlite3_finalize(pMMI->cursor.stmt);
pMMI->cursor.stmt = 0;
}
sqlite3_free(pMMI->zAdhocHelpText);
pMMI->zAdhocHelpText = 0;
#endif
}
/* Prepare an iterator that will produce a sequence of DotCommand
* pointers whose referents' names match the given cmdFragment.
* Return how many will match (if iterated upon return.) */
static int findMatchingDotCmds(const char *cmdFragment,
CmdMatchIter *pMMI,
ShellExState *psx){
CmdMatchIter mmi = { psx, 0, 0 };
int rv = 0;
#if SHELL_DYNAMIC_EXTENSION
if( ISS(psx)->bDbDispatch ){
sqlite3_stmt *stmtCount = 0;
/* Prepare rv.stmt to yield results glob-matching cmdFragment. */
static const char * const zSqlIter =
"SELECT name, extIx, cmdIx, help FROM "SHELL_HELP_VIEW" "
"WHERE name glob (?||'*') ORDER BY name";
static const char * const zSqlCount =
"SELECT count(*) FROM "SHELL_HELP_VIEW" "
"WHERE name glob (?||'*')";
if( pMMI ){
s3_prepare_v2_noom(psx->dbShell, zSqlIter, -1, &mmi.cursor.stmt, 0);
sqlite3_bind_text(mmi.cursor.stmt, 1, cmdFragment? cmdFragment:"", -1, 0);
}
s3_prepare_v2_noom(psx->dbShell, zSqlCount, -1, &stmtCount, 0);
sqlite3_bind_text(stmtCount, 1, cmdFragment? cmdFragment : "", -1, 0);
if( SQLITE_ROW==sqlite3_step(stmtCount) ){
rv = sqlite3_column_int(stmtCount, 0);
}else assert(0);
sqlite3_finalize(stmtCount);
}else
#endif
{
int i = 0;
mmi.zPattern = smprintf("%s*", cmdFragment? cmdFragment : "");
shell_check_ooms((void *)mmi.zPattern);
struct CommandInfo *pCI = command_table;
mmi.cursor.pDotCmd = (DotCommand *)command_table;
while( pCI<command_table+numCommands ){
if( sqlite3_strglob(mmi.zPattern, pCI->cmdName)==0 ) ++rv;
++pCI;
}
}
if( pMMI ) *pMMI = mmi;
else freeCmdMatchIter(&mmi);
return rv;
}
/* Produce the next DotCommand pointer from the iterator, or 0 if no next. */
static DotCommand * nextMatchingDotCmd(CmdMatchIter *pMMI){
DotCommand *rv = 0;
#if SHELL_DYNAMIC_EXTENSION
if( pMMI->zPattern==0 ){
int rc = sqlite3_step(pMMI->cursor.stmt);
if( rc==SQLITE_ROW ){
/* name, extIx, cmdIx, help */
int extIx = sqlite3_column_int(pMMI->cursor.stmt, 1);
int cmdIx = sqlite3_column_int(pMMI->cursor.stmt, 2);
ShellInState *psi = ISS(pMMI->psx);
sqlite3_free(pMMI->zAdhocHelpText);
if( cmdIx>=0 ){
pMMI->zAdhocHelpText = 0;
return command_by_index(psi, extIx, cmdIx);
}else{
const unsigned char *zHT = sqlite3_column_text(pMMI->cursor.stmt, 3);
assert(psi->pUnknown!=0);
assert(extIx<psi->numExtLoaded && extIx>0);
if( zHT==0 ) zHT = sqlite3_column_text(pMMI->cursor.stmt, 0);
pMMI->zAdhocHelpText = smprintf("%s", zHT);
return psi->pShxLoaded[extIx].pUnknown;
}
}else{
sqlite3_finalize(pMMI->cursor.stmt);
pMMI->cursor.stmt = 0;
}
}else
#endif
{
struct CommandInfo *pCI = (struct CommandInfo *)(pMMI->cursor.pDotCmd);
assert(pCI>=command_table && pCI<=command_table+numCommands);
while( pCI<command_table+numCommands ){
if( sqlite3_strglob(pMMI->zPattern, pCI->cmdName)==0 ){
rv = pMMI->cursor.pDotCmd;
}
pMMI->cursor.pDotCmd = (DotCommand *)(++pCI);
if( rv!=0 ) break;
}
}
return rv;
}
/*****************
** DotCommand lookup
**
** For the non-extended or non-extensible shell, this function does
** a binary search of the fixed list of dot-command info structs.
** For an extended shell, it queries the shell's DB. Either way,
** this function returns a DotCommand pointer if one can be found
** with an adequate match for the given name. Here, "adequate" may
** vary according to whether shell extensions have been loaded. If
** not, the match must be for as many characters as set within the
** above CommandInfo array (set via DISPATCHABLE_COMMAND macro call.)
** If shell extensions are loaded, the match must be long enough to
** result in a unique lookup.
*/
DotCommand *findDotCommand(const char *cmdName, ShellExState *psx,
/* out */ int *pnFound){
if( pnFound ) *pnFound = 0;
#if SHELL_DYNAMIC_EXTENSION
if( ISS(psx)->bDbDispatch ){
int rc;
int extIx = -1, cmdIx = -1, nf = 0;
sqlite3_stmt *pStmt = 0;
const char *zSql = "SELECT COUNT(*), extIx, cmdIx"
" FROM "SHELL_DISP_VIEW" WHERE name glob (?||'*')";
rc = s3_prepare_v2_noom(psx->dbShell, zSql, -1, &pStmt, 0);
sqlite3_bind_text(pStmt, 1, cmdName, -1, 0);
rc = sqlite3_step(pStmt);
nf = sqlite3_column_int(pStmt, 0);
extIx = sqlite3_column_int(pStmt, 1);
cmdIx = sqlite3_column_int(pStmt, 2);
sqlite3_finalize(pStmt);
if( rc!= SQLITE_ROW ) return 0;
if( pnFound ) *pnFound = nf;
if( nf!=1 ) return 0; /* Future: indicate collisions if > 1 */
return (cmdIx<0)? 0 : command_by_index(ISS(psx), extIx, cmdIx);
}else
#endif
{
int cmdLen = strlen30(cmdName);
struct CommandInfo *pci = 0;
int ixb = 0, ixe = numCommands-1;
while( ixb <= ixe ){
int ixm = (ixb+ixe)/2;
int md = cli_strncmp(cmdName, command_table[ixm].cmdName, cmdLen);
if( md>0 ){
ixb = ixm+1;
}else if( md<0 ){
ixe = ixm-1;
}else{
/* Have a match, see whether it's ambiguous. */
if( command_table[ixm].minLen > cmdLen ){
if( (ixm>0
&& !cli_strncmp(cmdName, command_table[ixm-1].cmdName, cmdLen))
||
(ixm<ixe
&& !cli_strncmp(cmdName, command_table[ixm+1].cmdName, cmdLen))){
/* Yes, a neighbor matches too. */
if( pnFound ) *pnFound = 2; /* could be more, but >1 suffices */
return 0;
}
}
pci = &command_table[ixm];
if( pnFound ) *pnFound = 1;
break;
}
}
if( pnFound && pci ) *pnFound = 1;
return (DotCommand *)pci;
}
}
/*
** Given a DotCommand, desired help level,
** ( possibly retrieved improvised help text for extensible shell, )
** and an optional all-text search pattern, then
** when level==0 and primary help available, output it
** when level==1 and primary or secondary help available, output it
** when level==2 and any help text matches pattern, output it
** when level>2 or no pattern: output all help text
** If cLead==0, anything meeting above criteria is output. Otherwise, output
** is restricted to those commands whose primary help begins with cLead.
** Return 1 if anything output, else 0.
*/
static int putSelectedCmdHelp(DotCommand *pmc, int iLevel, char cLead,
#if SHELL_DYNAMIC_EXTENSION
const char *zHelpText,
#endif
FILE *out, const char *zSearch){
int rc = 0;
assert(pmc!=0);
#if SHELL_DYNAMIC_EXTENSION
if( zHelpText ){
const char *zHT = zHelpText+1; /* skip over classifier */
if( cLead && *zHelpText!= cLead ) return 0;
if( *zHelpText==0 ) return 0;
const char *zLE = zHT;
switch( iLevel ){
case 0:
while( *zLE && *zLE++!='\n' ) {}
utf8_printf(out,".%.*s", (int)(zLE-zHT), zHT);
rc = 1;
break;
case 2:
if( zSearch ){
if( !sqlite3_strlike(zSearch, zHT, 0) ) break;
}
deliberate_fall_through;
case 1:
default:
utf8_printf(out,".%s", zHT);
rc = 1;
}
}else
#endif
{
const char *zHTp = pmc->pMethods->help(pmc, 0);
const char *zHTs = pmc->pMethods->help(pmc, "");
if( !zHTp && !zHTs ) return 0;
if( cLead && zHTp && *zHTp!= cLead ) return 0;
switch( iLevel ){
case 0:
case 1:
if( zHTp ){
utf8_printf(out, HELP_TEXT_FMTP, zHTp+1);
rc = 1;
}
if( iLevel>0 && zHTs ){
utf8_printf(out, HELP_TEXT_FMTS, zHTs);
rc = 1;
}
break;
case 2:
if( zSearch ){
int m = 0;
if( zHTp && !sqlite3_strlike(zSearch, zHTp, 0) ) ++m;
if( zHTs && !sqlite3_strlike(zSearch, zHTs, 0) ) ++m;
if( m==0 ) break;
}
deliberate_fall_through;
default:
if( zHTp ) utf8_printf(out, HELP_TEXT_FMTP, zHTp+1);
if( zHTs ) utf8_printf(out, HELP_TEXT_FMTS, zHTs);
rc = 1;
}
}
return rc;
}
/*
** Output primary (single-line) help for a known command.
*/
static void showPrimaryHelp(FILE *out, const char *zCmd, ShellExState *psx){
CmdMatchIter cmi = {0};
int nm = findMatchingDotCmds(zCmd, &cmi, psx);
DotCommand *pdc = nextMatchingDotCmd(&cmi);
if( pdc!=0 ){
putSelectedCmdHelp(pdc, 0, 0,
#if SHELL_DYNAMIC_EXTENSION
cmi.zAdhocHelpText,
#endif
out, 0);
}
freeCmdMatchIter(&cmi);
}
/*
** Output various subsets of help text. These 6 are defined:
** 1. HO_AllP For all commands, primary help text only.
** 2. HO_AllX For all commands, complete help text.
** 3. HO_LikeP For multiple commands matching pattern, primary help text only.
** 4. HO_OneX For a single matched command, complete help text.
** 5. HO_LikeT For commands whose help contains a pattern, complete help text.
** 6. HO_Undoc For all internal "undocumented" (without normal help) commands.
** These variations are indicated thus:
** 1. zPattern is NULL
** 2. zPattern is ""
** 3. zPattern is a prefix matching more than one command
** 4. zPattern is a word or prefix matching just one command
** 5. zPattern is neither case 3 or 4 but is found in complete help text
** 6. zPattern is exactly the pointer known locally as zHelpAll.
**
** Return the number of matches.
*/
static int showHelp(FILE *out, const char *zPattern, ShellExState *psx){
u8 bNullPattern = zPattern==0;
u8 bShowUndoc = zPattern==zHelpAll;
u8 bEmptyPattern = !bNullPattern && (*zPattern==0 || bShowUndoc);
int npm = 0; /* track how many matches found */
CmdMatchIter cmi = {0};
DotCommand *pdc;
char *zPat = 0;
char cLead = (bShowUndoc)? ',' : '.';
int iLevel = 0;
enum {
HO_Tbd, HO_AllP, HO_AllX, HO_LikeP, HO_OneX, HO_LikeT, HO_Undoc
} hoKind = bShowUndoc? HO_Undoc : HO_Tbd;
if( hoKind==HO_Undoc ){
unsigned ixct = 0;
utf8_printf(out, "%s\n%s\n",
"The following commands are for SQLite diagnosis and internal testing.",
"They are undocumented and subject to change without notice.");
/* Bypass command lookup/resolution. This is just for internal commands. */
while( ixct<numCommands ){
struct CommandInfo *pci = &command_table[ixct];
const char *zH = pci->azHelp[0];
if( zH && *zH==cLead ){
utf8_printf(out, HELP_TEXT_FMTP, zH+1);
zH = pci->azHelp[1];
if( zH ) utf8_printf(out, HELP_TEXT_FMTS, zH);
++npm;
}
++ixct;
}
return npm;
}
npm = findMatchingDotCmds(zPattern, &cmi, psx);
if( bNullPattern ) hoKind = HO_AllP;
else if( bEmptyPattern ) hoKind = HO_AllX;
else if( npm>1 ) hoKind = HO_LikeP;
else if( npm==1 ) hoKind = HO_OneX;
else{
hoKind = HO_LikeT;
zPat = smprintf("%%%s%%", zPattern);
shell_check_ooms(zPat);
}
zPattern = 0;
iLevel = 1;
switch( hoKind ){
case HO_AllP: case HO_LikeP:
iLevel = 0;
break;
case HO_AllX:
break;
case HO_OneX:
cLead = 0;
break;
case HO_LikeT:
zPattern = zPat;
break;
default: return 0;
}
npm = 0;
while( 0 != (pdc = nextMatchingDotCmd(&cmi)) ){
npm += putSelectedCmdHelp(pdc, iLevel, cLead,
#if SHELL_DYNAMIC_EXTENSION
cmi.zAdhocHelpText,
#endif
out, zPattern);
}
freeCmdMatchIter(&cmi);
sqlite3_free(zPat);
return npm;
}
/* Perform preparation needed prior to actually running any dot command. */
static void command_prep(ShellInState *psi){
clearTempFile(psi);
#ifndef SQLITE_OMIT_VIRTUALTABLE
if( psi->expert.pExpert ){
expertFinish(psi, 1, 0);
}
#endif
}
/* Perform post-execution actions needed after running any dot command. */
static void command_post(ShellInState *psi){
if( psi->outCount ){
psi->outCount--;
if( psi->outCount==0 ) output_reset(psi);
}
updateSafeMode(psi);
}
/* Issue errors per returned DotCmdRC and error message, handle certain
* exceptional returns, and translate return to the sanitized first 8.
*/
static DotCmdRC dot_command_errors(char *zErr, char *azArg[], int nArg,
DotCmdRC dcr, ShellExState *psx){
if( psx->shellAbruptExit!=0 ){
if( psx->shellAbruptExit>0x1ff ) dcr = DCR_AbortError;
else dcr = DCR_Exit | (dcr & DCR_Error);
}
if( dcr==DCR_CmdErred ){
/* Error message(s) already emitted. Just translate to execute error. */
dcr = DCR_Error;
}else if( dcr==DCR_SayUsage ){
if( zErr ){
utf8_printf(STD_ERR, "%s", zErr);
}else{
utf8_printf(STD_ERR, "Usage:\n");
showPrimaryHelp(STD_ERR, azArg[0], psx);
}
dcr = DCR_Error;
}else if( dcr > DCR_AbortError ){
/* Handle invocation errors. */
int ia = dcr & DCR_ArgIxMask;
const char *pArg1st = (ia>=nArg)? "" : azArg[ia];
const char *pArg2nd = 0;
int ec = dcr & ~DCR_ArgIxMask;
int unknownCmd = 0;
const char *z = 0;
switch( ec ){
case DCR_Unknown:
unknownCmd = ia==0;
if( unknownCmd ) z = "unknown dot command: \".%s\"";
else{
z = "On .%s, unknown option or subcommand: \"%s\"";
pArg2nd = pArg1st;
pArg1st = azArg[0];
}
break;
case DCR_Ambiguous:
if( ia>0 ){
z = "For .%s, \"%s\" is ambiguous; it matches more than one subcommand";
pArg2nd = pArg1st;
pArg1st = azArg[0];
}else z = "\"%s\" is ambiguous; it matches multiple dot commands";
break;
case DCR_Unpaired:
z = "With .%s, option %s must be paired with a value argument";
pArg2nd = pArg1st;
pArg1st = azArg[0];
break;
case DCR_TooMany:
z = "Excess arguments provided; try .help %s";
pArg1st = azArg[0];
break;
case DCR_TooFew:
z = "Insufficient arguments; try .help %s";
pArg1st = azArg[0];
break;
case DCR_Missing:
z = "Required arguments missing; try .help %s";
pArg1st = azArg[0];
break;
case DCR_ArgWrong:
z = "Argument(s) incorrect; try .help %s";
pArg1st = azArg[0];
break;
default:
z = "? Bizarre return from %s";
break;
}
if( !zErr ){
if( z!=0 ){
char *ze = smprintf("Error: %z\n", smprintf(z, pArg1st, pArg2nd));
utf8_printf(STD_ERR, "%s", ze);
sqlite3_free(ze);
}
}else{
const char *fmt
= (sqlite3_strnicmp(zErr, "Error", 5)==0)? "%s" : "Error: %s";
utf8_printf(STD_ERR, fmt, zErr);
}
if( INSOURCE_IS_INTERACTIVE(ISS(psx)->pInSource) ){
if( unknownCmd ){
utf8_printf(STD_ERR, "Enter \".help\" for a list of commands.\n");
}else{
utf8_printf(STD_ERR, "Usage:\n");
showPrimaryHelp(STD_ERR, azArg[0], psx);
}
}
/* If the shell DB becomes messed up, at least .quit will be doable. */
if( unknownCmd && psx->dbShell!=0
&& sqlite3_strnicmp(azArg[0],"quit",strlen30(azArg[0]))==0 ){
dcr = DCR_Return;
}else{
dcr = DCR_Error;
}
}else{
/* Handle execution errors. */
if( dcr==DCR_AbortError ){
if( zErr ){
utf8_printf(STD_ERR, "Error: \".%s\" may not %s in -safe mode\n",
azArg[0], zErr);
}else {
utf8_printf(STD_ERR, "Error: \".%s\" forbidden in -safe mode\n",
azArg[0]);
}
psx->shellAbruptExit = 0x203;
}else{
int error = dcr & DCR_Error;
int action = dcr & ~DCR_Error;
if( error ){
if( zErr ){
const char *fmt
= (sqlite3_strnicmp(zErr, "Error", 5)==0)? "%s" : "Error: %s";
utf8_printf(STD_ERR, fmt, zErr);
}
else utf8_printf(STD_ERR, "Error: .%s failed\n", azArg[0]);
}
}
}
return dcr;
}
/* Argument-check and execute a found DotCommand, wrapping execution
* with command_{prep,post}(...), and issue errors as made evident.
* Return one of the "Post-execute action and success/error status"
* codes from the DotCmdRC enum.
*
* Note that this function is exposed for shell extensions to use.
*
* This should be called for "top-level" dot command execution only.
* Should an extension wrap or use a DotCommand object to effect its
* own functionality, that object's execute() method should be called
* directly, without going through this function.
*/
static DotCmdRC runDotCommand(DotCommand *pmc, char *azArg[], int nArg,
ShellExState *psx){
char *zErr = 0;
DotCmdRC dcr = pmc->pMethods->argsCheck(pmc, &zErr, nArg, azArg);
ResourceMark mark = holder_mark();
command_prep(ISS(psx));
sstr_ptr_holder(&zErr);
if( dcr==DCR_Ok ){
dcr = pmc->pMethods->execute(pmc, psx, &zErr, nArg, azArg);
}
if( dcr!=DCR_Ok ){
dcr = dot_command_errors(zErr, azArg, nArg, dcr, psx);
}
RESOURCE_FREE(mark);
command_post(ISS(psx));
return dcr;
}
/*
** If an input line or line group begins with "." then invoke this routine
** to process that line.
**
** Returns sanitized DotCmdRC values, with invocation error codes
** translated to DCR_Error, so that only these 8 returns are possible:
** DCR_Ok, DCR_Return, DCR_Exit or DCR_Abort possibly or'ed with DCR_Error.
**
** Any applicable error messages are issued along with output messages.
*/
static DotCmdRC do_dot_command(char *zLine, ShellExState *psx){
int h = 1; /* Passing over leading '.' */
int nArg = 0;
char *azArg[52];
DotCmdRC dcr = DCR_Ok;
#if SHELL_VARIABLE_EXPANSION
int ncLineIn = strlen30(zLine);
u8 bExpVars = SHEXT_VAREXP(ISS(psx));
#endif
RipStackDest dotRipDest = RIP_STACK_DEST_INIT;
/* Parse the input line into tokens which are 0-terminated and left in-place.
*/
while( zLine[h] && nArg<ArraySize(azArg)-1 ){
/* Future: Complain if this fixed argument count limit is hit. */
while( IsSpace(zLine[h]) ){ h++; }
if( zLine[h]==0 ) break;
if( zLine[h]=='\'' || zLine[h]=='"' ){
int delim = zLine[h++];
azArg[nArg++] = &zLine[h];
while( zLine[h] && zLine[h]!=delim ){
if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++;
h++;
}
if( zLine[h]==delim ){
zLine[h++] = 0;
}
if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
}else{
azArg[nArg++] = &zLine[h];
while( zLine[h] && !IsSpace(zLine[h]) ){ h++; }
if( zLine[h] ) zLine[h++] = 0;
resolve_backslashes(azArg[nArg-1]);
}
}
azArg[nArg] = 0; /* No code here relies on this, but some extension might. */
/* Process the input line. If it was empty, do nothing and declare success.
* Note that "empty" includes a leading '.' followed by nothing else.
*/
register_exit_ripper(&dotRipDest);
if( 0==RIP_TO_HERE(dotRipDest) ){
if( nArg>0 ){
int nFound;
DotCommand *pmc = findDotCommand(azArg[0], psx, &nFound);
if( pmc==0 || nFound>1 ){
if( nFound==0 ){
dcr = DCR_Unknown;
#if SHELL_DYNAMIC_EXTENSION
pmc = ISS(psx)->pUnknown;
if( pmc ) dcr = runDotCommand(pmc, azArg, nArg, psx);
#endif
}else{
dcr = DCR_Ambiguous;
}
if( dcr > DCR_ArgIxMask ){
/* Issue error for unknown or inadequately specified dot command. */
dcr = dot_command_errors(0, azArg, nArg, dcr, psx);
}
}
else{
char *arg0 = azArg[0];
azArg[0] = (char *)(pmc->pMethods->name(pmc));
/* Run found command and issue or handle any errors it may report. */
dcr = runDotCommand(pmc, azArg, nArg, psx);
azArg[0] = arg0;
}
}
forget_exit_ripper(&dotRipDest);
}else{
dcr = DCR_Abort|DCR_Error;
}
#if SHELL_VARIABLE_EXPANSION
if( bExpVars ){
/* Free any arguments that are allocated rather than tokenized in place. */
for( n=1; n<nArg; ++n ){
int iArgOffset = azArg[n]-zLine;
u8 bInPlace = iArgOffset>0 && iArgOffset<ncLineIn;
if( !bInPlace ) sqlite3_free(azArg[n]);
}
}
#endif
return dcr;
}
/* Line scan result and intermediate states (supporting scan resumption)
*/
#ifndef CHAR_BIT
# define CHAR_BIT 8
#endif
typedef enum {
SSS_HasDark = 1<<CHAR_BIT, SSS_EndingSemi = 2<<CHAR_BIT,
SSS_CharMask = (1<<CHAR_BIT)-1, SSS_ScanMask = 3<<CHAR_BIT,
SSS_Start = 0
} SqlScanState;
#define SSS_SETV(qss, newst) ((newst) | ((qss) & SSS_ScanMask))
#define SSS_INPLAIN(qss) (((qss)&SSS_CharMask)==SSS_Start)
#define SSS_PLAINWHITE(qss) (((qss)&~SSS_EndingSemi)==SSS_Start)
#define SSS_PLAINDARK(qss) (((qss)&~SSS_EndingSemi)==SSS_HasDark)
#define SSS_SEMITERM(qss) (((qss)&~SSS_HasDark)==SSS_EndingSemi)
/*
** Scan line for classification to guide shell's handling.
** The scan is resumable for subsequent lines when prior
** return values are passed as the 2nd argument.
*/
static void sql_prescan(const char *zLine, SqlScanState *pSSS,
SCAN_TRACKER_REFTYPE pst){
SqlScanState sss = *pSSS;
char cin;
char cWait = (char)sss; /* intentional narrowing loss */
if( cWait==0 ){
PlainScan:
assert( cWait==0 );
while( (cin = *zLine++)!=0 ){
if( IsSpace(cin) )
continue;
switch (cin){
case '-':
if( *zLine!='-' )
break;
while((cin = *++zLine)!=0 )
if( cin=='\n')
goto PlainScan;
goto ScanDone;
case ';':
sss |= SSS_EndingSemi;
continue;
case '/':
if( *zLine=='*' ){
++zLine;
cWait = '*';
CONTINUE_PROMPT_AWAITS(pst, "/*");
sss = SSS_SETV(sss, cWait);
goto TermScan;
}
break;
case '[':
cin = ']';
deliberate_fall_through;
case '`': case '\'': case '"':
cWait = cin;
sss = SSS_HasDark | cWait;
CONTINUE_PROMPT_AWAITC(pst, cin);
goto TermScan;
case '(':
CONTINUE_PAREN_INCR(pst, 1);
break;
case ')':
CONTINUE_PAREN_INCR(pst, -1);
break;
default:
break;
}
sss = (sss & ~SSS_EndingSemi) | SSS_HasDark;
}
}else{
TermScan:
while( (cin = *zLine++)!=0 ){
if( cin==cWait ){
switch( cWait ){
case '*':
if( *zLine != '/' )
continue;
++zLine;
cWait = 0;
CONTINUE_PROMPT_AWAITC(pst, 0);
sss = SSS_SETV(sss, 0);
goto PlainScan;
case '`': case '\'': case '"':
if(*zLine==cWait){
/* Swallow doubled end-delimiter.*/
++zLine;
continue;
}
deliberate_fall_through;
case ']':
cWait = 0;
CONTINUE_PROMPT_AWAITC(pst, 0);
sss = SSS_SETV(sss, 0);
goto PlainScan;
default: assert(0);
}
}
}
}
ScanDone:
*pSSS = sss;
}
/*
** If the line typed in is an SQL command terminator other than ';',
** return a pointer to the terminator. Otherwise return 0.
** The SQL Server style "go" command and Oracle "/" are understood.
*/
static char *line_is_command_terminator(char *zLine){
int iSkip = 0;
const char *zDark = skipWhite(zLine);
if( zDark[0]=='/' )
iSkip = 1; /* Oracle */
else if( ToLower(zDark[0])=='g' && ToLower(zDark[1])=='o' )
iSkip = 2; /* SQL Server */
if( iSkip>0 ){
SqlScanState sss = SSS_Start;
sql_prescan(zDark+iSkip, &sss, 0);
if( sss==SSS_Start ) return (char *)zDark;
}
return 0;
}
/*
** The CLI needs a working sqlite3_complete() to work properly. So error
** out of the build if compiling with SQLITE_OMIT_COMPLETE.
*/
#ifdef SQLITE_OMIT_COMPLETE
# error the CLI application is imcompatable with SQLITE_OMIT_COMPLETE.
#endif
/*
** Return true if zSql is a complete SQL statement. Return false if it
** ends in the middle of a string literal or C-style comment.
*/
static int line_is_complete(char *zSql, int nSql){
int rc;
if( zSql==0 ) return 1;
zSql[nSql] = ';';
zSql[nSql+1] = 0;
rc = sqlite3_complete(zSql);
if( rc==SQLITE_NOMEM ) shell_out_of_memory();
zSql[nSql] = 0;
return rc;
}
/*
** Run a single line of SQL. Return the number of errors.
** bAltIn indicates that input has been redirected in some way.
*/
static int runOneSqlLine(ShellExState *psx, char *zSql,
int bAltIn, int startline){
int rc;
char *zErrMsg = 0;
RipStackDest sqlRipDest = RIP_STACK_DEST_INIT;
open_db(psx, 0);
sstr_ptr_holder(&zErrMsg);
if( ShellHasFlag(psx,SHFLG_Backslash) ) resolve_backslashes(zSql);
if( ISS(psx)->flgProgress & SHELL_PROGRESS_RESET ) ISS(psx)->nProgress = 0;
BEGIN_TIMER;
register_exit_ripper(&sqlRipDest);
if( 0==RIP_TO_HERE(sqlRipDest) ){
rc = shell_exec(psx, zSql, &zErrMsg);
forget_exit_ripper(&sqlRipDest);
}else{
psx->shellAbruptExit = 0x102;
rc = 1;
}
END_TIMER;
if( rc || zErrMsg ){
char zPrefix[100];
const char *zErrorTail;
const char *zErrorType;
if( psx->shellAbruptExit==0 ){
if( zErrMsg==0 ){
zErrorType = "Error";
zErrorTail = sqlite3_errmsg(DBX(psx));
}else if( cli_strncmp(zErrMsg, "in prepare, ",12)==0 ){
zErrorType = "Parse error";
zErrorTail = &zErrMsg[12];
}else if( cli_strncmp(zErrMsg, "stepping, ", 10)==0 ){
zErrorType = "Runtime error";
zErrorTail = &zErrMsg[10];
}else{
zErrorType = "Error";
zErrorTail = zErrMsg;
}
if( bAltIn || !stdin_is_interactive ){
sqlite3_snprintf(sizeof(zPrefix), zPrefix,
"%s near line %d:", zErrorType, startline);
}else{
sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType);
}
utf8_printf(STD_ERR, "%s %s\n", zPrefix, zErrorTail);
}
rc = 1;
}else if( ShellHasFlag(psx, SHFLG_CountChanges) ){
char zLineBuf[36+2*20];
sqlite3_snprintf(sizeof(zLineBuf), zLineBuf,
"changes: %lld total_changes: %lld",
sqlite3_changes64(DBX(psx)), sqlite3_total_changes64(DBX(psx)));
raw_printf(ISS(psx)->out, "%s\n", zLineBuf);
}
release_holder();
return rc;
}
#if SHELL_EXTENDED_PARSING
/* Resumable line classifier for dot-commands
**
** Determines if a dot-command is open, having either an unclosed
** quoted argument or an escape sequence opener ('\') at its end.
**
** The FSM design/behavior assumes/requires that a terminating '\'
** is not part of the character sequence being classified -- that
** it represents an escaped newline which is removed as physical
** lines are spliced to accumulate logical lines.
**
** The line or added line-portion is passed as zCmd.
** The pScanState pointer must reference an (opaque) DCmd_ScanState,
** which must be set to DCSS_Start to initialize the scanner state.
** Resumed scanning should always be done with zCmd logically just
** past the last non-0 char of the text previously passed in, with
** any previously scanned, trailing newline escape first trimmed.
** Returns are: 0 => not open (aka complete), 1 => is open (incomplete)
** The following macros may be applied to the scan state:
*/
#define DCSS_InDarkArg(dcss) (((dcss)&argPosMask)==inDqArg)
#define DCSS_EndEscaped(dcss) (((dcss)&endEscaped)!=0)
#define DCSS_IsOpen(dcss) (((dcss)&isOpenMask)!=0)
typedef enum {
DCSS_Start = 0,
twixtArgs = 0, inSqArg = 1, inDarkArg = 2, inDqArg = 3, /* ordered */
endEscaped = 4, /* bit used */
argPosMask = 3, /* bits used */
isOpenMask = 1|4 /* bit test */
} DCmd_ScanState;
static void dot_command_scan(char *zCmd, DCmd_ScanState *pScanState,
SCAN_TRACKER_REFTYPE pst){
DCmd_ScanState ss = *pScanState & ~endEscaped;
char c = (ss&isOpenMask)? 1 : *zCmd++;
while( c!=0 ){
switch( ss ){
case twixtArgs:
CONTINUE_PROMPT_AWAITC(pst, 0);
while( IsSpace(c) ){
if( (c=*zCmd++)==0 ) goto atEnd;
}
switch( c ){
case '\\':
if( *zCmd==0 ){
ss |= endEscaped;
goto atEnd;
}else goto inDark;
case '\'': ss = inSqArg; goto inSq;
case '"': ss = inDqArg; goto inDq;
default: ss = inDarkArg; goto inDark;
}
inSq:
case inSqArg:
CONTINUE_PROMPT_AWAITC(pst, '\'');
while( (c=*zCmd++)!='\'' ){
if( c==0 ) goto atEnd;
if( c=='\\' && *zCmd==0 ){
ss |= endEscaped;
goto atEnd;
}
}
ss = twixtArgs;
c = *zCmd++;
continue;
inDq:
case inDqArg:
CONTINUE_PROMPT_AWAITC(pst, '"');
do {
if( (c=*zCmd++)==0 ) goto atEnd;
if( c=='\\' ){
if( (c=*zCmd++)==0 ){
ss |= endEscaped;
goto atEnd;
}
if( (c=*zCmd++)==0 ) goto atEnd;
}
} while( c!='"' );
ss = twixtArgs;
c = *zCmd++;
continue;
inDark:
case inDarkArg:
CONTINUE_PROMPT_AWAITC(pst, 0);
while( !IsSpace(c) ){
if( c=='\\' && *zCmd==0 ){
ss |= endEscaped;
goto atEnd;
}
if( (c=*zCmd++)==0 ) goto atEnd;
}
ss = twixtArgs;
c = *zCmd++;
continue;
case endEscaped: case isOpenMask: default:
; /* Not reachable, but quiet compilers unable to see this. */
}
}
atEnd:
*pScanState = ss;
}
#else
# define dot_command_scan(x,y,z)
#endif
/* Utility functions for process_input. */
#if SHELL_EXTENDED_PARSING
/*
** Process dot-command line with its scan state to:
** 1. Setup for requested line-splicing; and
** 2. Say whether it is complete.
** The last two out parameters are the line's length, which may be
** adjusted, and the char to be used for joining a subsequent line.
** This is broken out of process_input() mainly for readability.
** The return is TRUE for dot-command ready to run, else false.
*/
static int line_join_done(DCmd_ScanState dcss, char *zLine,
i64 *pnLength, char *pcLE){
/* It is ready only if has no open argument or escaped newline. */
int bOpen = DCSS_IsOpen(dcss);
if( !DCSS_EndEscaped(dcss) ){
*pcLE = '\n';
return !bOpen;
}else{
*pcLE = (bOpen || DCSS_InDarkArg(dcss))? 0 : ' ';
/* Swallow the trailing escape character. */
zLine[--*pnLength] = 0;
return 0;
}
}
#endif
/*
** Grow the accumulation line buffer to accommodate ncNeed chars.
** In/out parameters pzBuf and pnAccum reference the buffer and its size.
** The buffer must eventually be sqlite3_free()'ed by the caller.
*/
static void grow_line_buffer(char **pzBuf, i64 *pnAccum, int ncNeed){
if( ncNeed > *pnAccum ){
*pnAccum += *pnAccum + (*pnAccum>>1) + 100;
*pzBuf = sqlite3_realloc(*pzBuf, *pnAccum);
shell_check_ooms(*pzBuf);
}
}
/*
** Read input from designated source (p->pInSource) and process it.
** If pInSource==0 then input is interactive - the user is typing it.
** Otherwise, input is coming from a file, stream device or string.
** Prompts issue and history is saved only for interactive input.
** An interrupt signal will cause this routine to exit immediately,
** with "exit demanded" code returned, unless input is interactive.
**
** Returns are the post-execute values of enum DotCmdRC:
** DCR_Ok, DCR_Return, DCR_Exit, DCR_Abort
** each of which may be bit-wise or'ed with DCR_Error.
*/
static DotCmdRC process_input(ShellInState *psi){
char *zLineInput = 0; /* a line-at-a-time input buffer or usable result */
char *zLineAccum = 0; /* accumulation buffer, used for multi-line input */
/* Above two pointers could be local to the group handling loop, but are
* not so that the number of memory allocations can be reduced. They are
* reused from one incoming group to another, realloc()'ed as needed. */
i64 naAccum = 0; /* tracking how big zLineAccum buffer has become */
/* Flag for ending the overall group processing loop, always 1 or 0 */
u8 bInputEnd = 0;
/* Termination kind: DCR_Ok, DCR_Error, DCR_Return, DCR_Exit, DCR_Abort,
* the greatest of whichever is applicable */
u8 termKind = (XSS(psi)->shellAbruptExit==0)? DCR_Ok : DCR_Exit;
/* Flag to indicate input from shell invocation argument. */
u8 bInvokeArg = INSOURCE_IS_INVOKEARG(psi->pInSource);
/* Flag to affect prompting and interrupt action */
u8 bInteractive = INSOURCE_IS_INTERACTIVE(psi->pInSource);
int nErrors = 0; /* count of errors during execution or its prep */
/* Block overly-recursive or absurdly nested input redirects. */
if( psi->inputNesting>=MAX_INPUT_NESTING ){
InSource *pInSrc = psi->pInSource->pFrom;
const char *zLead = "Input nesting limit ("
SHELL_STRINGIFY(MAX_INPUT_NESTING)") reached,";
int i = 3;
assert(pInSrc!=0 && MAX_INPUT_NESTING>0);
while( i-->0 && pInSrc!=0 ){
utf8_printf(STD_ERR,
"%s from line %d of \"%s\"",
zLead, pInSrc->lineno, pInSrc->zSourceSay);
zLead = (i%2==0)? "\n" : "";
pInSrc=pInSrc->pFrom;
}
utf8_printf(STD_ERR, " ...\nError: Check recursion.\n");
return DCR_Error;
}
++psi->inputNesting;
/* line-group processing loop (per SQL block, dot-command or comment) */
while( !bInputEnd && termKind==DCR_Ok ){
#if SHELL_DYNAMIC_EXTENSION
ScriptSupport *pSS = psi->script;
#endif
int nGroupLines = 0; /* count of lines belonging to this group */
i64 ncLineIn = 0; /* how many (non-zero) chars are in zLineInput */
i64 ncLineAcc = 0; /* how many (non-zero) chars are in zLineAccum */
i64 iLastLine = 0; /* index of last accumulated line start */
/* Initialize resumable scanner(s). */
SqlScanState sqScanState = SSS_Start; /* for SQL scan */
#if SHELL_EXTENDED_PARSING
DCmd_ScanState dcScanState = DCSS_Start; /* for dot-command scan */
int nLeadWhite = 0; /* skips over initial whitespace to . or # */
char cLineEnd = '\n'; /* May be swallowed or replaced with space. */
#else
# define nLeadWhite 0 /* For legacy parsing, no white before . or # . */
# define cLineEnd '\n' /* For legacy parsing, this always joins lines. */
#endif
/* An ordered enum to record kind of incoming line group. Its ordering
* means than a value greater than Comment implies something runnable.
*/
enum { Tbd = 0, Eof, Comment, Sql, Cmd
#if SHELL_DYNAMIC_EXTENSION
, Script
#endif
} inKind = Tbd;
/* An enum signifying the group disposition state */
enum {
Incoming, Runnable, Dumpable, Erroneous, Ignore
} disposition = Incoming;
char **pzLineUse = &zLineInput; /* ref line to be processed */
i64 *pncLineUse = &ncLineIn; /* ref that line's char count */
int iStartline = 0; /* starting line number of group */
seenInterrupt = 0;
fflush(psi->out);
CONTINUE_PROMPT_RESET;
zLineInput = one_input_line(psi->pInSource, zLineInput,
nGroupLines>0, &shellPrompts);
if( zLineInput==0 ){
bInputEnd = 1;
inKind = Eof;
disposition = Ignore;
if( bInteractive ) printf("\n");
}else{
++nGroupLines;
iStartline = psi->pInSource->lineno;
ncLineIn = strlen30(zLineInput);
if( seenInterrupt ){
seenInterrupt = 0;
disposition = Dumpable;
}else{
/* Classify and check for single-line dispositions, prep for more. */
#if SHELL_EXTENDED_PARSING
nLeadWhite = (SHEXT_PARSING(psi))
? skipWhite(zLineInput)-zLineInput
: 0; /* Disallow leading whitespace for . or # in legacy mode. */
#endif
#if SHELL_DYNAMIC_EXTENSION
if( pSS && pSS->pMethods->isScriptLeader(pSS, zLineInput+nLeadWhite) ){
inKind = Script;
}else
#endif
{
switch( zLineInput[nLeadWhite] ){
case '.':
inKind = Cmd;
dot_command_scan(zLineInput+nLeadWhite, &dcScanState,
CONTINUE_PROMPT_PSTATE);
break;
case '#':
inKind = Comment;
break;
default:
/* Might be SQL, or a swallowable whole SQL comment. */
sql_prescan(zLineInput, &sqScanState, CONTINUE_PROMPT_PSTATE);
if( SSS_PLAINWHITE(sqScanState) ){
/* It's either all blank or a whole SQL comment. Swallowable. */
inKind = Comment;
}else{
/* Something dark, not a # comment or dot-command. Must be SQL. */
inKind = Sql;
}
break;
}
}
}
} /* end read/classify initial group input line */
/* Here, if not at end of input, the initial line of group is in, and
* it has been scanned and classified. Next, do the processing needed
* to recognize whether the initial line or accumulated group so far
* is complete such that it may be run, and perform joining of more
* lines into the group while it is not so complete. This loop ends
* with the input group line(s) ready to be run, or if the input ends
* before it is ready, with the group marked as erroneous.
*/
while( disposition==Incoming ){
PROMPTS_UPDATE(inKind == Sql || inKind == Cmd);
/* Check whether more to accumulate, or ready for final disposition. */
switch( inKind ){
case Comment:
disposition = Dumpable;
case Cmd:
#if SHELL_EXTENDED_PARSING
if( SHEXT_PARSING(psi) ){
if( line_join_done(dcScanState, *pzLineUse, pncLineUse, &cLineEnd) ){
disposition = Runnable;
}
}else
#endif
disposition = Runnable; /* Legacy, any dot-command line is ready. */
break;
#if SHELL_DYNAMIC_EXTENSION
case Script:
if( pSS==0
|| pSS->pMethods->scriptIsComplete(pSS, *pzLineUse+nLeadWhite, 0) ){
disposition = Runnable;
}
break;
#endif
case Sql:
/* Check to see if it is complete and ready to run. */
if( SSS_SEMITERM(sqScanState) && 1==sqlite3_complete(*pzLineUse)){
disposition = Runnable;
}else if( SSS_PLAINWHITE(sqScanState) ){
/* It is a leading single-line or multi-line comment. */
disposition = Runnable;
inKind = Comment;
}else{
char *zT = line_is_command_terminator(zLineInput);
if( zT!=0 ){
/* Last line is a lone go or / -- prep for running it. */
if( nGroupLines>1 ){
disposition = Runnable;
memcpy(*pzLineUse+iLastLine,";\n",3);
*pncLineUse = iLastLine + 2;
}else{
/* Unless nothing preceded it, then dump it. */
disposition = Dumpable;
}
}
}
break;
case Tbd: case Eof: default: assert(0); /* Not reachable */
} /* end switch on inKind */
/* Collect and accumulate more input if group not yet complete. */
if( disposition==Incoming ){
if( nGroupLines==1 ){
grow_line_buffer(&zLineAccum, &naAccum, ncLineIn+2);
/* Copy line just input */
memcpy(zLineAccum, zLineInput, ncLineIn);
zLineAccum[ncLineIn] = 0;
ncLineAcc = ncLineIn;
pzLineUse = &zLineAccum;
pncLineUse = &ncLineAcc;
}
/* Read in next line of group, (if available.) */
zLineInput = one_input_line(psi->pInSource, zLineInput,
nGroupLines>0, &shellPrompts);
if( zLineInput==0 ){
bInputEnd = 1;
if( inKind==Sql && bInvokeArg ){
/* As a special dispensation, SQL arguments on the command line
** do not need to end with ';' (or a lone go.) */
if( nGroupLines>1 ){
grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+2);
}
strcpy( zLineAccum+ncLineAcc, ";" );
if( 1==sqlite3_complete(*pzLineUse) ){
zLineAccum[ncLineAcc] = 0;
disposition = Runnable;
continue;
}
}
disposition = Erroneous;
inKind = Eof;
if( bInteractive ) printf("\n");
continue;
}
++nGroupLines;
ncLineIn = strlen30(zLineInput);
/* Scan line just input (if needed) and append to accumulation. */
switch( inKind ){
case Cmd:
dot_command_scan(zLineInput, &dcScanState, CONTINUE_PROMPT_PSTATE);
break;
case Sql:
sql_prescan(zLineInput, &sqScanState, CONTINUE_PROMPT_PSTATE);
break;
default:
break;
}
grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+ncLineIn+2);
/* Join lines as setup by exam of previous line(s). */
if( cLineEnd!=0 ) zLineAccum[ncLineAcc++] = cLineEnd;
#if SHELL_EXTENDED_PARSING
cLineEnd = '\n'; /* reset to default after use */
#endif
memcpy(zLineAccum+ncLineAcc, zLineInput, ncLineIn);
iLastLine = ncLineAcc;
ncLineAcc += ncLineIn;
zLineAccum[ncLineAcc] = 0;
} /* end glom another line */
} /* end group collection loop */
/* Here, the group is fully collected or known to be incomplete forever. */
CONTINUE_PROMPT_RESET;
switch( disposition ){
case Dumpable:
echo_group_input(psi, *pzLineUse);
#if SHELL_DYNAMIC_EXTENSION
if( inKind==Script && pSS!=0 ) pSS->pMethods->resetCompletionScan(pSS);
#endif
break;
case Runnable:
switch( inKind ){
case Sql:
echo_group_input(psi, *pzLineUse);
nErrors += runOneSqlLine(XSS(psi), *pzLineUse,
INSOURCE_IS_INTERACTIVE(psi->pInSource),
iStartline);
break;
case Cmd: {
DotCmdRC dcr;
echo_group_input(psi, *pzLineUse);
dcr = do_dot_command(*pzLineUse+nLeadWhite, XSS(psi));
nErrors += (dcr & DCR_Error);
dcr &= ~DCR_Error;
if( dcr > termKind ) termKind = dcr;
break;
}
#if SHELL_DYNAMIC_EXTENSION
case Script: {
char *zErr = 0;
DotCmdRC dcr;
assert(pSS!=0);
/* Consider: Should echo flag be honored here? */
pSS->pMethods->resetCompletionScan(pSS);
dcr = pSS->pMethods->runScript(pSS, *pzLineUse+nLeadWhite,
XSS(psi), &zErr);
if( dcr!=DCR_Ok || zErr!=0 ){
/* Future: Handle errors more informatively and like dot commands. */
nErrors += (dcr!=DCR_Ok);
if( zErr!=0 ){
utf8_printf(STD_ERR, "Error: %s\n", zErr);
sqlite3_free(zErr);
}
}
break;
}
#endif
default:
assert(inKind!=Tbd);
break;
}
if( XSS(psi)->shellAbruptExit!=0 ){
termKind = DCR_Exit;
}
break;
case Erroneous:
utf8_printf(STD_ERR, "Error: Input incomplete at line %d of \"%s\"\n",
psi->pInSource->lineno, psi->pInSource->zSourceSay);
#if SHELL_DYNAMIC_EXTENSION
if( inKind==Script && pSS!=0 ) pSS->pMethods->resetCompletionScan(pSS);
#endif
++nErrors;
break;
case Ignore:
break;
default: assert(0);
}
if( bail_on_error && nErrors>0 && termKind==DCR_Ok ) termKind = DCR_Error;
} /* end group consume/prep/(run, dump or complain) loop */
/* Cleanup and determine return value based on flags and error count. */
free(zLineInput); /* Allocated via malloc() by readline or equivalents. */
sqlite3_free(zLineAccum);
/* Translate DCR_Return because it has been done here, not to propagate
* unless input is from shell invocation argument. */
if( termKind==DCR_Return && psi->pInSource!=&cmdInSource ){
termKind = DCR_Ok;
}
return termKind|(nErrors>0);
}
/*
** Return a pathname which is the user's home directory.
** A 0 return indicates an error of some kind.
*/
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) \
&& !defined(__RTP__) && !defined(_WRS_KERNEL) && !defined(SQLITE_WASI)
{
struct passwd *pwent;
uid_t uid = getuid();
if( (pwent=getpwuid(uid)) != NULL) {
home_dir = pwent->pw_dir;
}
}
#endif
#if defined(_WIN32_WCE)
/* Windows CE (arm-wince-mingw32ce-gcc) does not provide getenv()
*/
home_dir = "/";
#else
#if defined(_WIN32) || defined(WIN32)
if (!home_dir) {
home_dir = getenv("USERPROFILE");
}
#endif
if (!home_dir) {
home_dir = getenv("HOME");
}
#if defined(_WIN32) || defined(WIN32)
if (!home_dir) {
char *zDrive, *zPath;
int n;
zDrive = getenv("HOMEDRIVE");
zPath = getenv("HOMEPATH");
if( zDrive && zPath ){
n = strlen30(zDrive) + strlen30(zPath) + 1;
home_dir = malloc( n );
if( home_dir==0 ) return 0;
sqlite3_snprintf(n, home_dir, "%s%s", zDrive, zPath);
return home_dir;
}
home_dir = "c:\\";
}
#endif
#endif /* !_WIN32_WCE */
if( home_dir ){
int n = strlen30(home_dir) + 1;
char *z = malloc( n );
if( z ) memcpy(z, home_dir, n);
home_dir = z;
}
return home_dir;
}
/*
** On non-Windows platforms, look for $XDG_CONFIG_HOME.
** If ${XDG_CONFIG_HOME}/sqlite3/sqliterc is found, return
** the path to it, else return 0.
*/
#if defined(_WIN32) || defined(WIN32) || defined(_WIN32_WCE) \
|| defined(__RTP__) || defined(_WRS_KERNEL)
# define find_xdg_config() 0
#else
static const char *find_xdg_config(void){
char *zXdgConfig;
const char *zXdgHome;
zXdgHome = getenv("XDG_CONFIG_HOME");
if( zXdgHome==0 ){
return 0;
}
zXdgConfig = smprintf("%s/sqlite3/sqliterc", zXdgHome);
shell_check_ooms(zXdgConfig);
if( access(zXdgConfig,0)!=0 ){
sqlite3_free(zXdgConfig);
return 0;
}
return zXdgConfig;
}
#endif
/*
** Read input from the file given by sqliterc_override. Or if that
** parameter is NULL, take input from the first of find_xdg_config()
** or ~/.sqliterc which can be found, else do nothing.
** The return is similar to process_input() (0 success, 1 error, x abort)
*/
static void process_sqliterc(
ShellInState *psi, /* shell state (internal) */
const char *sqliterc_override /* Name of config file. NULL to use default */
){
char *home_dir = NULL;
const char *sqliterc = sqliterc_override;
char *zBuf = 0;
FILE *inUse;
ResourceMark mark = holder_mark();
if( sqliterc == NULL ){
sqliterc = find_xdg_config();
}
if( sqliterc == NULL ){
home_dir = find_home_dir(0);
if( home_dir==0 ){
raw_printf(STD_ERR, "-- warning: cannot find home directory;"
" cannot read ~/.sqliterc\n");
return;
}
zBuf = smprintf("%s/.sqliterc",home_dir);
shell_check_ooms(zBuf);
sqliterc = zBuf;
sstr_holder(zBuf);
}
inUse = fopen(sqliterc,"rb");
if( inUse!=0 ){
InSource inSourceRedir
= INSOURCE_FILE_REDIR(inUse, sqliterc, psi->pInSource);
AnyResourceHolder arh = { &psi->pInSource, (GenericFreer)finish_InSource };
DotCmdRC rc;
psi->pInSource = &inSourceRedir;
inSourceRedir.closer.stream = fclose;
if( stdin_is_interactive ){
utf8_printf(STD_ERR,"-- Loading resources from %s\n",sqliterc);
}
any_ref_holder(&arh);
rc = process_input(psi);
if( rc>DCR_Ok && bail_on_error ){
XSS(psi)->shellAbruptExit = 0x100|((rc>>1)&0x3);
}
}else if( sqliterc_override!=0 ){
utf8_printf(STD_ERR,"cannot open: \"%s\"\n", sqliterc);
if( bail_on_error ){
XSS(psi)->shellAbruptExit = 0x102;
}
}
RESOURCE_FREE(mark);
}
/*
** Show available command line options
*/
static const char *zOptions =
" -- treat no subsequent arguments as options\n"
#if ARCHIVE_ENABLE
" -A ARGS... run \".archive ARGS\" and exit\n"
#endif
" -append append the database to the end of the file\n"
" -ascii set output mode to 'ascii'\n"
" -bail stop after hitting an error\n"
" -batch force batch I/O\n"
" -box set output mode to 'box'\n"
" -column set output mode to 'column'\n"
" -cmd COMMAND run \"COMMAND\" before reading stdin\n"
" -csv set output mode to 'csv'\n"
#if !defined(SQLITE_OMIT_DESERIALIZE)
" -deserialize open the database using sqlite3_deserialize()\n"
#endif
" -echo print inputs before execution\n"
" -init FILENAME read/process named file in lieu of sqliterc\n"
" -[no]header turn headers on or off\n"
#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
" -heap SIZE Size of heap for memsys3 or memsys5\n"
#endif
" -help show this message\n"
" -html set output mode to HTML\n"
" -interactive force interactive I/O\n"
" -json set output mode to 'json'\n"
" -line set output mode to 'line'\n"
" -list set output mode to 'list'\n"
" -lookaside SIZE N use N entries of SZ bytes for lookaside memory\n"
" -markdown set output mode to 'markdown'\n"
#if !defined(SQLITE_OMIT_DESERIALIZE)
" -maxsize N maximum size for a --deserialize database\n"
#endif
" -memtrace trace all memory allocations and deallocations\n"
" -mmap N default mmap size set to N\n"
#ifdef SQLITE_ENABLE_MULTIPLEX
" -multiplex enable the multiplexor VFS\n"
#endif
" -newline SEP set output row separator. Default: '\\n'\n"
" -nofollow refuse to open symbolic links to database files\n"
" -nonce STRING set the safe-mode escape nonce\n"
" -nullvalue TEXT set text string for NULL values. Default ''\n"
" -pagecache SIZE N use N slots of SZ bytes each for page cache memory\n"
" -pcachetrace trace all page cache operations\n"
" -quote set output mode to 'quote'\n"
" -readonly open the database read-only\n"
" -safe enable safe-mode\n"
" -separator SEP set output column separator. Default: '|'\n"
#if SHELL_EXTENSIONS
" -shxopts BMASK enable shell extension features (7=all, 0=none)\n"
#endif
#ifdef SQLITE_ENABLE_SORTER_REFERENCES
" -sorterref SIZE sorter references threshold size\n"
#endif
" -stats print memory stats before each finalize\n"
" -table set output mode to 'table'\n"
" -tabs set output mode to 'tabs'\n"
" -unsafe-testing allow unsafe commands and modes for testing\n"
#if SHELL_WIN_UTF8_OPT
" -utf8 setup interactive console code page for UTF-8\n"
#endif
" -version show SQLite version\n"
" -vfs NAME use NAME as the default VFS\n"
#ifdef SQLITE_ENABLE_VFSTRACE
" -vfstrace enable tracing of all VFS calls\n"
#endif
#ifdef SQLITE_HAVE_ZLIB
" -zip open the file as a ZIP Archive\n"
#endif
;
static void usage(int showDetail){
utf8_printf(STD_ERR,
"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. Defaults to :memory:.\n", Argv0);
if( showDetail ){
utf8_printf(STD_ERR, "OPTIONS include:\n%s", zOptions);
}else{
raw_printf(STD_ERR, "Use the -help option for additional information\n");
}
}
/*
** Internal check: Verify that the SQLite is uninitialized. Print a
** error message if it is initialized.
*/
static void verify_uninitialized(void){
if( sqlite3_config(-1)==SQLITE_MISUSE ){
utf8_printf(STD_OUT, "WARNING: attempt to configure SQLite after"
" initialization.\n");
}
}
static void sayInterrupted(void){
if( seenInterrupt ){
fprintf(stderr, "SQLite CLI interrupted.\n");
}
}
/*
** Initialize the internal and external shell state information.
** Does no heap allocation, so will do no OOM abort.
*/
static void main_init(ShellInState *pDatai, ShellExState *pDatax) {
memset(pDatai, 0, sizeof(*pDatai));
memset(pDatax, 0, sizeof(*pDatax));
pDatax->sizeofThis = sizeof(*pDatax);
pDatax->pSIS = pDatai;
pDatai->pSXS = pDatax;
pDatax->pShowHeader = &pDatai->showHeader;
pDatax->zFieldSeparator = &pDatai->colSeparator[0];
pDatax->zRecordSeparator = &pDatai->rowSeparator[0];
pDatax->zNullValue = &pDatai->nullValue[0];
pDatai->out = STD_OUT;
pDatai->normalMode = pDatai->cMode = pDatai->mode = MODE_List;
pDatai->autoExplain = 1;
pDatai->pAuxDb = &pDatai->aAuxDb[0];
memcpy(pDatai->colSeparator,SEP_Column, 2);
memcpy(pDatai->rowSeparator,SEP_Row, 2);
pDatai->showHeader = 0;
pDatai->shellFlgs = SHFLG_Lookaside;
sqlite3_config(SQLITE_CONFIG_LOG, shellLog, pDatai);
#ifndef SQLITE_SHELL_FIDDLE
verify_uninitialized();
#else
datai.zDefaultDbName = "/fiddle.sqlite3";
#endif
sqlite3_config(SQLITE_CONFIG_URI, 1);
sqlite3_config(SQLITE_CONFIG_LOG, shellLog, pDatai);
sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> ");
sqlite3_snprintf(sizeof(continuePrompt), continuePrompt," ...> ");
/* Source at EOF (for now), saying it is command line. */
pDatai->pInSource = &cmdInSource;
}
/*
** Takedown and deallocate data structures held in shell state structs.
** Does no heap allocation, so will do no OOM abort. It leaves those
** structs zero-initialized to aid valgrind memory leak reporting.
*/
static void main_cleanup(ShellInState *psi, ShellExState *psx) {
int i;
set_table_name(psx, 0);
if( psx->dbUser ){
session_close_all(psi, -1);
close_db(psi,psx->dbUser);
}
for(i=0; i<ArraySize(psi->aAuxDb); i++){
sqlite3_free(psi->aAuxDb[i].zFreeOnClose);
if( psi->aAuxDb[i].db ){
session_close_all(psi, i);
close_db(psi,psi->aAuxDb[i].db);
}
}
find_home_dir(1);
output_reset(psi);
psi->doXdgOpen = 0;
clearTempFile(psi);
sqlite3_free(psi->zEditor);
explain_data_delete(psi);
#if SHELL_DYNAMIC_EXTENSION
notify_subscribers(psi, NK_DbAboutToClose, psx->dbShell);
/* It is necessary that the shell DB be closed after the user DBs.
* This is because loaded extensions are held by the shell DB and
* are therefor (automatically) unloaded when it is closed. */
notify_subscribers(psi, NK_ExtensionUnload, psx->dbShell);
/* Forcefully unsubscribe any extension which ignored above or did
* not unsubscribe upon getting above event. */
unsubscribe_extensions(psi);
/* This must be done before any extensions unload. */
free_all_shext_tracking(psi);
/* Unload extensions and free the DB used for dealing with them. */
sqlite3_close(psx->dbShell);
/* This notification can only reach statically linked extensions. */
notify_subscribers(psi, NK_ShutdownImminent, 0);
/* Forcefully unsubscribe static extension event listeners. */
subscribe_events(psx, 0, psx, NK_Unsubscribe, 0);
#endif /* SHELL_DYNAMIC_EXTENSION */
free(psx->pSpecWidths);
free(psi->zNonce);
for(i=0; i<psi->nSavedModes; ++i) sqlite3_free(psi->pModeStack[i]);
/* Clear shell state objects so that valgrind detects real memory leaks. */
memset(psi, 0, sizeof(*psi));
memset(psx, 0, sizeof(*psx));
}
/* Setup or restore ^C interrupt handling. */
static void interruption_alter(int setup_nrestore){
#if defined(SIGINT)
# if !defined(SHELL_LEGACY_SIGINT)
static struct sigaction cli_sigaction = { 0 };
static struct sigaction cli_sigrestore = { 0 };
if( setup_nrestore ){
/* Leave .sa_mask and .sa_flags zero-initialized. */
cli_sigaction.sa_handler = interrupt_handler;
sigaction(SIGINT,&cli_sigaction,&cli_sigrestore);
}else{
sigaction(SIGINT,&cli_sigrestore,&cli_sigaction);
}
# else
static __sighandler_t cli_sigrestore = SIG_DFL;
if( setup_nrestore ){
cli_sigrestore = signal(SIGINT, interrupt_handler);
}else{
signal(SIGINT, cli_sigrestore);
}
# endif
#elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE)
BOOL add = setup_nrestore!=0;
BOOL success = SetConsoleCtrlHandler(ConsoleCtrlHandler, add);
if( add && !success ) fprintf(stderr, "No ^C handler.\n");
#else
(void)setup_nrestore;
#endif
}
/*
** Prepare execution environment for running shell.
** Return is SQLITE_OK on success, else an error code.
*/
static int execution_prepare(ShellInState *psi, ShellExState *psx){
/* Register the control-C (SIGINT) handler.
** Make sure we have a valid signal handler early, before anything
** is done that might take long. */
interruption_alter(1);
/* Ensure stderr is unbuffered. */
setvbuf(STD_ERR, 0, _IONBF, 0);
#ifdef SQLITE_SHELL_FIDDLE
stdin_is_interactive = 0;
stdout_is_console = 1;
#else
stdin_is_interactive = isatty(0);
stdout_is_console = isatty(1);
#endif
#if SHELL_WIN_UTF8_OPT
upon_terminate(console_restore);
#endif
upon_terminate(sayInterrupted);
#if !defined(_WIN32_WCE)
if( getenv("SQLITE_DEBUG_BREAK") ){
if( isatty(0) && isatty(2) ){
fprintf(STD_ERR,
"attach debugger to process %d and press any key to continue.\n",
GETPID());
fgetc(STD_IN);
}else{
#if defined(_WIN32) || defined(WIN32)
#if SQLITE_OS_WINRT
__debugbreak();
#else
DebugBreak();
#endif
#elif defined(SIGTRAP)
raise(SIGTRAP);
#endif
}
}
#endif
/**** Data initialization. ****/
main_init(psi,psx);
init_std_inputs(stdin);
#if SHELL_DATAIO_EXT
{
BuiltInFFExporter *pFFX
= (BuiltInFFExporter *)sqlite3_malloc(sizeof(BuiltInFFExporter));
BuiltInCMExporter *pCMX
= (BuiltInCMExporter *)sqlite3_malloc(sizeof(BuiltInCMExporter));
if( pFFX!=0 && pCMX!=0 ){
BuiltInFFExporter ffExporter = BI_FF_EXPORTER_INIT( psi );
BuiltInCMExporter cmExporter = BI_CM_EXPORTER_INIT( psi );
memcpy(pFFX, &ffExporter, sizeof(BuiltInFFExporter));
memcpy(pCMX, &cmExporter, sizeof(BuiltInCMExporter));
psi->pFreeformExporter = (ExportHandler *)pFFX;
psi->pColumnarExporter = (ExportHandler *)pCMX;
psi->pActiveExporter = psi->pFreeformExporter;
}else{
sqlite3_free(pFFX);
sqlite3_free(pCMX);
return SQLITE_NOMEM;
}
}
#endif
return SQLITE_OK;
}
#ifndef SQLITE_SHELL_FIDDLE
/*
** Undo execution environment preparation for shell.
*/
static void execution_restore(ShellInState *psi, ShellExState *psx){
#if SHELL_DATAIO_EXT
ExportHandler *pEHCM = psi->pColumnarExporter;
ExportHandler *pEHFF = psi->pFreeformExporter;
pEHCM->pMethods->destruct(pEHCM);
pEHFF->pMethods->destruct(pEHFF);
sqlite3_free(pEHCM);
sqlite3_free(pEHFF);
#endif
main_cleanup(psi, psx);
interruption_alter(0);
}
#endif
/*
** Output text to the console in a font that attracts extra attention.
*/
#ifdef _WIN32
static void printBold(const char *zText){
#if !SQLITE_OS_WINRT
HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO defaultScreenInfo;
GetConsoleScreenBufferInfo(out, &defaultScreenInfo);
SetConsoleTextAttribute(out,
FOREGROUND_RED|FOREGROUND_INTENSITY
);
#endif
fprintf(STD_OUT, "%s", zText);
#if !SQLITE_OS_WINRT
SetConsoleTextAttribute(out, defaultScreenInfo.wAttributes);
#endif
}
#else
static void printBold(const char *zText){
fprintf(STD_OUT, "\033[1m%s\033[0m", zText);
}
#endif
/*
** Get the argument to an --option. Throw an error and die if no argument
** is available.
*/
static char *cmdline_option_value(int argc, char **argv, int i){
if( i==argc ){
utf8_printf(STD_ERR, "%s: Error: missing argument to %s\n",
argv[0], argv[argc-1]);
quit_moan("invocation error", 1);
}
return argv[i];
}
static void zapGlobalDbLock(void){
if( pGlobalDbLock ){
sqlite3_mutex_free(pGlobalDbLock);
pGlobalDbLock = 0;
}
}
/* A vector of command strings collected from the command line. */
typedef struct CmdArgs {
char **azCmd; /* the strings */
int nCmd; /* how many collected */
u8 bArgsHeld; /* whether "the strings" are owned by this object */
} CmdArgs;
/* Freer for above. */
static void freeCmdArgs(CmdArgs *pca){
int i;
if( !pca ) return;
if( pca->bArgsHeld ){
for( i=0; i<pca->nCmd; ++i ){
free(pca->azCmd[i]);
}
}
free(pca->azCmd);
pca->azCmd = 0;
pca->nCmd = 0;
}
/* Capacity grower for above. May terminate for OOM. */
static void growCmdArgs(CmdArgs *pca, int nRoom){
void *vaz = realloc(pca->azCmd, sizeof(pca->azCmd[0])*nRoom);
shell_check_oomm(vaz);
pca->azCmd = (char**)vaz;
}
/* Data collected during args scanning. */
typedef struct ArgsData {
int readStdin; /* whether stdin will be read */
int nOptsEnd; /* where -- seen, else argc */
const char *zInitFile; /* specified init file */
const char *zVfs; /* -vfs command-line option */
short bQuiet; /* -quiet option */
#if ARCHIVE_ENABLE
short bArCmd; /* -A option given */
#endif /* ARCHIVE_ENABLE */
} ArgsData;
/*
** Perform CLI invocation argument processing.
** This code is collected here for convenience, to declutter main()
** and to make this processing a little simpler to understand.
** Parameters are:
** argc, argv : command-line arguments
** pass : the pass number, 1 or 2
** *pcaCmd (out) : arguments preceded by -cmd (which are run first)
** *pcaBare (out) : non-option or -A arguments (never the DB name)
** *pad (out, in/out) : option data not held in Shell??State
**
** The 1st pass must be done with SQLite uninitialized.
** The 2nd pass is indifferent to SQLite initialized or not.
**
** Returns are: 0 => normal, 1 => error, 2 => quit
**
** This function may terminate abruptly under OOM conditions.
*/
static int scanInvokeArgs(int argc, char **argv, int pass, ShellInState *psi,
CmdArgs *pcaCmd, CmdArgs *pcaBare, ArgsData *pad){
int rc = 0;
int i;
if( pass==1 ){
for(i=1; i<argc && rc<2; i++){
char *z = argv[i];
if( z[0]!='-' || i>pad->nOptsEnd ){
if( psi->aAuxDb->zDbFilename==0 ){
psi->aAuxDb->zDbFilename = z;
}else{
growCmdArgs(pcaBare, pcaBare->nCmd+1);
pcaBare->azCmd[pcaBare->nCmd++] = z;
/* Excess, non-option-like arguments are interpreted as SQL (or
** dot-commands) and mean that nothing is to be read from stdin. */
pad->readStdin = 0;
}
continue;
}
if( z[1]=='-' ) z++;
if( cli_strcmp(z, "-")==0 ){
pad->nOptsEnd = i;
continue;
}else if( cli_strcmp(z,"-separator")==0
|| cli_strcmp(z,"-nullvalue")==0
|| cli_strcmp(z,"-newline")==0
|| cli_strcmp(z,"-cmd")==0
){
(void)cmdline_option_value(argc, argv, ++i);
/* Will pickup value on next pass. */
}else if( cli_strcmp(z,"-init")==0 ){
pad->zInitFile = cmdline_option_value(argc, argv, ++i);
}else if( cli_strcmp(z,"-batch")==0 ){
/* Need to check for batch mode here to so we can avoid printing
** informational messages (like from process_sqliterc) before
** we do the actual processing of arguments later in a second pass.
*/
stdin_is_interactive = 0;
}else if( cli_strcmp(z,"-heap")==0 ){
#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
const char *zSize;
sqlite3_int64 szHeap;
zSize = cmdline_option_value(argc, argv, ++i);
szHeap = integerValue(zSize);
if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000;
verify_uninitialized();
sqlite3_config(SQLITE_CONFIG_HEAP,malloc((int)szHeap),(int)szHeap, 64);
#else
(void)cmdline_option_value(argc, argv, ++i);
#endif
}else if( cli_strcmp(z,"-pagecache")==0 ){
sqlite3_int64 n, sz;
void *pvCache = 0;
sz = integerValue(cmdline_option_value(argc,argv,++i));
if( sz>70000 ) sz = 70000;
if( sz<0 ) sz = 0;
n = integerValue(cmdline_option_value(argc,argv,++i));
if( sz>0 && n>0 && 0xffffffffffffLL/sz<n ){
n = 0xffffffffffffLL/sz;
}
verify_uninitialized();
if( n>0 && sz>0 ) pvCache = malloc(n*sz);
shell_check_oomm(pvCache);
sqlite3_config(SQLITE_CONFIG_PAGECACHE, pvCache, sz, n);
psi->shellFlgs |= SHFLG_Pagecache;
}else if( cli_strcmp(z,"-lookaside")==0 ){
int n, sz;
sz = (int)integerValue(cmdline_option_value(argc,argv,++i));
if( sz<0 ) sz = 0;
n = (int)integerValue(cmdline_option_value(argc,argv,++i));
if( n<0 ) n = 0;
verify_uninitialized();
sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n);
if( sz*n==0 ) psi->shellFlgs &= ~SHFLG_Lookaside;
}else if( cli_strcmp(z,"-threadsafe")==0 ){
int n;
n = (int)integerValue(cmdline_option_value(argc,argv,++i));
verify_uninitialized();
switch( n ){
case 0: sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); break;
case 2: sqlite3_config(SQLITE_CONFIG_MULTITHREAD); break;
default: sqlite3_config(SQLITE_CONFIG_SERIALIZED); break;
}
#ifdef SQLITE_ENABLE_VFSTRACE
}else if( cli_strcmp(z,"-vfstrace")==0 ){
extern int vfstrace_register(
const char *zTraceName,
const char *zOldVfsName,
int (*xOut)(const char*,void*),
void *pOutArg,
int makeDefault
);
vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,STD_ERR,1);
#endif
#ifdef SQLITE_ENABLE_MULTIPLEX
}else if( cli_strcmp(z,"-multiplex")==0 ){
extern int sqlite3_multiplex_initialize(const char*,int);
sqlite3_multiplex_initialize(0, 1);
#endif
}else if( cli_strcmp(z,"-mmap")==0 ){
sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
verify_uninitialized();
sqlite3_config(SQLITE_CONFIG_MMAP_SIZE, sz, sz);
#ifdef SQLITE_ENABLE_SORTER_REFERENCES
}else if( cli_strcmp(z,"-sorterref")==0 ){
sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
verify_uninitialized();
sqlite3_config(SQLITE_CONFIG_SORTERREF_SIZE, (int)sz);
#endif
}else if( cli_strcmp(z,"-vfs")==0 ){
pad->zVfs = cmdline_option_value(argc, argv, ++i);
#ifdef SQLITE_HAVE_ZLIB
}else if( cli_strcmp(z,"-zip")==0 ){
psi->openMode = SHELL_OPEN_ZIPFILE;
#endif
}else if( cli_strcmp(z,"-append")==0 ){
psi->openMode = SHELL_OPEN_APPENDVFS;
#ifndef SQLITE_OMIT_DESERIALIZE
}else if( cli_strcmp(z,"-deserialize")==0 ){
psi->openMode = SHELL_OPEN_DESERIALIZE;
}else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
psi->szMax = integerValue(argv[++i]);
#endif
}else if( cli_strcmp(z,"-readonly")==0 ){
psi->openMode = SHELL_OPEN_READONLY;
}else if( cli_strcmp(z,"-nofollow")==0 ){
psi->openFlags = SQLITE_OPEN_NOFOLLOW;
#if ARCHIVE_ENABLE
}else if( cli_strncmp(z, "-A",2)==0 ){
/* All remaining command-line arguments are passed to the ".archive"
** command, so ignore them */
break;
#endif
}else if( cli_strcmp(z, "-memtrace")==0 ){
sqlite3MemTraceActivate(STD_ERR);
}else if( cli_strcmp(z, "-pcachetrace")==0 ){
sqlite3PcacheTraceActivate(STD_ERR);
}else if( cli_strcmp(z,"-bail")==0 ){
bail_on_error = 1;
#if SHELL_EXTENSIONS
}else if( cli_strcmp(z,"-shxopts")==0 ){
psi->bExtendedDotCmds = (u8)integerValue(argv[++i]);
#endif
}else if( cli_strcmp(z,"-nonce")==0 ){
free(psi->zNonce);
z = cmdline_option_value(argc,argv,++i);
shell_check_oomm(psi->zNonce = strdup(z));
}else if( cli_strcmp(z,"-quiet")==0 ){
pad->bQuiet = (int)integerValue(cmdline_option_value(argc,argv,++i));
}else if( cli_strcmp(z,"-unsafe-testing")==0 ){
psi->shellFlgs |= SHFLG_TestingMode;
}else if( cli_strcmp(z,"-safe")==0 ){
/* catch this on the second pass (Unsafe is fine on invocation.) */
}
}
}else if( pass==2 ){
for(i=1; i<argc && rc<2; i++){
char *z = argv[i];
char *zModeSet = 0;
if( z[0]!='-' || i>=pad->nOptsEnd ) continue;
if( z[1]=='-' ){ z++; }
if( cli_strcmp(z,"-init")==0 ){
i++;
}else if( cli_strcmp(z,"-html")==0 ){
zModeSet = z;
}else if( cli_strcmp(z,"-list")==0 ){
zModeSet = z;
}else if( cli_strcmp(z,"-quote")==0 ){
zModeSet = z;
}else if( cli_strcmp(z,"-line")==0 ){
zModeSet = z;
}else if( cli_strcmp(z,"-column")==0 ){
zModeSet = z;
}else if( cli_strcmp(z,"-json")==0 ){
zModeSet = z;
}else if( cli_strcmp(z,"-markdown")==0 ){
zModeSet = z;
}else if( cli_strcmp(z,"-table")==0 ){
zModeSet = z;
}else if( cli_strcmp(z,"-box")==0 ){
zModeSet = z;
}else if( cli_strcmp(z,"-csv")==0 ){
zModeSet = z;
}else if( cli_strcmp(z,"-ascii")==0 ){
zModeSet = z;
}else if( cli_strcmp(z,"-tabs")==0 ){
zModeSet = z;
#ifdef SQLITE_HAVE_ZLIB
}else if( cli_strcmp(z,"-zip")==0 ){
psi->openMode = SHELL_OPEN_ZIPFILE;
#endif
}else if( cli_strcmp(z,"-append")==0 ){
psi->openMode = SHELL_OPEN_APPENDVFS;
#ifndef SQLITE_OMIT_DESERIALIZE
}else if( cli_strcmp(z,"-deserialize")==0 ){
psi->openMode = SHELL_OPEN_DESERIALIZE;
}else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
psi->szMax = integerValue(argv[++i]);
#endif
}else if( cli_strcmp(z,"-readonly")==0 ){
psi->openMode = SHELL_OPEN_READONLY;
}else if( cli_strcmp(z,"-nofollow")==0 ){
psi->openFlags |= SQLITE_OPEN_NOFOLLOW;
}else if( cli_strcmp(z,"-separator")==0 ){
sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator,
"%s",cmdline_option_value(argc,argv,++i));
}else if( cli_strcmp(z,"-newline")==0 ){
sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator,
"%s",cmdline_option_value(argc,argv,++i));
}else if( cli_strcmp(z,"-nullvalue")==0 ){
sqlite3_snprintf(sizeof(psi->nullValue), psi->nullValue,
"%s",cmdline_option_value(argc,argv,++i));
}else if( cli_strcmp(z,"-header")==0 ){
psi->showHeader = 1;
ShellSetFlagI(psi, SHFLG_HeaderSet);
}else if( cli_strcmp(z,"-noheader")==0 ){
psi->showHeader = 0;
ShellSetFlagI(psi, SHFLG_HeaderSet);
}else if( cli_strcmp(z,"-echo")==0 ){
ShellSetFlagI(psi, SHFLG_Echo);
}else if( cli_strcmp(z,"-eqp")==0 ){
psi->autoEQP = AUTOEQP_on;
}else if( cli_strcmp(z,"-eqpfull")==0 ){
psi->autoEQP = AUTOEQP_full;
}else if( cli_strcmp(z,"-stats")==0 ){
psi->statsOn = 1;
}else if( cli_strcmp(z,"-scanstats")==0 ){
psi->scanstatsOn = 1;
}else if( cli_strcmp(z,"-backslash")==0 ){
/* Undocumented command-line option: -backslash
** Causes C-style backslash escapes to be evaluated in SQL statements
** prior to sending the SQL into SQLite. Useful for injecting crazy
** bytes in the middle of SQL statements for testing and debugging.
*/
ShellSetFlagI(psi, SHFLG_Backslash);
}else if( cli_strcmp(z,"-bail")==0 ){
/* No-op. The bail_on_error flag should already be set. */
#if SHELL_EXTENSIONS
}else if( cli_strcmp(z,"-shxopts")==0 ){
i++; /* Handled on first pass. */
#endif
}else if( cli_strcmp(z,"-version")==0 ){
fprintf(STD_OUT, "%s %s\n", sqlite3_libversion(), sqlite3_sourceid());
rc = 2;
}else if( cli_strcmp(z,"-interactive")==0 ){
stdin_is_interactive = 1;
}else if( cli_strcmp(z,"-batch")==0 ){
stdin_is_interactive = 0;
}else if( cli_strcmp(z,"-utf8")==0 ){
#if SHELL_WIN_UTF8_OPT
console_utf8 = 1;
#endif /* SHELL_WIN_UTF8_OPT */
}else if( cli_strcmp(z,"-heap")==0 ){
i++;
}else if( cli_strcmp(z,"-pagecache")==0 ){
i+=2;
}else if( cli_strcmp(z,"-lookaside")==0 ){
i+=2;
}else if( cli_strcmp(z,"-threadsafe")==0 ){
i+=2;
}else if( cli_strcmp(z,"-nonce")==0 ){
i+=2;
}else if( cli_strcmp(z,"-mmap")==0 ){
i++;
}else if( cli_strcmp(z,"-memtrace")==0 ){
i++;
}else if( cli_strcmp(z,"-pcachetrace")==0 ){
i++;
#ifdef SQLITE_ENABLE_SORTER_REFERENCES
}else if( cli_strcmp(z,"-sorterref")==0 ){
i++;
#endif
}else if( cli_strcmp(z,"-vfs")==0 ){
i++;
#ifdef SQLITE_ENABLE_VFSTRACE
}else if( cli_strcmp(z,"-vfstrace")==0 ){
i++;
#endif
#ifdef SQLITE_ENABLE_MULTIPLEX
}else if( cli_strcmp(z,"-multiplex")==0 ){
i++;
#endif
}else if( cli_strcmp(z,"-help")==0 ){
usage(1);
rc = 2;
break;
}else if( cli_strcmp(z,"-cmd")==0 ){
/* Run commands that follow -cmd first and separately from commands
** that simply appear on the command-line. This is likely surprising.
** Better would be to run all commands in the order that they appear.
** But we retain this goofy behavior for historical compatibility. */
if( i==argc-1 ) break; /* Pretend (un)specified command is empty. */
growCmdArgs(pcaCmd, pcaCmd->nCmd+1);
pcaCmd->azCmd[pcaCmd->nCmd++] = cmdline_option_value(argc,argv,++i);
#if ARCHIVE_ENABLE
}else if( cli_strncmp(z, "-A", 2)==0 ){
if( pcaBare->nCmd>0 ){
utf8_printf(STD_ERR, "Error: cannot mix regular SQL or dot-commands"
" with \"%s\"\n", z);
rc = 1;
break;
}
growCmdArgs(pcaBare, argc-i+1);
if( z[2] ) pcaBare->azCmd[pcaBare->nCmd++] = &z[2];
while( i<argc ){
pcaBare->azCmd[pcaBare->nCmd++] = argv[i++];
}
pad->readStdin = 0;
pad->bArCmd = 1;
break;
#endif /* ARCHIVE_ENABLE */
}else if( cli_strcmp(z,"-safe")==0 ){
psi->bSafeMode = psi->bSafeModeFuture = 1;
}else if( cli_strcmp(z,"-unsafe-testing")==0 ){
/* Acted upon in first pass. */
}else if( cli_strcmp(z,"-quiet")==0 ){
++i;
}else{
utf8_printf(STD_ERR,"%s: Error: unknown option: %s\n", Argv0, z);
raw_printf(STD_ERR,"Use -help for a list of options.\n");
rc = 2;
}
if( zModeSet!=0 ){
char *azModeCmd[] = { ".mode", zModeSet+1 };
modeCommand(azModeCmd, 2, XSS(psi), 0);
psi->cMode = psi->mode;
}
}
}
return rc;
}
#ifndef SQLITE_SHELL_IS_UTF8
# if (defined(_WIN32) || defined(WIN32)) \
&& (defined(_MSC_VER) || (defined(UNICODE) && defined(__GNUC__)))
# define SQLITE_SHELL_IS_UTF8 (0)
# else
# define SQLITE_SHELL_IS_UTF8 (1)
# endif
#endif
#ifndef SHELL_MAIN
# if defined(SQLITE_SHELL_FIDDLE)
# define SHELL_MAIN fiddle_main
# elif SQLITE_SHELL_IS_UTF8
# define SHELL_MAIN main
# else
# define SHELL_MAIN wmain
# endif
#endif
#if SQLITE_SHELL_IS_UTF8
int SQLITE_CDECL SHELL_MAIN(int argc, char **argv){
#else
int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
char **argv;
#endif
#ifdef SQLITE_DEBUG
sqlite3_int64 mem_main_enter = 0;
#endif
#ifdef SQLITE_SHELL_FIDDLE
# define datai shellStateI
# define datax shellStateX
#else
ShellInState datai;
ShellExState datax;
#endif
RipStackDest mainRipDest = RIP_STACK_DEST_INIT;
int i; /* General purpose */
int iAbruptExitCode;
int rc = 0; /* main() exit code */
DotCmdRC drc = DCR_Ok;
int warnInmemoryDb = 0;
/* azCmd, nCmd, bArgsHeld */
CmdArgs cmdArgsCmd = {0,0,0}; /* for -cmd <do_x> invocation arguments */
AnyResourceHolder cacRH = {&cmdArgsCmd, (GenericFreer)freeCmdArgs};
CmdArgs cmdArgsBare = {0,0,0}; /* for bare or -A invocation arguments */
AnyResourceHolder cabRH = {&cmdArgsBare, (GenericFreer)freeCmdArgs};
/* readStdin, nOptsEnd, zInitFile, zVfs, bQuiet */
ArgsData argsData = { 1, argc, 0,0,0 };
#if !SQLITE_SHELL_IS_UTF8
CmdArgs argsUtf8 = {0,0,1};
AnyResourceHolder caRH = {&argsUtf8, (GenericFreer)freeCmdArgs};
#endif
RESOURCE_MARK(mark);
/* Just return error if using mismatched dynamic SQLite library version. */
#if USE_SYSTEM_SQLITE+0!=1
if( cli_strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,60)!=0 ){
utf8_printf(STD_ERR, "SQLite header and source version mismatch\n%s\n%s\n",
sqlite3_sourceid(), SQLITE_SOURCE_ID);
return 1;
}
#endif
sqlite3_initialize();
/* Create a mutex for thread-safe query execution interruption. */
if( pGlobalDbLock==0 ){
pGlobalDbLock = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
}
upon_terminate(zapGlobalDbLock);
#ifdef SQLITE_DEBUG
mem_main_enter = sqlite3_memory_used();
#endif
/* On Windows, we must translate command-line arguments into UTF-8.
** The SQLite memory allocator subsystem has to be enabled in order to
** do this. But we want to run an sqlite3_shutdown() afterwards so that
** subsequent sqlite3_config() calls will work. So copy all results into
** memory that does not come from the SQLite memory allocator.
*/
#if !SQLITE_SHELL_IS_UTF8
growCmdArgs(&argsUtf8, argc);
argsUtf8.nCmd = 0;
any_ref_holder(&caRH); /* This will normally activate as shell exits. */
for(i=0; i<argc; i++){
char *z = sqlite3_win32_unicode_to_utf8(wargv[i]);
shell_check_ooms(z);
sstr_holder(z);
argsUtf8.azCmd[i] = strdup(z);
shell_check_oomm(argsUtf8.azCmd[i]);
release_holder();
++argsUtf8.nCmd;
}
argv = argsUtf8.azCmd;
#endif
/**** Execution environment setup. ****/
sqlite3_shutdown();
if( (rc = execution_prepare(&datai,&datax))!=0 ){
utf8_printf(STD_ERR, "Cannot initialize. Quitting.\n");
return 1;
}
sqlite3_initialize();
/* From here on, within the true clause of this next test, various
** heap allocations are made which may fail, resulting in an abrupt
** shell exit. Such an exit happens in 1 of 2 ways: A held resource
** stack and the call stack are ripped back to this point; or just
** the held resource stack is ripped back and a process exit occurs.
** This kind of exit is considered unrecoverable, so it is taken for
** all processing, whether of invocation arguments, ~/.sqliterc, or
** input from stdin (and input redirects instigated there.)
*/
register_exit_ripper(&mainRipDest);
if( 0==RIP_TO_HERE(mainRipDest) ){
/**** Input processing. ****/
assert( argc>=1 && argv && argv[0] );
Argv0 = argv[0];
#if SHELL_DYNAMIC_EXTENSION
initStartupDir();
if( isExtendedBasename(Argv0) ){
datai.bExtendedDotCmds = SHELL_ALL_EXTENSIONS;
}
#endif
any_ref_holder(&cacRH);
any_ref_holder(&cabRH);
#ifdef SQLITE_SHELL_DBNAME_PROC
{
/* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name
** of a C-function that will provide the name of the database file. Use
** this compile-time option to embed this shell program in larger
** applications. */
extern void SQLITE_SHELL_DBNAME_PROC(const char**);
SQLITE_SHELL_DBNAME_PROC(&datai.pAuxDb->zDbFilename);
warnInmemoryDb = 0;
}
#endif
/* Do an initial pass through the command-line argument to locate
** the name of the database file, the name of the initialization file,
** the size of the alternative malloc heap,
** and the first command to execute.
*/
sqlite3_shutdown();
#ifndef SQLITE_SHELL_FIDDLE
verify_uninitialized();
#endif
i = scanInvokeArgs(argc, argv, /*pass*/ 1, &datai,
&cmdArgsCmd, &cmdArgsBare, &argsData);
#ifndef SQLITE_SHELL_FIDDLE
verify_uninitialized();
#endif
#ifdef SQLITE_SHELL_INIT_PROC
{
/* If the SQLITE_SHELL_INIT_PROC macro is defined, then it is the name
** of a C-function that will perform initialization actions on SQLite that
** occur just before or after sqlite3_initialize(). Use this compile-time
** option to embed this shell program in larger applications.
**
** The provided C-function must call sqlite3_initialize() sometime
** before returning (leaving SQLite in the initialized state.)
*/
extern void SQLITE_SHELL_INIT_PROC(void);
SQLITE_SHELL_INIT_PROC();
}
#else
/* All the sqlite3_config() calls have now been made. So it is safe
** to call sqlite3_initialize() and process any command line -vfs option. */
sqlite3_initialize();
#endif
if( argsData.zVfs ){
sqlite3_vfs *pVfs = sqlite3_vfs_find(argsData.zVfs);
if( pVfs ){
sqlite3_vfs_register(pVfs, 1);
}else{
utf8_printf(STD_ERR, "no such VFS: \"%s\"\n", argsData.zVfs);
rc = 1;
goto shell_bail;
}
}
if( datai.pAuxDb->zDbFilename==0 ){
#ifndef SQLITE_OMIT_MEMORYDB
datai.pAuxDb->zDbFilename = ":memory:";
warnInmemoryDb = argc==1;
#else
utf8_printf(STD_ERR,"%s: Error: no database filename specified\n", Argv0);
rc = 1;
goto shell_bail;
#endif
}
#ifndef SQLITE_SHELL_FIDDLE
sqlite3_appendvfs_init(0,0,0);
#endif
/* Go ahead and open the database file if it already exists. If the
** file does not exist, delay opening it. This prevents empty database
** files from being created if a user mistypes the database name argument
** to the sqlite command-line tool.
*/
if( access(datai.pAuxDb->zDbFilename, 0)==0 ){
open_db(&datax, 0);
}
/* Process the initialization file if there is one. If no -init option
** is given on the command line, look for a file named ~/.sqliterc and
** try to process it, without any quitting or bail-on-error.
*/
process_sqliterc(&datai,argsData.zInitFile);
/* Make a second pass through the command-line argument and set
** options. This second pass is delayed until after the initialization
** file is processed so that the command-line arguments will override
** settings in the initialization file.
*/
rc = scanInvokeArgs(argc, argv, /*pass*/ 2, &datai,
&cmdArgsCmd, &cmdArgsBare, &argsData);
if( rc>0 ){
goto shell_bail;
}
#if SHELL_WIN_UTF8_OPT
if( console_utf8 && stdin_is_interactive ){
console_prepare();
}else{
setBinaryMode(stdin, 0);
console_utf8 = 0;
}
#endif
if( cmdArgsCmd.nCmd > 0 ){
/* cmdArgsCmd not empty; some -cmd commands are to be run first. */
for( i=0; i<cmdArgsCmd.nCmd; ++i ){
set_invocation_cmd(cmdArgsCmd.azCmd[i]);
drc = process_input(&datai);
rc = (drc>2)? 2 : drc;
if( rc>0 ) goto shell_bail;
}
}
if( !argsData.readStdin && cmdArgsBare.nCmd>0 ){
/* cmdArgsBare holds either "bare command" arguments or -A arguments.
** (Former are arguments not naming the DB or beginning with '-'.)
** Run whichever kind there are. */
#if ARCHIVE_ENABLE
if( argsData.bArCmd ){
open_db(&datax, OPEN_DB_ZIPFILE);
drc = arDotCommand(&datax, 1, cmdArgsBare.azCmd, cmdArgsBare.nCmd);
}else
#endif /* ARCHIVE_ENABLE */
{
/* Run bare command arguments like separate command-line inputs. */
for(i=0; i<cmdArgsBare.nCmd && rc<2; i++){
set_invocation_cmd(cmdArgsBare.azCmd[i]);
drc = process_input(&datai);
rc = (drc>2)? 2 : drc;
if( drc > DCR_Ok ) break;
}
}
rc = (drc>2)? 2 : drc;
if( rc>0 ) goto shell_bail;
}else{
/* Run commands received from standard input (however defined.)
*/
#ifndef SQLITE_SHELL_FIDDLE
if( stdin_is_interactive ){
char *zHistory = 0;
if( argsData.bQuiet>1 ){
/* bQuiet>1 is almost like normal interactive, but quieter, without
** prompts and avoids history keeping and line editor completions.
** It exists for testing or bug repro purposes. */
mainPrompt[0] = 0;
continuePrompt[0] = 0;
}else{
char *zHome;
if( argsData.bQuiet<1 ){
fprintf(STD_OUT,
"SQLite version %s %.19s\n" /*extra-version-info*/
"Enter \".help\" for usage hints.\n",
sqlite3_libversion(), sqlite3_sourceid()
);
if( warnInmemoryDb ){
fprintf(STD_OUT, "Connected to a ");
printBold("transient in-memory database");
fprintf(STD_OUT, ".\nUse \".open FILENAME\" to reopen on a "
"persistent database.\n");
}
}
zHistory = getenv("SQLITE_HISTORY");
if( zHistory ){
zHistory = strdup(zHistory);
}else if( (zHome = find_home_dir(0))!=0 ){
static const char zBasename[] = ".sqlite_history";
int nHistory = strlen30(zHome) + 2 + sizeof(zBasename);
if( (zHistory = malloc(nHistory))!=0 ){
sqlite3_snprintf(nHistory, zHistory,"%s/%s", zHome, zBasename);
}
}
if( zHistory ){
mstr_holder(zHistory);
shell_read_history(zHistory);
}
# if HAVE_READLINE || HAVE_EDITLINE
rl_attempted_completion_function = readline_completion;
# elif HAVE_LINENOISE
linenoiseSetCompletionCallback(linenoise_completion);
# endif
}
datai.pInSource = &termInSource; /* read from stdin interactively */
drc = process_input(&datai);
if( !argsData.bQuiet ){
if( zHistory ){
shell_stifle_history(2000);
shell_write_history(zHistory);
release_holder();
}
}
}else{
datai.pInSource = &stdInSource; /* read from stdin without prompts */
drc = process_input(&datai);
}
#else /* !defined(SQLITE_SHELL_FIDDLE) */
datai.pInSource = &fiddleInSource; /* read per set_fiddle_input_text() */
drc = process_input(&datai);
#endif /* defined(SQLITE_SHELL_FIDDLE) */
rc = (drc>2)? 2 : drc;
}
shell_bail:
forget_exit_ripper(&mainRipDest);
/* Next is a no-op except for "goto shell_bail" early exits. */
RESOURCE_FREE(mainRipDest.resDest);
}else{
/* Abrupt, stack-ripping exit arrives here (with mainRipDest forgotten.) */
utf8_printf(STD_ERR,"Terminating with error (2) after orderly cleanup.\n");
datax.shellAbruptExit = 0x102;
}
/* Extract this code to preserve its value before disposal. */
iAbruptExitCode = datax.shellAbruptExit;
/**** Termination cleanup. ****/
#ifndef SQLITE_SHELL_FIDDLE
/* In WASM mode we have to leave the db state in place so that
** client code can "push" SQL into it after this call returns.
** For that build, just bypass freeing all acquired resources.
*/
execution_restore(&datai, &datax);
RESOURCE_FREE(mark);
# ifdef SQLITE_DEBUG
if( sqlite3_memory_used()>mem_main_enter ){
utf8_printf(stderr, "Memory leaked: %u bytes\n",
(unsigned int)(sqlite3_memory_used()-mem_main_enter));
}
# endif
#endif /* !defined(SQLITE_SHELL_FIDDLE) */
/* Process exit codes to yield single shell exit code.
* rc == 2 is a quit signal, resulting in no error by itself.
* datax.shellAbruptExit conveyed either a normal (success or error)
* exit code or an abnormal exit code. Its abnormal values take priority.
*/
/* Check for an abnormal exit, and issue error if so. */
if( iAbruptExitCode!=0 ){
rc = iAbruptExitCode & 0xff;
if( iAbruptExitCode>0x1ff ) raw_printf(STD_ERR,"Abnormal exit (%d)\n",rc);
}else{
/* rc is one of 0,1,2, mapping to 0,1,0 shell exit codes. */
rc &= ~2;
}
terminate_actions();
return rc;
}
#ifdef SQLITE_SHELL_FIDDLE
/* Only for emcc experimentation purposes. */
int fiddle_experiment(int a,int b){
return a + b;
}
/*
** Returns a pointer to the current DB handle.
*/
sqlite3 * fiddle_db_handle(){
return GLOBAL_DB;
}
/*
** Returns a pointer to the given DB name's VFS. If zDbName is 0 then
** "main" is assumed. Returns 0 if no db with the given name is
** open.
*/
sqlite3_vfs * fiddle_db_vfs(const char *zDbName){
sqlite3_vfs * pVfs = 0;
if(globalDb){
sqlite3_file_control(GLOBAL_DB, zDbName ? zDbName : "main",
SQLITE_FCNTL_VFS_POINTER, &pVfs);
}
return pVfs;
}
/* Only for emcc experimentation purposes. */
sqlite3 * fiddle_db_arg(sqlite3 *arg){
printf("fiddle_db_arg(%p)\n", (const void*)arg);
return arg;
}
/*
** Intended to be called via a SharedWorker() while a separate
** SharedWorker() (which manages the wasm module) is performing work
** which should be interrupted. Unfortunately, SharedWorker is not
** portable enough to make real use of.
*/
void fiddle_interrupt(void){
active_db_interrupt();
}
/*
** Returns the filename of the given db name, assuming "main" if
** zDbName is NULL. Returns NULL if globalDb is not opened.
*/
const char * fiddle_db_filename(const char * zDbName){
return globalDb
? sqlite3_db_filename(GLOBAL_DB, zDbName ? zDbName : "main")
: NULL;
}
/*
** Completely wipes out the contents of the currently-opened database
** but leaves its storage intact for reuse.
*/
void fiddle_reset_db(void){
if( globalDb ){
int rc = sqlite3_db_config(GLOBAL_DB, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0);
if( 0==rc ) rc = sqlite3_exec(GLOBAL_DB, "VACUUM", 0, 0, 0);
sqlite3_db_config(GLOBAL_DB, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0);
}
}
/*
** Uses the current database's VFS xRead to stream the db file's
** contents out to the given callback. The callback gets a single
** chunk of size n (its 2nd argument) on each call and must return 0
** on success, non-0 on error. This function returns 0 on success,
** SQLITE_NOTFOUND if no db is open, or propagates any other non-0
** code from the callback. Note that this is not thread-friendly: it
** expects that it will be the only thread reading the db file and
** takes no measures to ensure that is the case.
*/
int fiddle_export_db( int (*xCallback)(unsigned const char *zOut, int n) ){
sqlite3_int64 nSize = 0;
sqlite3_int64 nPos = 0;
sqlite3_file * pFile = 0;
unsigned char buf[1024 * 8];
int nBuf = (int)sizeof(buf);
int rc = shellStateI.db
? sqlite3_file_control(shellStateI.db, "main",
SQLITE_FCNTL_FILE_POINTER, &pFile)
: SQLITE_NOTFOUND;
if( rc ) return rc;
rc = pFile->pMethods->xFileSize(pFile, &nSize);
if( rc ) return rc;
if(nSize % nBuf){
/* DB size is not an even multiple of the buffer size. Reduce
** buffer size so that we do not unduly inflate the db size when
** exporting. */
if(0 == nSize % 4096) nBuf = 4096;
else if(0 == nSize % 2048) nBuf = 2048;
else if(0 == nSize % 1024) nBuf = 1024;
else nBuf = 512;
}
for( ; 0==rc && nPos<nSize; nPos += nBuf ){
rc = pFile->pMethods->xRead(pFile, buf, nBuf, nPos);
if(SQLITE_IOERR_SHORT_READ == rc){
rc = (nPos + nBuf) < nSize ? rc : 0/*assume EOF*/;
}
if( 0==rc ) rc = xCallback(buf, nBuf);
}
return rc;
}
/*
** Trivial exportable function for emscripten. It processes zSql as if
** it were input to the sqlite3 shell and redirects all output to the
** wasm binding. fiddle_main() must have been called before this
** is called, or results are undefined.
*/
void fiddle_exec(const char * zSql){
if(zSql && *zSql){
if('.'==*zSql) puts(zSql);
set_fiddle_input_text(zSql);
process_input(&shellStateI);
set_fiddle_input_text(0);
}
}
#endif /* defined(SQLITE_SHELL_FIDDLE) */