/* ** This file is automatically generated by the script in the canonical ** SQLite source tree at tool/mkshellc.tcl. That script combines source ** code from various constituent source files of SQLite into this single ** "shell.c" file used to implement the SQLite command-line shell. ** ** Most of the code found below comes from the "src/shell.c.in" file in ** the canonical SQLite source tree. That main file contains "INCLUDE" ** lines that specify other files in the canonical source tree that are ** inserted to getnerate this complete program source file. ** ** The code from multiple files is combined into this single "shell.c" ** source file to help make the command-line program easier to compile. ** ** To modify this program, get a copy of the canonical SQLite source tree, ** edit the src/shell.c.in" and/or some of the other files that are included ** by "src/shell.c.in", then rerun the tool/mkshellc.tcl script. */ /* ** 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 "sqlite" command line ** utility 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 /* ** 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 /* ** 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 : 4702) #pragma warning(disable : 4706) #endif /* defined(_MSC_VER) */ /* ** 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 #include #include #include #include #include "duckdb_shell_wrapper.h" #include "duckdb/common/box_renderer.hpp" #include "duckdb/common/file_system.hpp" #include "duckdb/parser/qualified_name.hpp" #include "sqlite3.h" typedef sqlite3_int64 i64; typedef sqlite3_uint64 u64; typedef unsigned char u8; #include #if !defined(_WIN32) && !defined(WIN32) #include #if !defined(__RTP__) && !defined(_WRS_KERNEL) #include #endif #endif #if (!defined(_WIN32) && !defined(WIN32)) || defined(__MINGW32__) #include #include #endif #if defined(__MINGW32__) #define DIRENT dirent #ifndef S_ISLNK #define S_ISLNK(mode) (0) #endif #endif #include #include #if HAVE_READLINE #include #include #endif #if HAVE_EDITLINE #include #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 #include "duckdb.hpp" #include "shell_renderer.hpp" #include "shell_highlight.hpp" #include "shell_state.hpp" using namespace duckdb_shell; #if defined(_WIN32) || defined(WIN32) #if SQLITE_OS_WINRT #define SQLITE_OMIT_POPEN 1 #else #include #include #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) /* popen and pclose are not C89 functions and so are ** sometimes omitted from the 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 #if defined(_WIN32) || defined(WIN32) #if SQLITE_OS_WINRT #include #endif #include /* string conversion routines only needed on Win32 */ extern char *sqlite3_win32_unicode_to_utf8(LPCWSTR); extern char *sqlite3_win32_mbcs_to_utf8_v2(const char *, int); extern char *sqlite3_win32_utf8_to_mbcs_v2(const char *, int); extern LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText); #endif /* On Windows, we normally run with output mode of TEXT so that \n characters ** are automatically translated into \r\n. However, this behavior needs ** to be disabled in some cases (ex: when generating CSV output and when ** 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 /* True if the timer is enabled */ static bool enableTimer = false; /* Return the current wall-clock time */ static sqlite3_int64 timeOfDay(void) { static sqlite3_vfs *clockVfs = nullptr; sqlite3_int64 t; if (!clockVfs) { clockVfs = sqlite3_vfs_find(0); } 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 #include /* 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); printf("Run Time (s): 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); printf("Run Time (s): 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) /* ** Number of elements in an array */ #define ArraySize(X) (int)(sizeof(X) / sizeof(X[0])) /* ** If the following flag is set, then command execution stops at an error ** if we are not interactive, including any error in processed files. */ static bool bail_on_error = false; /* ** Treat stdin as an interactive input if the following variable ** is true. Otherwise, assume stdin is connected to a file or pipe. */ static bool stdin_is_interactive = true; /* ** 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 bool stdout_is_console = true; static bool stderr_is_console = true; /* ** 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 sqlite3 *globalDb = nullptr; /* ** True 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 const char *program_name; enum class OptionType { DEFAULT, ON, OFF }; /* ** Whether or not we are running in safe mode */ static bool safe_mode = false; /* ** Whether or not we are highlighting errors */ static OptionType highlight_errors = OptionType::DEFAULT; static bool HighlightErrors() { if (highlight_errors == OptionType::DEFAULT) { return stderr_is_console; } return highlight_errors == OptionType::ON; } /* ** Whether or not we are highlighting results */ static OptionType highlight_results = OptionType::DEFAULT; static bool HighlightResults() { if (highlight_results == OptionType::DEFAULT) { return stdout_is_console; } return highlight_results == OptionType::ON; } /* ** Prompt strings. Initialized in main. Settable with ** .prompt main continue */ static char mainPrompt[20]; /* First line prompt. default: "D "*/ static char continuePrompt[20]; /* Continuation prompt. default: " ...> " */ static char continuePromptSelected[20]; /* Selected continuation prompt. default: " ...> " */ /* ** Render output like fprintf(). Except, if the output is going to the ** console and if this is running on a Windows machine, translate the ** output from UTF-8 into MBCS. */ #if defined(_WIN32) || defined(WIN32) static int win_utf8_mode = 0; void utf8_printf(FILE *out, const char *zFormat, ...) { va_list ap; va_start(ap, zFormat); if (stdout_is_console && (out == stdout || out == stderr)) { char *z1 = sqlite3_vmprintf(zFormat, ap); if (win_utf8_mode && SetConsoleOutputCP(CP_UTF8)) { // we can write UTF8 directly fputs(z1, out); } else { // fallback to writing old style windows unicode char *z2 = sqlite3_win32_utf8_to_mbcs_v2(z1, 0); fputs(z2, out); sqlite3_free(z2); } sqlite3_free(z1); } else { vfprintf(out, zFormat, ap); } va_end(ap); } #elif !defined(utf8_printf) #define utf8_printf fprintf #endif /* ** Render output like fprintf(). This should not be used on anything that ** includes string formatting (e.g. "%s"). */ #if !defined(raw_printf) #define raw_printf fprintf #endif /* Indicate out-of-memory and exit. */ static void shell_out_of_memory(void) { raw_printf(stderr, "Error: out of memory\n"); exit(1); } ShellState::ShellState() { nullValue = "NULL"; } void ShellState::Print(PrintOutput output, const char *str) { utf8_printf(output == PrintOutput::STDOUT ? out : stderr, "%s", str); } void ShellState::Print(PrintOutput output, const string &str) { Print(output, str.c_str()); } void ShellState::Print(const char *str) { Print(PrintOutput::STDOUT, str); } void ShellState::Print(const string &str) { Print(PrintOutput::STDOUT, str.c_str()); } void ShellState::PrintValue(const char *str) { Print(str ? str : nullValue.c_str()); } void ShellState::PrintPadded(const char *str, idx_t len) { utf8_printf(out, "%*s", int(len), str); } /* ** 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. */ void ShellState::UTF8WidthPrint(FILE *pOut, idx_t w, const string &str, bool right_align) { auto zUtf = str.c_str(); int i; int n; int aw = w < 0 ? -w : w; #ifdef HAVE_LINENOISE i = linenoiseGetRenderPosition(zUtf, strlen(zUtf), aw, &n); if (i < 0) #endif 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 (right_align) { 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. */ bool ShellState::IsNumber(const char *z, int *realnum) { if (*z == '-' || *z == '+') z++; if (!IsDigit(*z)) { return false; } z++; if (realnum) *realnum = 0; while (IsDigit(*z)) { z++; } if (*z == '.') { z++; if (!IsDigit(*z)) return false; while (IsDigit(*z)) { z++; } if (realnum) *realnum = 1; } if (*z == 'e' || *z == 'E') { z++; if (*z == '+' || *z == '-') z++; if (!IsDigit(*z)) return false; 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. */ idx_t ShellState::StringLength(const char *z) { return strlen(z); } /* ** Return the length of a string in characters. */ idx_t ShellState::RenderLength(const char *z) { #ifdef HAVE_LINENOISE return linenoiseComputeRenderWidth(z, strlen(z)); #else int n = 0; while (*z) { if ((0xc0 & *(z++)) != 0x80) n++; } return n; #endif } idx_t ShellState::RenderLength(const string &str) { return RenderLength(str.c_str()); } int ShellState::RunInitialCommand(char *sql, bool bail) { int rc; if (sql[0] == '.') { rc = DoMetaCommand(sql); if (rc && bail) { return rc == 2 ? false : rc; } } else { char *zErrMsg = nullptr; OpenDB(0); BEGIN_TIMER; rc = ExecuteSQL(sql, &zErrMsg); END_TIMER; if (zErrMsg != 0) { PrintDatabaseError(zErrMsg); sqlite3_free(zErrMsg); if (bail) { return rc != 0 ? rc : 1; } } else if (rc != 0) { utf8_printf(stderr, "Error: unable to process SQL: \"%s\"\n", sql); if (bail) { return rc; } } } return 0; } /* ** Return true if zFile does not exist or if it is not an ordinary file. */ #ifdef _WIN32 #define notNormalFile(X) 0 #else static int notNormalFile(const char *zFile) { struct stat x; int rc; memset(&x, 0, sizeof(x)); rc = stat(zFile, &x); return rc || !S_ISREG(x.st_mode); } #endif /* ** This routine reads a line of text from FILE in, stores ** the text in memory obtained from malloc() and returns a pointer ** to the text. NULL is returned at end of file, or if malloc() ** fails. ** ** 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, FILE *in) { idx_t nLine = zLine == 0 ? 0 : 100; idx_t n = 0; #if defined(_WIN32) || defined(WIN32) int is_stdin = stdin_is_interactive && in == stdin; int is_utf8 = 0; if (is_stdin && win_utf8_mode) { if (SetConsoleCP(CP_UTF8)) { is_utf8 = 1; } } #endif while (1) { if (n + 100 > nLine) { nLine = nLine * 2 + 100; zLine = (char *)realloc(zLine, nLine); if (zLine == 0) shell_out_of_memory(); } if (fgets(&zLine[n], nLine - n, in) == 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, translate the ** multi-byte characterset characters into UTF-8. */ if (is_stdin && !is_utf8) { char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0); if (zTrans) { idx_t nTrans = ShellState::StringLength(zTrans) + 1; if (nTrans > nLine) { zLine = (char *)realloc(zLine, nTrans); if (zLine == 0) 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. ** ** If in==0 then read from standard input and prompt before each line. ** If isContinuation is true, then a continuation prompt is appropriate. ** If isContinuation is zero, then the main prompt should be used. ** ** If zPrior is not NULL then it is a buffer from a prior call to this ** routine that can be reused. ** ** The result is stored in space obtained from malloc() and must either ** be freed by the caller or else passed back into this routine via the ** zPrior argument for reuse. */ static char *one_input_line(FILE *in, char *zPrior, int isContinuation) { char *zPrompt; char *zResult; if (in != 0) { zResult = local_getline(zPrior, in); } else { zPrompt = isContinuation ? continuePrompt : mainPrompt; #if SHELL_USE_LOCAL_GETLINE printf("%s", zPrompt); fflush(stdout); zResult = local_getline(zPrior, stdin); #else free(zPrior); zResult = shell_readline(zPrompt); #endif } return zResult; } /* ** Return the value of a hexadecimal digit. Return -1 if the input ** 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 { const 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; } /* 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. */ static void appendText(string &text, char const *zAppend, char quote) { if (!quote) { text += zAppend; return; } text += quote; for (const char *c = zAppend; *c; c++) { text += *c; if (*c == quote) { text += quote; } } text += quote; } /* ** 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 (!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; } /* Allowed values for ShellState.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 */ static const char *modeDescr[] = {"line", "column", "list", "semi", "html", "insert", "quote", "tcl", "csv", "explain", "ascii", "prettyprint", "eqp", "json", "markdown", "table", "box", "latex", "trash", "jsonlines", "duckbox"}; /* ** 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) { ShellState *p = (ShellState *)pArg; if (p->pLog == 0) return; utf8_printf(p->pLog, "(%d) %s\n", iErrCode, zMsg); fflush(p->pLog); } /* ** Save or restore the current output mode */ void ShellState::PushOutputMode() { modePrior = mode; priorShFlgs = shellFlgs; colSepPrior = colSeparator; rowSepPrior = rowSeparator; } void ShellState::PopOutputMode() { mode = modePrior; shellFlgs = priorShFlgs; colSeparator = colSepPrior; rowSeparator = rowSepPrior; } /* ** Output the given string as a hex-encoded blob (eg. X'1234' ) */ void ShellState::OutputHexBlob(const void *pBlob, int nBlob) { int i; char *zBlob = (char *)pBlob; raw_printf(out, "X'"); for (i = 0; i < nBlob; i++) { raw_printf(out, "%02x", zBlob[i] & 0xff); } raw_printf(out, "'"); } /* ** 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. */ 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(20, 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() */ void ShellState::OutputQuotedString(const char *z) { int i; char c; SetBinaryMode(); 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(); } /* ** 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. */ void ShellState::OutputQuotedEscapedString(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[20], zBuf2[20]; 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. */ void ShellState::OutputCString(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. */ void ShellState::OutputJSONString(const char *z, int n) { unsigned int c; if (n < 0) n = (int)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); } /* ** 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, 0, 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, }; void ShellState::PrintOptionallyQuotedIdentifier(const char *input) { if (quoteChar(input)) { char *quoted = sqlite3_mprintf("\"%w\"", input); Print(quoted); sqlite3_free(quoted); } else { Print(input); } } /* ** 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. */ void ShellState::OutputCSV(const char *z, int bSep) { if (!z) { Print(nullValue); } else { int i; int nSep = colSeparator.size(); for (i = 0; z[i]; i++) { if (needCsvQuote[((unsigned char *)z)[i]] || (z[i] == colSeparator[0] && (nSep == 1 || memcmp(z, colSeparator.c_str(), nSep) == 0))) { i = 0; break; } } if (i == 0) { char *zQuoted = sqlite3_mprintf("\"%w\"", z); Print(zQuoted); sqlite3_free(zQuoted); } else { Print(z); } } if (bSep) { Print(colSeparator); } } /* ** This routine runs when the user presses Ctrl-C */ static void interrupt_handler(int NotUsed) { UNUSED_PARAMETER(NotUsed); seenInterrupt++; if (seenInterrupt > 2) { exit(1); } if (globalDb) { sqlite3_interrupt(globalDb); } } #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 /* ** Print a schema statement. Part of RenderMode::Semi and RenderMode::Pretty output. ** ** This routine converts some CREATE TABLE statements for shadow tables ** in FTS3/4/5 into CREATE TABLE IF NOT EXISTS statements. */ void ShellState::PrintSchemaLine(const char *z, const char *zTail) { if (!z || !zTail) { return; } 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); } } void ShellState::PrintSchemaLineN(char *z, int n, const char *zTail) { char c = z[n]; z[n] = 0; PrintSchemaLine(z, zTail); z[n] = c; } /* ** Print N dashes */ void ShellState::PrintDashes(idx_t N) { const char zDash[] = "--------------------------------------------------"; const idx_t nDash = sizeof(zDash) - 1; while (N > nDash) { fputs(zDash, out); N -= nDash; } raw_printf(out, "%.*s", static_cast(N), zDash); } /* ** Print a markdown or table-style row separator using ascii-art */ void ShellState::PrintRowSeparator(idx_t nArg, const char *zSep, const vector &actualWidth) { if (nArg > 0) { fputs(zSep, out); PrintDashes(actualWidth[0] + 2); for (idx_t i = 1; i < nArg; i++) { fputs(zSep, out); PrintDashes(actualWidth[i] + 2); } fputs(zSep, out); } fputs("\n", out); } void ShellState::PrintMarkdownSeparator(idx_t nArg, const char *zSep, const vector &colTypes, const vector &actualWidth) { if (nArg > 0) { for (idx_t i = 0; i < nArg; i++) { Print(zSep); if (colTypes[i] == SQLITE_INTEGER || colTypes[i] == SQLITE_FLOAT) { // right-align numerics in tables PrintDashes(actualWidth[i] + 1); Print(":"); } else { PrintDashes(actualWidth[i] + 2); } } Print(zSep); } Print("\n"); } void ShellState::SetBinaryMode() { setBinaryMode(out, 1); } void ShellState::SetTextMode() { setTextMode(out, 1); } /* ** This is the callback routine that the shell ** invokes for each row of a query result. */ int ShellState::RenderRow(RowRenderer &renderer, RowResult &result) { auto &data = result.data; if (data.empty()) { return 0; } renderer.Render(result); 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 */ auto renderer = (RowRenderer *)pArg; RowResult result; for (int i = 0; i < nArg; i++) { result.column_names.push_back(azCol[i]); result.data.push_back(azArg[i]); } return renderer->state.RenderRow(*renderer, result); } /* ** Set the destination table field of the ShellState structure to ** the name of the table given. Escape any quote characters in the ** table name. */ void ShellState::SetTableName(const char *zName) { if (zDestTable) { free(zDestTable); zDestTable = nullptr; } if (!zName) { return; } auto cQuote = quoteChar(zName); idx_t n = StringLength(zName); if (cQuote) { n += n + 2; } auto z = zDestTable = (char *)malloc(n + 1); if (!z) { shell_out_of_memory(); } n = 0; if (cQuote) { z[n++] = cQuote; } for (idx_t i = 0; zName[i]; i++) { z[n++] = zName[i]; if (zName[i] == cQuote) { z[n++] = cQuote; } } if (cQuote) { z[n++] = cQuote; } z[n] = 0; } /* ** 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. */ int ShellState::RunTableDumpQuery(const char *zSelect /* SELECT statement to extract content */ ) { sqlite3_stmt *pSelect; const char *z; int rc = sqlite3_prepare_v2(db, zSelect, -1, &pSelect, 0); if (rc != SQLITE_OK || !pSelect) { utf8_printf(out, "/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(db)); if ((rc & 0xff) != SQLITE_CORRUPT) { nErr++; } return rc; } rc = sqlite3_step(pSelect); idx_t nResult = sqlite3_column_count(pSelect); while (rc == SQLITE_ROW) { z = (const char *)sqlite3_column_text(pSelect, 0); Print(z); for (idx_t i = 1; i < nResult; i++) { Print((const char *)sqlite3_column_text(pSelect, static_cast(i))); } if (!z) { z = ""; } while (z[0] && (z[0] != '-' || z[1] != '-')) { z++; } if (z[0]) { raw_printf(out, "\n;\n"); } else { raw_printf(out, ";\n"); } rc = sqlite3_step(pSelect); } rc = sqlite3_finalize(pSelect); if (rc != SQLITE_OK) { utf8_printf(out, "/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(db)); if ((rc & 0xff) != SQLITE_CORRUPT) { nErr++; } } return rc; } /* ** Allocate space and save off current error string. */ static char *SaveErrorMessage(sqlite3 *db) { idx_t nErrMsg = 1 + ShellState::StringLength(sqlite3_errmsg(db)); auto zErrMsg = (char *)sqlite3_malloc64(nErrMsg); if (zErrMsg) { memcpy(zErrMsg, sqlite3_errmsg(db), nErrMsg); } return zErrMsg; } string ShellState::strdup_handle_newline(const char *z) { static constexpr idx_t MAX_SIZE = 80; if (!z) { return nullValue; } if (cMode != RenderMode::BOX) { return z; } string result; idx_t count = 0; bool interrupted = false; for (const char *s = z; *s; s++) { if (*s == '\n') { result += "\\"; result += "n"; } else { result += *s; } count++; if (count >= MAX_SIZE && ((*s & 0xc0) != 0x80)) { interrupted = true; break; } } if (interrupted) { result += "..."; } return result; } bool ShellState::ColumnTypeIsInteger(const char *type) { if (!type) { return false; } if (strcmp(type, "TINYINT") == 0) { return true; } if (strcmp(type, "SMALLINT") == 0) { return true; } if (strcmp(type, "INTEGER") == 0) { return true; } if (strcmp(type, "BIGINT") == 0) { return true; } if (strcmp(type, "FLOAT") == 0) { return true; } if (strcmp(type, "DOUBLE") == 0) { return true; } if (strcmp(type, "DECIMAL") == 0) { return true; } return false; } ColumnarResult ShellState::ExecuteColumnar(sqlite3_stmt *pStmt) { ColumnarResult result; int rc = sqlite3_step(pStmt); if (rc != SQLITE_ROW) { return result; } // fetch the column count, column names and types result.column_count = sqlite3_column_count(pStmt); result.data.reserve(result.column_count * 4); for (idx_t i = 0; i < result.column_count; i++) { result.data.push_back(strdup_handle_newline(sqlite3_column_name(pStmt, i))); result.types.push_back(sqlite3_column_type(pStmt, i)); result.type_names.push_back(sqlite3_column_decltype(pStmt, i)); } // execute the query and fetch the entire result set do { for (idx_t i = 0; i < result.column_count; i++) { auto z = (const char *)sqlite3_column_text(pStmt, i); result.data.push_back(strdup_handle_newline(z)); } } while ((rc = sqlite3_step(pStmt)) == SQLITE_ROW); // compute the column widths for (idx_t i = 0; i < result.column_count; i++) { int w = i < colWidth.size() ? colWidth[i] : 0; if (w < 0) { result.right_align.push_back(true); w = -w; } else { result.right_align.push_back(false); } result.column_width.push_back(static_cast(w)); } for (idx_t i = 0; i < result.data.size(); i++) { idx_t width = RenderLength(result.data[i]); idx_t column_idx = i % result.column_count; if (width > result.column_width[column_idx]) { result.column_width[column_idx] = width; } } return result; } /* ** Run a prepared statement and output the result in one of the ** table-oriented formats: RenderMode::Column, RenderMode::Markdown, RenderMode::Table, ** RenderMode::Box or RenderMode::DuckBox ** ** 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. */ void ShellState::ExecutePreparedStatementColumnar(sqlite3_stmt *pStmt) { auto result = ExecuteColumnar(pStmt); if (seenInterrupt) { utf8_printf(out, "Interrupt\n"); return; } if (result.data.empty()) { // nothing to render return; } auto column_renderer = GetColumnRenderer(); column_renderer->RenderHeader(result); auto colSep = column_renderer->GetColumnSeparator(); auto rowSep = column_renderer->GetRowSeparator(); auto row_start = column_renderer->GetRowStart(); for (idx_t i = result.column_count, j = 0; i < result.data.size(); i++, j++) { if (j == 0 && row_start) { Print(row_start); } idx_t w = result.column_width[j]; bool right_align = result.right_align[j]; UTF8WidthPrint(out, w, result.data[i], right_align); if (j == result.column_count - 1) { Print(rowSep); j = -1; if (seenInterrupt) goto columnar_end; } else { Print(colSep); } } column_renderer->RenderFooter(result); columnar_end: if (seenInterrupt) { utf8_printf(out, "Interrupt\n"); } } extern "C" { extern void sqlite3_print_duckbox(sqlite3_stmt *pStmt, size_t max_rows, size_t max_width, const char *null_value, int columns, char thousands, char decimal, int large_number_rendering, duckdb::BaseResultRenderer *renderer); } class DuckBoxRenderer : public duckdb::BaseResultRenderer { public: DuckBoxRenderer(ShellState &state, bool highlight) : shell_highlight(state), output(PrintOutput::STDOUT), highlight(highlight) { } void RenderLayout(const string &text) override { PrintText(text, HighlightElementType::LAYOUT); } void RenderColumnName(const string &text) override { PrintText(text, HighlightElementType::COLUMN_NAME); } void RenderType(const string &text) override { PrintText(text, HighlightElementType::COLUMN_TYPE); } void RenderValue(const string &text, const duckdb::LogicalType &type) override { if (type.IsNumeric()) { PrintText(text, HighlightElementType::NUMERIC_VALUE); } else if (type.IsTemporal()) { PrintText(text, HighlightElementType::TEMPORAL_VALUE); } else { PrintText(text, HighlightElementType::STRING_VALUE); } } void RenderNull(const string &text, const duckdb::LogicalType &type) override { PrintText(text, HighlightElementType::NULL_VALUE); } void RenderFooter(const string &text) override { PrintText(text, HighlightElementType::FOOTER); } void PrintText(const string &text, HighlightElementType element_type) { if (highlight) { shell_highlight.PrintText(text, output, element_type); } else { utf8_printf(shell_highlight.state.out, "%s", text.c_str()); } } private: ShellHighlight shell_highlight; PrintOutput output; bool highlight = true; }; class TrashRenderer : public duckdb::BaseResultRenderer { public: TrashRenderer() { } void RenderLayout(const string &) override { } void RenderColumnName(const string &) override { } void RenderType(const string &) override { } void RenderValue(const string &, const duckdb::LogicalType &) override { } void RenderNull(const string &, const duckdb::LogicalType &) override { } void RenderFooter(const string &) override { } void PrintText(const string &, HighlightElementType) { } }; /* ** Run a prepared statement */ void ShellState::ExecutePreparedStatement(sqlite3_stmt *pStmt) { if (cMode == RenderMode::DUCKBOX) { size_t max_rows = this->max_rows; size_t max_width = this->max_width; if (!outfile.empty() && outfile[0] != '|') { max_rows = (size_t)-1; max_width = (size_t)-1; } LargeNumberRendering large_rendering = large_number_rendering; if (!stdout_is_console) { max_width = (size_t)-1; } if (large_rendering == LargeNumberRendering::DEFAULT) { large_rendering = stdout_is_console ? LargeNumberRendering::FOOTER : LargeNumberRendering::NONE; } DuckBoxRenderer renderer(*this, HighlightResults()); sqlite3_print_duckbox(pStmt, max_rows, max_width, nullValue.c_str(), columns, thousand_separator, decimal_separator, int(large_rendering), &renderer); return; } if (cMode == RenderMode::TRASH) { TrashRenderer renderer; sqlite3_print_duckbox(pStmt, 1, 80, "", false, '\0', '\0', 0, &renderer); return; } if (ShellRenderer::IsColumnar(cMode)) { ExecutePreparedStatementColumnar(pStmt); return; } /* perform the first step. this will tell us if we ** have a result set or not and how wide it is. */ int rc = sqlite3_step(pStmt); /* if we have a result set... */ if (SQLITE_ROW != rc) { return; } RowResult result; // initialize the result and the column names int nCol = sqlite3_column_count(pStmt); result.column_names.reserve(nCol); result.data.resize(nCol); result.types.resize(nCol); for (int i = 0; i < nCol; i++) { result.column_names.push_back(sqlite3_column_name(pStmt, i)); } result.pStmt = pStmt; auto renderer = GetRowRenderer(); // iterate over the rows do { /* extract the data and data types */ for (int i = 0; i < nCol; i++) { result.types[i] = sqlite3_column_type(pStmt, i); result.data[i] = (const char *)sqlite3_column_text(pStmt, i); if (!result.data[i] && result.types[i] != SQLITE_NULL) { // OOM rc = SQLITE_NOMEM; break; } } /* if data and types extracted successfully... */ if (SQLITE_ROW == rc) { /* call the supplied callback with the result row data */ if (RenderRow(*renderer, result)) { rc = SQLITE_ABORT; } else { rc = sqlite3_step(pStmt); } } } while (SQLITE_ROW == rc); renderer->RenderFooter(result); } /* ** 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. */ int ShellState::ExecuteSQL(const char *zSql, /* SQL to be evaluated */ char **pzErrMsg /* Error msg written here */ ) { sqlite3_stmt *pStmt = NULL; /* Statement to execute. */ int rc = SQLITE_OK; /* Return Code */ int rc2; const char *zLeftover; /* Tail of unprocessed SQL */ if (pzErrMsg) { *pzErrMsg = NULL; } while (zSql[0] && (SQLITE_OK == rc)) { static const char *zStmtSql; rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover); if (SQLITE_OK != rc) { if (pzErrMsg) { *pzErrMsg = SaveErrorMessage(db); } } else { if (!pStmt) { /* this happens for a comment or white-space */ zSql = zLeftover; while (IsSpace(zSql[0])) zSql++; continue; } if (sqlite3_bind_parameter_count(pStmt) != 0) { zSql = zLeftover; while (IsSpace(zSql[0])) zSql++; if (pzErrMsg) { *pzErrMsg = strdup("Prepared statement parameters cannot be used directly\nTo use prepared " "statement parameters, use PREPARE to prepare a statement, followed by EXECUTE"); } sqlite3_finalize(pStmt); continue; } zStmtSql = sqlite3_sql(pStmt); if (zStmtSql == 0) zStmtSql = ""; while (IsSpace(zStmtSql[0])) zStmtSql++; /* save off the prepared statment handle and reset row count */ this->pStmt = pStmt; /* echo the sql statement if echo on */ if (ShellHasFlag(SHFLG_Echo)) { utf8_printf(out, "%s\n", zStmtSql ? zStmtSql : zSql); } cMode = mode; if (sqlite3_stmt_isexplain(pStmt) == 1) { cMode = RenderMode::EXPLAIN; } ExecutePreparedStatement(pStmt); /* 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); if (rc != SQLITE_NOMEM) rc = rc2; if (rc == SQLITE_OK) { zSql = zLeftover; while (IsSpace(zSql[0])) zSql++; } else if (pzErrMsg) { *pzErrMsg = SaveErrorMessage(db); } /* clear saved stmt handle */ pStmt = NULL; } } /* end while */ return rc; } /* ** Release memory previously allocated by tableColumnList(). */ static void freeColumnList(char **azCol) { for (idx_t i = 1; azCol[i]; i++) { sqlite3_free(azCol[i]); } /* azCol[0] is a static string */ 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 freeColumnList(). ** ** 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. */ char **ShellState::TableColumnList(const char *zTab) { 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 */ int preserveRowid = ShellHasFlag(SHFLG_PreserveRowid); int rc; zSql = sqlite3_mprintf("PRAGMA table_info=%Q", zTab); rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); if (rc) return 0; while (sqlite3_step(pStmt) == SQLITE_ROW) { if (nCol >= nAlloc - 2) { nAlloc = nAlloc * 2 + nCol + 10; azCol = (char **)sqlite3_realloc(azCol, nAlloc * sizeof(azCol[0])); if (azCol == 0) shell_out_of_memory(); } azCol[++nCol] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 1)); 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; } } } sqlite3_finalize(pStmt); if (azCol == 0) return 0; azCol[0] = 0; azCol[nCol + 1] = 0; /* 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 alise 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 = sqlite3_mprintf("SELECT 1 FROM pragma_index_list(%Q)" " WHERE origin='pk'", zTab); rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); if (rc) { freeColumnList(azCol); return 0; } rc = sqlite3_step(pStmt); sqlite3_finalize(pStmt); preserveRowid = rc == SQLITE_ROW; } if (preserveRowid) { /* Only preserve the rowid if we can find a name to use for the ** rowid */ static const 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] = (char *)azRowid[j]; break; } } } return azCol; } /* ** Toggle the reverse_unordered_selects setting. */ static void toggleSelectOrder(sqlite3 *db) { sqlite3_stmt *pStmt = 0; int iSetting = 0; char zStmt[100]; sqlite3_prepare_v2(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); sqlite3_exec(db, zStmt, 0, 0, 0); } /* ** Lookup the schema for a table using the information schema. */ static char *getTableSchema(sqlite3 *db, const char *zTable) { char *zSchema = NULL; sqlite3_stmt *pStmt = NULL; char *zSql = sqlite3_mprintf("SELECT table_schema FROM information_schema.tables " "WHERE table_name = %Q AND table_type='BASE TABLE' " "ORDER BY (table_schema='main') DESC LIMIT 1", zTable); if (!zSql) return NULL; int rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); if (rc == SQLITE_OK && pStmt) { if (sqlite3_step(pStmt) == SQLITE_ROW) { const char *schema = (const char *)sqlite3_column_text(pStmt, 0); if (schema) zSchema = sqlite3_mprintf("%s", schema); } sqlite3_finalize(pStmt); } return zSchema; } /* ** Quote an identifier if needed. */ static char *quoteIdentifier(const char *zIdent) { if (!zIdent) return NULL; bool needsQuote = !isalpha((unsigned char)zIdent[0]) && zIdent[0] != '_'; if (!needsQuote) { for (const char *p = zIdent; *p; p++) { if (!isalnum((unsigned char)*p) && *p != '_') { needsQuote = true; break; } } } return needsQuote ? sqlite3_mprintf("\"%w\"", zIdent) : sqlite3_mprintf("%s", zIdent); } /* ** Build a qualified name: schema.table */ static char *buildQualifiedName(const char *zSchema, const char *zTable) { char *quotedSchema = quoteIdentifier(zSchema); char *quotedTable = quoteIdentifier(zTable); char *result = NULL; if (quotedSchema && quotedTable) { result = sqlite3_mprintf("%s.%s", quotedSchema, quotedTable); } sqlite3_free(quotedSchema); sqlite3_free(quotedTable); return result; } /* ** 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; ShellState *p = (ShellState *)pArg; UNUSED_PARAMETER(azNotUsed); if (nArg != 3 || azArg == 0) return 0; zTable = azArg[0]; zType = azArg[1]; zSql = azArg[2]; if (strcmp(zTable, "sqlite_sequence") == 0) { raw_printf(p->out, "DELETE FROM sqlite_sequence;\n"); } else if (sqlite3_strglob("sqlite_stat?", zTable) == 0) { raw_printf(p->out, "ANALYZE sqlite_schema;\n"); } else if (strncmp(zTable, "sqlite_", 7) == 0) { return 0; } else if (strncmp(zSql, "CREATE VIRTUAL TABLE", 20) == 0) { char *zIns; zIns = sqlite3_mprintf("INSERT INTO sqlite_schema(type,name,tbl_name,rootpage,sql)" "VALUES('table','%q','%q',0,'%q');", zTable, zTable, zSql); utf8_printf(p->out, "%s\n", zIns); sqlite3_free(zIns); return 0; } else { p->PrintSchemaLine(zSql, ";\n"); } if (strcmp(zType, "table") == 0) { string sSelect; string sTable; char **azCol; int i; char *savedDestTable; RenderMode savedMode; char *zSchema = NULL; char *zQualifiedName = NULL; zSchema = getTableSchema(p->db, zTable); if (!zSchema) zSchema = sqlite3_mprintf("main"); zQualifiedName = buildQualifiedName(zSchema, zTable); if (!zQualifiedName) { sqlite3_free(zSchema); p->nErr++; return 0; } azCol = p->TableColumnList(zQualifiedName); if (azCol == 0) { sqlite3_free(zQualifiedName); sqlite3_free(zSchema); p->nErr++; return 0; } if (strcmp(zSchema, "main") != 0) { appendText(sTable, zQualifiedName, 0); } else { 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 */ 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); } } freeColumnList(azCol); appendText(sSelect, " FROM ", 0); appendText(sSelect, zQualifiedName, 0); savedDestTable = p->zDestTable; savedMode = p->mode; p->zDestTable = (char *)sTable.c_str(); p->mode = p->cMode = RenderMode::INSERT; rc = p->ExecuteSQL(sSelect.c_str(), 0); if ((rc & 0xff) == SQLITE_CORRUPT) { raw_printf(p->out, "/****** CORRUPTION ERROR *******/\n"); toggleSelectOrder(p->db); p->ExecuteSQL(sSelect.c_str(), 0); toggleSelectOrder(p->db); } p->zDestTable = savedDestTable; p->mode = savedMode; sqlite3_free(zQualifiedName); sqlite3_free(zSchema); if (rc != SQLITE_OK && rc != SQLITE_DONE) p->nErr++; } 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. */ int ShellState::RunSchemaDumpQuery(const char *zQuery) { int rc; char *zErr = 0; rc = sqlite3_exec(db, zQuery, dump_callback, this, &zErr); if (rc == SQLITE_CORRUPT) { char *zQ2; int len = StringLength(zQuery); raw_printf(out, "/****** CORRUPTION ERROR *******/\n"); if (zErr) { utf8_printf(out, "/****** %s ******/\n", zErr); sqlite3_free(zErr); zErr = 0; } zQ2 = (char *)malloc(len + 100); if (zQ2 == 0) return rc; sqlite3_snprintf(len + 100, zQ2, "%s ORDER BY rowid DESC", zQuery); rc = sqlite3_exec(db, zQ2, dump_callback, this, &zErr); if (rc) { utf8_printf(out, "/****** ERROR: %s ******/\n", zErr); } else { rc = SQLITE_CORRUPT; } sqlite3_free(zErr); free(zQ2); } else if (zErr) { sqlite3_free(zErr); zErr = 0; } return rc; } /* ** Text of help messages. ** ** The help text for each individual command begins with a line that starts ** with ".". Subsequent lines are supplimental information. ** ** There must be two or more spaces between the end of the command and the ** start of the description of what that command does. */ static const char *azHelp[] = { ".bail on|off Stop after hitting an error. Default OFF", ".binary on|off Turn binary output on or off. Default OFF", ".cd DIRECTORY Change the working directory to DIRECTORY", ".changes on|off Show number of rows changed by SQL", ".check GLOB Fail if output since .testcase does not match", ".columns Column-wise rendering of query results", #ifdef HAVE_LINENOISE ".constant ?COLOR? Sets the syntax highlighting color used for constant values", " COLOR is one of:", " red|green|yellow|blue|magenta|cyan|white|brightblack|brightred|brightgreen", " brightyellow|brightblue|brightmagenta|brightcyan|brightwhite", ".constantcode ?CODE? Sets the syntax highlighting terminal code used for constant values", #endif ".databases List names and files of attached databases", ".decimal_sep SEP Sets the decimal separator used when rendering numbers. Only for duckbox mode.", ".dump ?TABLE? Render database content as SQL", " Options:", " --preserve-rowids Include ROWID values in the output", " --newlines Allow unescaped newline characters in output", " TABLE is a LIKE pattern for the tables to dump", " Additional LIKE patterns can be given in subsequent arguments", ".echo on|off Turn command echo on or off", ".excel Display the output of next command in spreadsheet", " --bom Put a UTF8 byte-order mark on intermediate file", #ifdef HAVE_LINENOISE ".edit Opens an external text editor to edit a query.", " Notes:", " * The editor is read from the environment variables", " DUCKDB_EDITOR, EDITOR, VISUAL in-order", " * If none of these are set, the default editor is vi", " * \\e can be used as an alias for .edit", #endif ".exit ?CODE? Exit this program with return-code CODE", ".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto", ".fullschema ?--indent? Show schema and the content of sqlite_stat tables", ".headers on|off Turn display of headers on or off", ".help ?-all? ?PATTERN? Show help text for PATTERN", #ifdef HAVE_LINENOISE ".highlight [on|off] Toggle syntax highlighting in the shell on/off", #endif ".highlight_colors [element] [color] ([bold])? Configure highlighting colors", ".highlight_errors [on|off] Toggle highlighting of errors in the shell on/off", ".highlight_results [on|off] Toggle highlighting of results in the shell on/off", ".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", " -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.", ".indexes ?TABLE? Show names of indexes", " If TABLE is specified, only show indexes for", " tables matching TABLE using the LIKE operator.", #ifdef HAVE_LINENOISE ".keyword ?COLOR? Sets the syntax highlighting color used for keywords", " COLOR is one of:", " red|green|yellow|blue|magenta|cyan|white|brightblack|brightred|brightgreen", " brightyellow|brightblue|brightmagenta|brightcyan|brightwhite", ".keywordcode ?CODE? Sets the syntax highlighting terminal code used for keywords", #endif ".large_number_rendering all|footer|off Toggle readable rendering of large numbers (duckbox only)", ".log FILE|off Turn logging on or off. FILE can be stderr/stdout", ".maxrows COUNT Sets the maximum number of rows for display (default: 40). Only for duckbox mode.", ".maxwidth COUNT Sets the maximum width in characters. 0 defaults to terminal width. Only for duckbox " "mode.", ".mode MODE ?TABLE? 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)", " duckbox Tables with extensive features", " html HTML code", " insert SQL insert statements for TABLE", " json Results in a JSON array", " jsonlines Results in a NDJSON", " latex LaTeX tabular environment code", " line One value per line", " list Values delimited by \"|\"", " markdown Markdown table format", " quote Escape answers as for SQL", " table ASCII-art table", " tabs Tab-separated values", " tcl TCL list elements", " trash No output", ".nullvalue STRING Use STRING in place of NULL values", ".once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE", " If FILE begins with '|' then open as a pipe", " --bom Put a UTF8 byte-order mark at the beginning", " -e Send output to the system text editor", " -x Send output as CSV to a spreadsheet (same as \".excel\")", ".open ?OPTIONS? ?FILE? Close existing database and reopen FILE", " Options:", " --new Initialize FILE to an empty database", " --nofollow Do not follow symbolic links", " --readonly Open FILE readonly", ".output ?FILE? Send output to FILE or stdout if FILE is omitted", " If FILE begins with '|' then open it as a pipe.", " Options:", " --bom Prefix output with a UTF8 byte-order mark", " -e Send output to the system text editor", " -x Send output as CSV to a spreadsheet", ".print STRING... Print literal STRING", ".prompt MAIN CONTINUE Replace the standard prompts", ".quit Exit this program", ".read FILE Read input from FILE", ".rows Row-wise rendering of query results (default)", ".safe_mode Enable safe-mode", ".schema ?PATTERN? Show the CREATE statements matching PATTERN", " Options:", " --indent Try to pretty-print the schema", ".separator COL ?ROW? Change the column and row separators", #ifndef SQLITE_NOHAVE_SYSTEM ".shell CMD ARGS... Run CMD ARGS... in a system shell", #endif ".show Show the current values for various settings", #ifndef SQLITE_NOHAVE_SYSTEM ".system CMD ARGS... Run CMD ARGS... in a system shell", #endif ".tables ?TABLE? List names of tables matching LIKE pattern TABLE", ".testcase NAME Begin redirecting output to 'testcase-out.txt'", ".thousand_sep SEP Sets the thousand separator used when rendering numbers. Only for duckbox mode.", ".timer on|off Turn SQL timer on or off", ".width NUM1 NUM2 ... Set minimum column widths for columnar output", " Negative values right-justify", #if defined(_WIN32) || defined(WIN32) ".utf8 Enable experimental UTF-8 console output mode" #endif }; /* ** Output help text. ** ** zPattern describes the set of commands for which help text is provided. ** If zPattern is NULL, then show all commands, but only give a one-line ** description of each. ** ** Return the number of matches. */ static int showHelp(FILE *out, const char *zPattern) { int i = 0; int j = 0; int n = 0; char *zPat; if (zPattern == 0 || zPattern[0] == '0' || strcmp(zPattern, "-a") == 0 || strcmp(zPattern, "-all") == 0 || strcmp(zPattern, "--all") == 0) { /* Show all commands, but only one line per command */ if (zPattern == 0) zPattern = ""; for (i = 0; i < ArraySize(azHelp); i++) { if (azHelp[i][0] == '.' || zPattern[0]) { utf8_printf(out, "%s\n", azHelp[i]); n++; } } } else { /* Look for commands that for which zPattern is an exact prefix */ zPat = sqlite3_mprintf(".%s*", zPattern); for (i = 0; i < ArraySize(azHelp); i++) { if (sqlite3_strglob(zPat, azHelp[i]) == 0) { utf8_printf(out, "%s\n", azHelp[i]); j = i + 1; n++; } } sqlite3_free(zPat); if (n) { if (n == 1) { /* when zPattern is a prefix of exactly one command, then include the ** details of that command, which should begin at offset j */ while (j < ArraySize(azHelp) - 1 && azHelp[j][0] != '.') { utf8_printf(out, "%s\n", azHelp[j]); j++; } } return n; } /* Look for commands that contain zPattern anywhere. Show the complete ** text of all commands that match. */ zPat = sqlite3_mprintf("%%%s%%", zPattern); for (i = 0; i < ArraySize(azHelp); i++) { if (azHelp[i][0] == '.') j = i; if (sqlite3_strlike(zPat, azHelp[i], 0) == 0) { utf8_printf(out, "%s\n", azHelp[j]); while (j < ArraySize(azHelp) - 1 && azHelp[j + 1][0] != '.') { j++; utf8_printf(out, "%s\n", azHelp[j]); } i = j; n++; } } sqlite3_free(zPat); } return n; } /* ** 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. */ int deduceDatabaseType(const char *zName, int dfltZip) { return SHELL_OPEN_NORMAL; } /* Flags for open_db(). ** ** The default behavior of open_db() is to exit(1) if the database fails to ** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error ** but still returns without calling exit. ** ** The OPEN_DB_ZIPFILE flag causes open_db() to prefer to open files as a ** ZIP archive if the file does not exist or is empty and its name matches ** the *.zip pattern. */ #define OPEN_DB_KEEPALIVE 0x001 /* Return after error if true */ #define OPEN_DB_ZIPFILE 0x002 /* Open as ZIP if name matches *.zip */ /* ** Make sure the database is open. If it is not, then open it. If ** the database fails to open, print an error message and exit. */ void ShellState::OpenDB(int flags) { if (db == 0) { if (openMode == SHELL_OPEN_UNSPEC) { if (zDbFilename.empty()) { openMode = SHELL_OPEN_NORMAL; } else { openMode = (u8)deduceDatabaseType(zDbFilename.c_str(), (flags & OPEN_DB_ZIPFILE) != 0); } } switch (openMode) { case SHELL_OPEN_APPENDVFS: { sqlite3_open_v2(zDbFilename.c_str(), &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | openFlags, "apndvfs"); break; } case SHELL_OPEN_HEXDB: case SHELL_OPEN_DESERIALIZE: { sqlite3_open(0, &db); break; } case SHELL_OPEN_ZIPFILE: { sqlite3_open(":memory:", &db); break; } case SHELL_OPEN_READONLY: { sqlite3_open_v2(zDbFilename.c_str(), &db, SQLITE_OPEN_READONLY | openFlags, 0); break; } case SHELL_OPEN_UNSPEC: case SHELL_OPEN_NORMAL: { sqlite3_open_v2(zDbFilename.c_str(), &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | openFlags, 0); break; } } globalDb = db; if (db == 0 || SQLITE_OK != sqlite3_errcode(db)) { utf8_printf(stderr, "Error: unable to open database \"%s\": %s\n", zDbFilename.c_str(), sqlite3_errmsg(db)); if (flags & OPEN_DB_KEEPALIVE) { sqlite3_open(":memory:", &db); return; } exit(1); } if (safe_mode) { sqlite3_exec(db, "SET enable_external_access=false", NULL, NULL, NULL); } if (stdout_is_console) { sqlite3_exec(db, "PRAGMA enable_progress_bar", NULL, NULL, NULL); sqlite3_exec(db, "PRAGMA enable_print_progress_bar", NULL, NULL, NULL); } } } /* ** Attempt to close the databaes connection. Report errors. */ void close_db(sqlite3 *db) { int rc = sqlite3_close(db); if (rc) { utf8_printf(stderr, "Error: sqlite3_close() returns %d: %s\n", rc, sqlite3_errmsg(db)); } } #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); zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase" " FROM completion(%Q) ORDER BY 1", text); sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0); sqlite3_free(zSql); } if (sqlite3_step(pStmt) == SQLITE_ROW) { zRet = strdup((const char *)sqlite3_column_text(pStmt, 0)); } else { sqlite3_finalize(pStmt); pStmt = 0; zRet = 0; } return zRet; } static char **readline_completion(const char *zText, int iStart, int iEnd) { rl_attempted_completion_over = 1; return rl_completion_matches(zText, readline_completion_generator); } #elif HAVE_LINENOISE /* ** Linenoise completion callback */ static int linenoise_open_flags = 0; static void linenoise_completion(const char *zLine, linenoiseCompletions *lc) { idx_t nLine = ShellState::StringLength(zLine); int copiedSuggestion = 0; sqlite3_stmt *pStmt = 0; char *zSql; char zBuf[1000]; if (nLine > sizeof(zBuf) - 30) { return; } if (zLine[0] == '.') { // auto-complete dot command // look for all completions in the help file size_t line_idx; for (line_idx = 0; line_idx < ArraySize(azHelp); line_idx++) { const char *line = azHelp[line_idx]; if (line[0] != '.') { continue; } int found_match = 1; size_t line_pos; for (line_pos = 0; !IsSpace(line[line_pos]) && line[line_pos] && line_pos + 1 < sizeof(zBuf); line_pos++) { zBuf[line_pos] = line[line_pos]; if (line_pos < nLine && line[line_pos] != zLine[line_pos]) { // only match prefixes for auto-completion, i.e. ".sh" matches ".shell" found_match = 0; break; } } zBuf[line_pos] = '\0'; if (found_match && line_pos >= nLine) { linenoiseAddCompletion(lc, zBuf); } } return; } if (zLine[0] == '#') { return; } // if( i==nLine-1 ) return; zSql = sqlite3_mprintf("CALL sql_auto_complete(%Q)", zLine); sqlite3 *localDb = NULL; if (!globalDb) { sqlite3_open_v2(":memory:", &localDb, linenoise_open_flags, 0); sqlite3_prepare_v2(localDb, zSql, -1, &pStmt, 0); } else { sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0); } sqlite3_free(zSql); while (sqlite3_step(pStmt) == SQLITE_ROW) { const char *zCompletion = (const char *)sqlite3_column_text(pStmt, 0); int nCompletion = sqlite3_column_bytes(pStmt, 0); int iStart = sqlite3_column_int(pStmt, 1); if (iStart + nCompletion < int(sizeof(zBuf) - 1)) { if (!copiedSuggestion) { memcpy(zBuf, zLine, iStart); copiedSuggestion = 1; } memcpy(zBuf + iStart, zCompletion, nCompletion + 1); linenoiseAddCompletion(lc, zBuf); } } sqlite3_finalize(pStmt); if (localDb) { sqlite3_close(localDb); } } #endif /* ** Do C-language style dequoting. ** ** \a -> alarm ** \b -> backspace ** \t -> tab ** \n -> newline ** \v -> vertical tab ** \f -> form feed ** \r -> carriage return ** \s -> space ** \" -> " ** \' -> ' ** \\ -> backslash ** \NNN -> ascii character NNN in octal */ 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 >= '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 bool booleanValue(const char *zArg) { idx_t 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 bool(integerValue(zArg) & 0xffffffff); if (sqlite3_stricmp(zArg, "on") == 0 || sqlite3_stricmp(zArg, "yes") == 0) { return true; } if (sqlite3_stricmp(zArg, "off") == 0 || sqlite3_stricmp(zArg, "no") == 0) { return false; } utf8_printf(stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n", zArg); return false; } /* ** Set or clear a shell flag according to a boolean value. */ void ShellState::SetOrClearFlag(unsigned mFlag, const char *zArg) { if (booleanValue(zArg)) { ShellSetFlag(mFlag); } else { ShellClearFlag(mFlag); } } /* ** Close an output file, assuming it is not stderr or stdout */ static void output_file_close(FILE *f) { if (f && f != stdout && f != stderr) fclose(f); } /* ** Try to open an output file. The names "stdout" and "stderr" are ** recognized and do the right thing. NULL is returned if the output ** filename is "off". */ static FILE *output_file_open(const char *zFile, int bTextMode) { FILE *f = nullptr; if (strcmp(zFile, "stdout") == 0) { f = stdout; } else if (strcmp(zFile, "stderr") == 0) { f = stderr; } else if (strcmp(zFile, "off") == 0) { f = 0; } else { const string expanded_path = duckdb::FileSystem::ExpandPath(zFile, /*opener=*/nullptr); f = fopen(expanded_path.c_str(), bTextMode ? "w" : "wb"); if (f == 0) { utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile); } } return f; } /* ** 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 = (char *)sqlite3_realloc64(p->z, p->nAlloc); if (p->z == 0) shell_out_of_memory(); } 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 = p->cColSep; int rSep = p->cRowSep; p->n = 0; c = fgetc(p->in); if (c == EOF || seenInterrupt) { p->cTerm = EOF; return 0; } if (c == '"') { int pc, ppc; int startLine = p->nLine; int cQuote = c; pc = ppc = 0; while (1) { c = fgetc(p->in); if (c == rSep) p->nLine++; if (c == cQuote) { if (pc == cQuote) { pc = 0; continue; } } if ((c == cSep && pc == cQuote) || (c == rSep && pc == cQuote) || (c == rSep && pc == '\r' && ppc == cQuote) || (c == EOF && pc == cQuote)) { do { p->n--; } while (p->z[p->n] != cQuote); p->cTerm = c; break; } if (pc == cQuote && c != '\r') { utf8_printf(stderr, "%s:%d: unescaped %c character\n", p->zFile, p->nLine, cQuote); } if (c == EOF) { utf8_printf(stderr, "%s:%d: unterminated %c-quoted field\n", p->zFile, startLine, cQuote); p->cTerm = c; break; } import_append_char(p, c); ppc = pc; pc = c; } } else { /* If this is the first field being parsed and it begins with the ** UTF-8 BOM (0xEF BB BF) then skip the BOM */ if ((c & 0xff) == 0xef && p->bNotFirst == 0) { import_append_char(p, c); c = fgetc(p->in); if ((c & 0xff) == 0xbb) { import_append_char(p, c); c = fgetc(p->in); if ((c & 0xff) == 0xbf) { p->bNotFirst = 1; p->n = 0; return csv_read_one_field(p); } } } while (c != EOF && c != cSep && c != rSep) { import_append_char(p, c); c = fgetc(p->in); } if (c == rSep) { p->nLine++; if (p->n > 0 && p->z[p->n - 1] == '\r') p->n--; } p->cTerm = c; } if (p->z) p->z[p->n] = 0; p->bNotFirst = 1; return p->z; } /* Read a single field of ASCII delimited text. ** ** + Input comes from p->in. ** + Store results in p->z of length p->n. Space to hold p->z comes ** from sqlite3_malloc64(). ** + Use p->cSep as the column separator. The default is "\x1F". ** + Use p->rSep as the row separator. The default is "\x1E". ** + Keep track of the row number in p->nLine. ** + Store the character that terminates the field in p->cTerm. Store ** EOF on end-of-file. ** + Report syntax errors on stderr */ static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p) { int c; int cSep = p->cColSep; int rSep = 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; } /* ** Change the output file back to stdout. ** ** If the p->doXdgOpen flag is set, that means the output was being ** redirected to a temporary file named by p->zTempFile. In that case, ** launch start/open/xdg-open on that temporary file. */ void ShellState::ResetOutput() { if (outfile.size() > 1 && outfile[0] == '|') { #ifndef SQLITE_OMIT_POPEN pclose(out); #endif } else { output_file_close(out); #ifndef SQLITE_NOHAVE_SYSTEM if (doXdgOpen) { const char *zXdgOpenCmd = #if defined(_WIN32) "start"; #elif defined(__APPLE__) "open"; #else "xdg-open"; #endif char *zCmd; zCmd = sqlite3_mprintf("%s %s", zXdgOpenCmd, zTempFile); if (system(zCmd)) { utf8_printf(stderr, "Failed: [%s]\n", zCmd); } else { /* Give the start/open/xdg-open command some time to get ** going before we continue, and potential delete the ** zTempFile data file out from under it */ sqlite3_sleep(2000); } sqlite3_free(zCmd); PopOutputMode(); doXdgOpen = 0; } #endif /* !defined(SQLITE_NOHAVE_SYSTEM) */ } outfile = string(); out = stdout; stdout_is_console = true; } void ShellState::PrintDatabaseError(const char *zErr) { if (!HighlightErrors()) { utf8_printf(stderr, "%s\n", zErr); return; } ShellHighlight shell_highlight(*this); shell_highlight.PrintError(zErr); } /* ** Print the current sqlite3_errmsg() value to stderr and return 1. */ int ShellState::ShellDatabaseError(sqlite3 *db) { const char *zErr = sqlite3_errmsg(db); PrintDatabaseError(zErr); return 1; } /* ** Compare the string as a command-line option with either one or two ** initial "-" characters. */ static int optionMatch(const char *zStr, const char *zOpt) { if (zStr[0] != '-') return 0; zStr++; if (zStr[0] == '-') zStr++; return strcmp(zStr, zOpt) == 0; } /* ** Delete a file. */ int shellDeleteFile(const char *zFilename) { int rc; #ifdef _WIN32 wchar_t *z = sqlite3_win32_utf8_to_unicode(zFilename); rc = _wunlink(z); sqlite3_free(z); #else rc = unlink(zFilename); #endif return rc; } /* ** Try to delete the temporary file (if there is one) and free the ** memory used to hold the name of the temp file. */ void ShellState::ClearTempFile() { if (zTempFile == 0) return; if (doXdgOpen) return; if (shellDeleteFile(zTempFile)) return; sqlite3_free(zTempFile); zTempFile = 0; } /* ** Create a new temp file name with the given suffix. */ void ShellState::NewTempFile(const char *zSuffix) { ClearTempFile(); sqlite3_free(zTempFile); zTempFile = 0; if (db) { sqlite3_file_control(db, 0, SQLITE_FCNTL_TEMPFILENAME, &zTempFile); } if (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 */ const 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 } zTempFile = sqlite3_mprintf("%s/temp%llx.%s", zTemp, r, zSuffix); } else { zTempFile = sqlite3_mprintf("%z.%s", zTempFile, zSuffix); } if (zTempFile == 0) { raw_printf(stderr, "out of memory\n"); exit(1); } } enum class MetadataResult : uint8_t { SUCCESS = 0, FAIL = 1, EXIT = 2, PRINT_USAGE = 3 }; typedef MetadataResult (*metadata_command_t)(ShellState &state, const char **azArg, idx_t nArg); struct MetadataCommand { const char *command; idx_t argument_count; metadata_command_t callback; const char *usage; const char *description; idx_t match_size; }; MetadataResult ToggleBail(ShellState &state, const char **azArg, idx_t nArg) { bail_on_error = booleanValue(azArg[1]); return MetadataResult::SUCCESS; } MetadataResult ToggleBinary(ShellState &state, const char **azArg, idx_t nArg) { if (booleanValue(azArg[1])) { state.SetBinaryMode(); } else { state.SetTextMode(); } return MetadataResult::SUCCESS; } MetadataResult ChangeDirectory(ShellState &state, const char **azArg, idx_t nArg) { if (safe_mode) { utf8_printf(stderr, ".cd cannot be used in -safe mode\n"); return MetadataResult::FAIL; } int rc; #if defined(_WIN32) || defined(WIN32) wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]); rc = !SetCurrentDirectoryW(z); sqlite3_free(z); #else rc = chdir(azArg[1]); #endif if (rc) { utf8_printf(stderr, "Cannot change to directory \"%s\"\n", azArg[1]); return MetadataResult::FAIL; } return MetadataResult::SUCCESS; } MetadataResult ToggleChanges(ShellState &state, const char **azArg, idx_t nArg) { state.SetOrClearFlag(SHFLG_CountChanges, azArg[1]); return MetadataResult::SUCCESS; } MetadataResult ShowDatabases(ShellState &state, const char **azArg, idx_t nArg) { char *zErrMsg = 0; state.OpenDB(0); auto renderer = state.GetRowRenderer(RenderMode::LIST); renderer->show_header = false; renderer->col_sep = ": "; sqlite3_exec(state.db, "SELECT name, file FROM pragma_database_list", callback, renderer.get(), &zErrMsg); if (zErrMsg) { state.PrintDatabaseError(zErrMsg); sqlite3_free(zErrMsg); return MetadataResult::FAIL; } return MetadataResult::SUCCESS; } MetadataResult SetSeparator(ShellState &state, const char **azArg, idx_t nArg, const char *separator_name, char &separator) { if (nArg == 1) { raw_printf(state.out, "current %s separator: %c\n", separator_name, separator); } else if (nArg != 2) { return MetadataResult::PRINT_USAGE; } else if (strcmp(azArg[1], "space") == 0) { separator = ' '; } else if (strcmp(azArg[1], "none") == 0) { separator = '\0'; } else if (strlen(azArg[1]) != 1) { raw_printf(stderr, ".%s_sep SEP must be one byte, \"space\" or \"none\"\n", separator_name); return MetadataResult::FAIL; } else { separator = azArg[1][0]; } return MetadataResult::SUCCESS; } MetadataResult SetDecimalSep(ShellState &state, const char **azArg, idx_t nArg) { return SetSeparator(state, azArg, nArg, "decimal", state.decimal_separator); } MetadataResult SetThousandSep(ShellState &state, const char **azArg, idx_t nArg) { return SetSeparator(state, azArg, nArg, "thousand", state.thousand_separator); } MetadataResult SetLargeNumberRendering(ShellState &state, const char **azArg, idx_t nArg) { if (strcmp(azArg[1], "all") == 0) { state.large_number_rendering = LargeNumberRendering::ALL; } else if (strcmp(azArg[1], "footer") == 0) { state.large_number_rendering = LargeNumberRendering::FOOTER; } else { if (booleanValue(azArg[1])) { state.large_number_rendering = LargeNumberRendering::DEFAULT; } else { state.large_number_rendering = LargeNumberRendering::NONE; } } return MetadataResult::SUCCESS; } MetadataResult DumpTable(ShellState &state, const char **azArg, idx_t nArg) { char *zLike = 0; char *zSql; bool savedShowHeader = state.showHeader; int savedShellFlags = state.shellFlgs; state.ShellClearFlag(SHFLG_PreserveRowid | SHFLG_Newlines | SHFLG_Echo); for (idx_t i = 1; i < nArg; i++) { if (azArg[i][0] == '-') { const char *z = azArg[i] + 1; if (z[0] == '-') z++; if (strcmp(z, "newlines") == 0) { state.ShellSetFlag(SHFLG_Newlines); } else { raw_printf(stderr, "Unknown option \"%s\" on \".dump\"\n", azArg[i]); sqlite3_free(zLike); return MetadataResult::FAIL; } } else if (zLike) { zLike = sqlite3_mprintf("%z OR name LIKE %Q ESCAPE '\\'", zLike, azArg[i]); } else { zLike = sqlite3_mprintf("name LIKE %Q ESCAPE '\\'", azArg[i]); } } state.OpenDB(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(state.out, "BEGIN TRANSACTION;\n"); state.showHeader = 0; state.nErr = 0; if (zLike == 0) zLike = sqlite3_mprintf("true"); // Emit CREATE SCHEMA for non-main schemas first zSql = sqlite3_mprintf("SELECT DISTINCT table_schema FROM information_schema.tables " "WHERE table_schema != 'main' AND table_schema NOT LIKE 'pg_%%' " "AND table_schema != 'information_schema' " "AND table_name IN (SELECT name FROM sqlite_schema WHERE (%s) AND type=='table') " "ORDER BY table_schema", zLike); sqlite3_stmt *pStmt = NULL; if (sqlite3_prepare_v2(state.db, zSql, -1, &pStmt, 0) == SQLITE_OK) { while (sqlite3_step(pStmt) == SQLITE_ROW) { const char *schema = (const char *)sqlite3_column_text(pStmt, 0); if (schema) { char *quotedSchema = quoteIdentifier(schema); if (quotedSchema) { raw_printf(state.out, "CREATE SCHEMA IF NOT EXISTS %s;\n", quotedSchema); sqlite3_free(quotedSchema); } } } sqlite3_finalize(pStmt); } sqlite3_free(zSql); zSql = sqlite3_mprintf("SELECT name, type, sql FROM sqlite_schema " "WHERE (%s) AND type=='table'" " AND sql NOT NULL" " ORDER BY tbl_name='sqlite_sequence'", zLike); state.RunSchemaDumpQuery(zSql); sqlite3_free(zSql); zSql = sqlite3_mprintf("SELECT sql FROM sqlite_schema " "WHERE (%s) AND sql NOT NULL" " AND type IN ('index','trigger','view')", zLike); state.RunTableDumpQuery(zSql); sqlite3_free(zSql); sqlite3_free(zLike); raw_printf(state.out, state.nErr ? "ROLLBACK; -- due to errors\n" : "COMMIT;\n"); state.showHeader = savedShowHeader; state.shellFlgs = savedShellFlags; return MetadataResult::SUCCESS; } MetadataResult ToggleEcho(ShellState &state, const char **azArg, idx_t nArg) { state.SetOrClearFlag(SHFLG_Echo, azArg[1]); return MetadataResult::SUCCESS; } MetadataResult ExitProcess(ShellState &state, const char **azArg, idx_t nArg) { if (nArg > 2) { return MetadataResult::PRINT_USAGE; } int rc = 0; if (nArg > 1 && (rc = (int)integerValue(azArg[1])) != 0) { // exit immediately if a custom error code is provided exit(rc); } return MetadataResult::EXIT; } MetadataResult ToggleHeaders(ShellState &state, const char **azArg, idx_t nArg) { state.showHeader = booleanValue(azArg[1]); state.shellFlgs |= SHFLG_HeaderSet; return MetadataResult::SUCCESS; } MetadataResult SetHighlightColors(ShellState &state, const char **azArg, idx_t nArg) { if (nArg < 3 || nArg > 4) { return MetadataResult::PRINT_USAGE; } ShellHighlight highlighter(state); if (!highlighter.SetColor(azArg[1], azArg[2], nArg == 3 ? nullptr : azArg[3])) { return MetadataResult::FAIL; } return MetadataResult::SUCCESS; } MetadataResult ToggleHighlighErrors(ShellState &state, const char **azArg, idx_t nArg) { highlight_errors = booleanValue(azArg[1]) ? OptionType::ON : OptionType::OFF; return MetadataResult::SUCCESS; } MetadataResult ToggleHighlightResult(ShellState &state, const char **azArg, idx_t nArg) { highlight_results = booleanValue(azArg[1]) ? OptionType::ON : OptionType::OFF; return MetadataResult::SUCCESS; } MetadataResult ShowHelp(ShellState &state, const char **azArg, idx_t nArg) { if (nArg >= 2) { int n = showHelp(state.out, azArg[1]); if (n == 0) { utf8_printf(state.out, "Nothing matches '%s'\n", azArg[1]); } } else { showHelp(state.out, 0); } return MetadataResult::SUCCESS; } MetadataResult ToggleLog(ShellState &state, const char **azArg, idx_t nArg) { if (safe_mode) { utf8_printf(stderr, ".log cannot be used in -safe mode\n"); return MetadataResult::FAIL; } const char *zFile = azArg[1]; output_file_close(state.pLog); state.pLog = output_file_open(zFile, 0); return MetadataResult::SUCCESS; } MetadataResult SetMaxRows(ShellState &state, const char **azArg, idx_t nArg) { if (nArg > 2) { return MetadataResult::PRINT_USAGE; } if (nArg == 1) { raw_printf(state.out, "current max rows: %zu\n", state.max_rows); } else { state.max_rows = (size_t)integerValue(azArg[1]); } return MetadataResult::SUCCESS; } MetadataResult SetMaxWidth(ShellState &state, const char **azArg, idx_t nArg) { if (nArg > 2) { return MetadataResult::PRINT_USAGE; } if (nArg == 1) { raw_printf(state.out, "current max rows: %zu\n", state.max_width); } else { state.max_width = (size_t)integerValue(azArg[1]); } return MetadataResult::SUCCESS; } MetadataResult SetColumnRendering(ShellState &state, const char **azArg, idx_t nArg) { state.columns = 1; return MetadataResult::SUCCESS; } MetadataResult SetRowRendering(ShellState &state, const char **azArg, idx_t nArg) { state.columns = 0; return MetadataResult::SUCCESS; } MetadataResult EnableSafeMode(ShellState &state, const char **azArg, idx_t nArg) { safe_mode = true; if (state.db) { // db has been opened - disable external access sqlite3_exec(state.db, "SET enable_external_access=false", NULL, NULL, NULL); } return MetadataResult::SUCCESS; } bool ShellState::SetOutputMode(const char *mode_str, const char *tbl_name) { idx_t n2 = StringLength(mode_str); char c2 = mode_str[0]; if (tbl_name && !(c2 == 'i' && strncmp(mode_str, "insert", n2) == 0)) { raw_printf(stderr, "TABLE argument can only be used with .mode insert"); return false; } if (c2 == 'l' && n2 > 2 && strncmp(mode_str, "lines", n2) == 0) { mode = RenderMode::LINE; rowSeparator = SEP_Row; } else if (c2 == 'c' && strncmp(mode_str, "columns", n2) == 0) { mode = RenderMode::COLUMN; if ((shellFlgs & SHFLG_HeaderSet) == 0) { showHeader = true; } rowSeparator = SEP_Row; } else if (c2 == 'l' && n2 > 2 && strncmp(mode_str, "list", n2) == 0) { mode = RenderMode::LIST; colSeparator = SEP_Column; rowSeparator = SEP_Row; } else if (c2 == 'h' && strncmp(mode_str, "html", n2) == 0) { mode = RenderMode::HTML; } else if (c2 == 't' && strncmp(mode_str, "tcl", n2) == 0) { mode = RenderMode::TCL; colSeparator = SEP_Space; rowSeparator = SEP_Row; } else if (c2 == 'c' && strncmp(mode_str, "csv", n2) == 0) { mode = RenderMode::CSV; colSeparator = SEP_Comma; rowSeparator = SEP_CrLf; } else if (c2 == 't' && strncmp(mode_str, "tabs", n2) == 0) { mode = RenderMode::LIST; colSeparator = SEP_Tab; } else if (c2 == 'i' && strncmp(mode_str, "insert", n2) == 0) { mode = RenderMode::INSERT; SetTableName(tbl_name ? tbl_name : "table"); } else if (c2 == 'q' && strncmp(mode_str, "quote", n2) == 0) { mode = RenderMode::QUOTE; colSeparator = SEP_Comma; rowSeparator = SEP_Row; } else if (c2 == 'a' && strncmp(mode_str, "ascii", n2) == 0) { mode = RenderMode::ASCII; colSeparator = SEP_Unit; rowSeparator = SEP_Record; } else if (c2 == 'm' && strncmp(mode_str, "markdown", n2) == 0) { mode = RenderMode::MARKDOWN; } else if (c2 == 't' && strncmp(mode_str, "table", n2) == 0) { mode = RenderMode::TABLE; } else if (c2 == 'b' && strncmp(mode_str, "box", n2) == 0) { mode = RenderMode::BOX; } else if (c2 == 'd' && strncmp(mode_str, "duckbox", n2) == 0) { mode = RenderMode::DUCKBOX; } else if (c2 == 'j' && strncmp(mode_str, "json", n2) == 0) { mode = RenderMode::JSON; } else if (c2 == 'l' && strncmp(mode_str, "latex", n2) == 0) { mode = RenderMode::LATEX; } else if (c2 == 't' && strncmp(mode_str, "trash", n2) == 0) { mode = RenderMode::TRASH; } else if (c2 == 'j' && strncmp(mode_str, "jsonlines", n2) == 0) { mode = RenderMode::JSONLINES; } else { raw_printf(stderr, "Error: mode should be one of: " "ascii box column csv duckbox html insert json jsonlines latex line " "list markdown quote table tabs tcl trash \n"); return false; } cMode = mode; return true; } MetadataResult SetOutputMode(ShellState &state, const char **azArg, idx_t nArg) { if (nArg > 3) { return MetadataResult::PRINT_USAGE; } if (nArg == 1) { raw_printf(state.out, "current output mode: %s\n", modeDescr[int(state.mode)]); } else { if (!state.SetOutputMode(azArg[1], nArg > 2 ? azArg[2] : nullptr)) { return MetadataResult::FAIL; } } return MetadataResult::SUCCESS; } MetadataResult SetNullValue(ShellState &state, const char **azArg, idx_t nArg) { state.nullValue = azArg[1]; return MetadataResult::SUCCESS; } bool ShellState::ImportData(const char **azArg, idx_t nArg) { if (safe_mode) { utf8_printf(stderr, ".import cannot be used in -safe mode\n"); return false; } int rc; const char *zTable = nullptr; /* Insert data into this table */ const char *zFile = nullptr; /* Name of file to extra content from */ sqlite3_stmt *pStmt = nullptr; /* A statement */ int nCol; /* Number of columns in the table */ int nByte; /* Number of bytes in an SQL string */ int j; /* Loop counters */ int needCommit; /* True to COMMIT or ROLLBACK at end */ char *zSql; /* An SQL statement */ ImportCtx sCtx; /* Reader context */ char *(SQLITE_CDECL * xRead)(ImportCtx *); /* Func to read one value */ int eVerbose = 0; /* Larger for more console output */ int nSkip = 0; /* Initial lines to skip */ int useOutputMode = 1; /* Use output mode to determine separators */ memset(&sCtx, 0, sizeof(sCtx)); if (mode == RenderMode::ASCII) { xRead = ascii_read_one_field; } else { xRead = csv_read_one_field; } for (idx_t i = 1; i < nArg; i++) { auto z = azArg[i]; if (z[0] == '-' && z[1] == '-') z++; if (z[0] != '-') { if (zFile == 0) { zFile = z; } else if (zTable == 0) { zTable = z; } else { utf8_printf(out, "ERROR: extra argument: \"%s\". Usage:\n", z); showHelp(out, "import"); return false; } } else if (strcmp(z, "-v") == 0) { eVerbose++; } else if (strcmp(z, "-skip") == 0 && i < nArg - 1) { nSkip = (int)integerValue(azArg[++i]); } else if (strcmp(z, "-ascii") == 0) { sCtx.cColSep = SEP_Unit[0]; sCtx.cRowSep = SEP_Record[0]; xRead = ascii_read_one_field; useOutputMode = 0; } else if (strcmp(z, "-csv") == 0) { sCtx.cColSep = ','; sCtx.cRowSep = '\n'; xRead = csv_read_one_field; useOutputMode = 0; } else { utf8_printf(out, "ERROR: unknown option: \"%s\". Usage:\n", z); showHelp(out, "import"); return false; } } if (zTable == 0) { utf8_printf(out, "ERROR: missing %s argument. Usage:\n", zFile == 0 ? "FILE" : "TABLE"); showHelp(out, "import"); return false; } seenInterrupt = 0; OpenDB(0); if (useOutputMode) { /* If neither the --csv or --ascii options are specified, then set ** the column and row separator characters from the output mode. */ int nSep = colSeparator.size(); if (nSep == 0) { raw_printf(stderr, "Error: non-null column separator required for import\n"); return false; } if (nSep > 1) { raw_printf(stderr, "Error: multi-character column separators not allowed" " for import\n"); return false; } nSep = rowSeparator.size(); if (nSep == 0) { raw_printf(stderr, "Error: non-null row separator required for import\n"); return false; } if (nSep == 2 && mode == RenderMode::CSV && rowSeparator == SEP_CrLf) { /* 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. */ rowSeparator = SEP_Row; nSep = rowSeparator.size(); } if (nSep > 1) { raw_printf(stderr, "Error: multi-character row separators not allowed" " for import\n"); return false; } sCtx.cColSep = colSeparator[0]; sCtx.cRowSep = rowSeparator[0]; } sCtx.zFile = zFile; sCtx.nLine = 1; if (sCtx.zFile[0] == '|') { #ifdef SQLITE_OMIT_POPEN raw_printf(stderr, "Error: pipes are not supported in this OS\n"); rc = 1; goto meta_command_exit; #else sCtx.in = popen(sCtx.zFile + 1, "r"); sCtx.zFile = ""; sCtx.xCloser = pclose; #endif } else { sCtx.in = fopen(sCtx.zFile, "rb"); sCtx.xCloser = fclose; } if (sCtx.in == 0) { utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile); return false; } if (eVerbose >= 2 || (eVerbose >= 1 && useOutputMode)) { char zSep[2]; zSep[1] = 0; zSep[0] = sCtx.cColSep; utf8_printf(out, "Column separator "); OutputCString(zSep); utf8_printf(out, ", row separator "); zSep[0] = sCtx.cRowSep; OutputCString(zSep); utf8_printf(out, "\n"); } while ((nSkip--) > 0) { while (xRead(&sCtx) && sCtx.cTerm == sCtx.cColSep) { } } zSql = sqlite3_mprintf("SELECT * FROM %s", zTable); if (zSql == 0) { import_cleanup(&sCtx); shell_out_of_memory(); } nByte = StringLength(zSql); rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ if (rc && sqlite3_strglob("Catalog Error: Table with name *", sqlite3_errmsg(db)) == 0) { char *zCreate = sqlite3_mprintf("CREATE TABLE %s", zTable); char cSep = '('; while (xRead(&sCtx)) { zCreate = sqlite3_mprintf("%z%c\n \"%w\" TEXT", zCreate, cSep, sCtx.z); cSep = ','; if (sCtx.cTerm != sCtx.cColSep) break; } if (cSep == '(') { sqlite3_free(zCreate); import_cleanup(&sCtx); utf8_printf(stderr, "%s: empty file\n", sCtx.zFile); return false; } zCreate = sqlite3_mprintf("%z\n)", zCreate); if (eVerbose >= 1) { utf8_printf(out, "%s\n", zCreate); } rc = sqlite3_exec(db, zCreate, 0, 0, 0); sqlite3_free(zCreate); if (rc) { utf8_printf(stderr, "CREATE TABLE %s(...) failed: %s\n", zTable, sqlite3_errmsg(db)); import_cleanup(&sCtx); return false; } rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); } sqlite3_free(zSql); if (rc) { if (pStmt) sqlite3_finalize(pStmt); ShellDatabaseError(db); import_cleanup(&sCtx); return false; } nCol = sqlite3_column_count(pStmt); sqlite3_finalize(pStmt); pStmt = 0; if (nCol == 0) return 0; /* no columns, no error */ zSql = (char *)sqlite3_malloc64(nByte * 2 + 20 + nCol * 2); if (zSql == 0) { import_cleanup(&sCtx); shell_out_of_memory(); } sqlite3_snprintf(nByte + 20, zSql, "INSERT INTO \"%w\" VALUES(?", zTable); j = StringLength(zSql); for (int i = 1; i < nCol; i++) { zSql[j++] = ','; zSql[j++] = '?'; } zSql[j++] = ')'; zSql[j] = 0; if (eVerbose >= 2) { utf8_printf(out, "Insert using: %s\n", zSql); } rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); if (rc) { ShellDatabaseError(db); if (pStmt) sqlite3_finalize(pStmt); import_cleanup(&sCtx); return false; } needCommit = sqlite3_get_autocommit(db); if (needCommit) sqlite3_exec(db, "BEGIN", 0, 0, 0); do { int startLine = sCtx.nLine; int i; 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 (mode == RenderMode::ASCII && (z == 0 || z[0] == 0) && i == 0) break; sqlite3_bind_text(pStmt, i + 1, z, -1, SQLITE_TRANSIENT); if (i < nCol - 1 && sCtx.cTerm != sCtx.cColSep) { utf8_printf(stderr, "%s:%d: expected %d columns but found %d - " "filling the rest with NULL\n", sCtx.zFile, startLine, nCol, i + 1); i += 2; while (i <= nCol) { sqlite3_bind_null(pStmt, i); i++; } } } if (sCtx.cTerm == sCtx.cColSep) { do { xRead(&sCtx); i++; } while (sCtx.cTerm == sCtx.cColSep); utf8_printf(stderr, "%s:%d: expected %d columns but found %d - " "extras ignored\n", sCtx.zFile, startLine, nCol, i); } if (i >= nCol) { sqlite3_step(pStmt); rc = sqlite3_reset(pStmt); if (rc != SQLITE_OK) { utf8_printf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile, startLine, sqlite3_errmsg(db)); sCtx.nErr++; } else { sCtx.nRow++; } } } while (sCtx.cTerm != EOF); import_cleanup(&sCtx); sqlite3_finalize(pStmt); if (needCommit) sqlite3_exec(db, "COMMIT", 0, 0, 0); if (eVerbose > 0) { utf8_printf(out, "Added %d rows with %d errors using %d lines of input\n", sCtx.nRow, sCtx.nErr, sCtx.nLine - 1); } return true; } MetadataResult ImportData(ShellState &state, const char **azArg, idx_t nArg) { if (!state.ImportData(azArg, nArg)) { return MetadataResult::FAIL; } return MetadataResult::SUCCESS; } bool ShellState::OpenDatabase(const char **azArg, idx_t nArg) { if (safe_mode) { utf8_printf(stderr, ".open cannot be used in -safe mode\n"); return false; } char *zNewFilename; /* Name of the database file to open */ idx_t iName = 1; /* Index in azArg[] of the filename */ bool newFlag = false; /* True to delete file before opening */ /* Close the existing database */ close_db(db); db = nullptr; globalDb = nullptr; zDbFilename = string(); openMode = SHELL_OPEN_UNSPEC; openFlags = openFlags & ~(SQLITE_OPEN_NOFOLLOW); // don't overwrite settings loaded in the command line szMax = 0; /* Check for command-line arguments */ for (iName = 1; iName < nArg && azArg[iName][0] == '-'; iName++) { const char *z = azArg[iName]; if (optionMatch(z, "new")) { newFlag = true; } else if (optionMatch(z, "readonly")) { openMode = SHELL_OPEN_READONLY; } else if (optionMatch(z, "nofollow")) { openFlags |= SQLITE_OPEN_NOFOLLOW; } else if (z[0] == '-') { utf8_printf(stderr, "unknown option: %s\n", z); return false; } } /* If a filename is specified, try to open it first */ zNewFilename = nArg > iName ? sqlite3_mprintf("%s", azArg[iName]) : 0; if (zNewFilename || openMode == SHELL_OPEN_HEXDB) { if (newFlag) { shellDeleteFile(zNewFilename); } zDbFilename = zNewFilename; sqlite3_free(zNewFilename); OpenDB(OPEN_DB_KEEPALIVE); if (!db) { utf8_printf(stderr, "Error: cannot open '%s'\n", zNewFilename); } } if (!db) { /* As a fall-back open a TEMP database */ zDbFilename = string(); OpenDB(0); } return true; } MetadataResult OpenDatabase(ShellState &state, const char **azArg, idx_t nArg) { if (!state.OpenDatabase(azArg, nArg)) { return MetadataResult::FAIL; } return MetadataResult::SUCCESS; } MetadataResult PrintArguments(ShellState &state, const char **azArg, idx_t nArg) { for (idx_t i = 1; i < nArg; i++) { if (i > 1) { raw_printf(state.out, " "); } utf8_printf(state.out, "%s", azArg[i]); } raw_printf(state.out, "\n"); return MetadataResult::SUCCESS; } MetadataResult SetPrompt(ShellState &, const char **azArg, idx_t nArg) { if (nArg >= 2) { strncpy(mainPrompt, azArg[1], (int)ArraySize(mainPrompt) - 1); } if (nArg >= 3) { strncpy(continuePrompt, azArg[2], (int)ArraySize(continuePrompt) - 1); } if (nArg >= 4) { strncpy(continuePromptSelected, azArg[3], (int)ArraySize(continuePromptSelected) - 1); } return MetadataResult::SUCCESS; } MetadataResult SetSeparator(ShellState &state, const char **azArg, idx_t nArg) { if (nArg < 2 || nArg > 3) { return MetadataResult::PRINT_USAGE; } state.colSeparator = azArg[1]; if (nArg >= 3) { state.rowSeparator = azArg[2]; } return MetadataResult::SUCCESS; } MetadataResult QuitProcess(ShellState &, const char **azArg, idx_t nArg) { return MetadataResult::EXIT; } bool ShellState::SetOutputFile(const char **azArg, idx_t nArg, char output_mode) { if (safe_mode) { utf8_printf(stderr, ".output/.once/.excel cannot be used in -safe mode\n"); return false; } const char *zFile = nullptr; int bTxtMode = 0; int eMode = 0; bool bBOM = false; int bOnce = 0; /* 0: .output, 1: .once, 2: .excel */ if (output_mode == 'e') { // .excel eMode = 'x'; bOnce = 2; } else if (output_mode == 'o') { // .once bOnce = 1; } for (idx_t i = 1; i < nArg; i++) { const char *z = azArg[i]; if (z[0] == '-') { if (z[1] == '-') { z++; } if (strcmp(z, "-bom") == 0) { bBOM = true; } else if (output_mode != 'e' && strcmp(z, "-x") == 0) { eMode = 'x'; /* spreadsheet */ } else if (output_mode != 'e' && strcmp(z, "-e") == 0) { eMode = 'e'; /* text editor */ } else { utf8_printf(out, "ERROR: unknown option: \"%s\". Usage:\n", azArg[i]); showHelp(out, azArg[0]); return false; } } else if (!zFile) { zFile = z; } else { utf8_printf(out, "ERROR: extra parameter: \"%s\". Usage:\n", azArg[i]); showHelp(out, azArg[0]); return false; } } if (!zFile) { zFile = "stdout"; } if (bOnce) { outCount = 2; } else { outCount = 0; } ResetOutput(); #ifndef SQLITE_NOHAVE_SYSTEM if (eMode == 'e' || eMode == 'x') { doXdgOpen = 1; PushOutputMode(); if (eMode == 'x') { /* spreadsheet mode. Output as CSV. */ NewTempFile("csv"); ShellClearFlag(SHFLG_Echo); mode = RenderMode::CSV; colSeparator = SEP_Comma; rowSeparator = SEP_CrLf; } else { /* text editor mode */ NewTempFile("txt"); bTxtMode = 1; } zFile = zTempFile; } #endif /* SQLITE_NOHAVE_SYSTEM */ if (zFile[0] == '|') { #ifdef SQLITE_OMIT_POPEN raw_printf(stderr, "Error: pipes are not supported in this OS\n"); out = stdout; return false; #else out = popen(zFile + 1, "w"); if (out == 0) { utf8_printf(stderr, "Error: cannot open pipe \"%s\"\n", zFile + 1); out = stdout; return false; } else { if (bBOM) { fprintf(out, "\357\273\277"); } outfile = zFile; } #endif } else { out = output_file_open(zFile, bTxtMode); if (!out) { if (strcmp(zFile, "off") != 0) { utf8_printf(stderr, "Error: cannot write to \"%s\"\n", zFile); } out = stdout; return false; } else { if (bBOM) { fprintf(out, "\357\273\277"); } outfile = zFile; } } stdout_is_console = false; return true; } MetadataResult SetOutput(ShellState &state, const char **azArg, idx_t nArg) { if (!state.SetOutputFile(azArg, nArg, '\0')) { return MetadataResult::FAIL; } return MetadataResult::SUCCESS; } MetadataResult SetOutputOnce(ShellState &state, const char **azArg, idx_t nArg) { if (!state.SetOutputFile(azArg, nArg, 'o')) { return MetadataResult::FAIL; } return MetadataResult::SUCCESS; } MetadataResult SetOutputExcel(ShellState &state, const char **azArg, idx_t nArg) { if (!state.SetOutputFile(azArg, nArg, 'e')) { return MetadataResult::FAIL; } return MetadataResult::SUCCESS; } bool ShellState::ReadFromFile(const string &file) { if (safe_mode) { utf8_printf(stderr, ".read cannot be used in -safe mode\n"); return false; } FILE *inSaved = in; int savedLineno = lineno; int rc; if (notNormalFile(file.c_str()) || (in = fopen(file.c_str(), "rb")) == 0) { utf8_printf(stderr, "Error: cannot open \"%s\"\n", file.c_str()); rc = 1; } else { rc = ProcessInput(InputMode::FILE); fclose(in); } in = inSaved; lineno = savedLineno; return rc == 0; } MetadataResult ReadFromFile(ShellState &state, const char **azArg, idx_t nArg) { if (!state.ReadFromFile(azArg[1])) { return MetadataResult::FAIL; } return MetadataResult::SUCCESS; } bool ShellState::DisplaySchemas(const char **azArg, idx_t nArg) { string sSelect; char *zErrMsg = 0; const char *zDiv = "("; const char *zName = 0; int bDebug = 0; int rc; OpenDB(0); RenderMode mode = RenderMode::SEMI; for (idx_t ii = 1; ii < nArg; ii++) { if (optionMatch(azArg[ii], "indent")) { mode = RenderMode::PRETTY; } else if (optionMatch(azArg[ii], "debug")) { bDebug = 1; } else if (zName == 0) { zName = azArg[ii]; } else { raw_printf(stderr, "Usage: .schema ?--indent? ?LIKE-PATTERN?\n"); return false; } } auto renderer = GetRowRenderer(mode); renderer->show_header = false; if (zDiv) { appendText(sSelect, "SELECT sql FROM sqlite_master WHERE ", 0); if (zName) { char *zQarg = sqlite3_mprintf("%Q", zName); int bGlob = strchr(zName, '*') != 0 || strchr(zName, '?') != 0 || strchr(zName, '[') != 0; if (strchr(zName, '.')) { appendText(sSelect, "lower(printf('%s.%s',sname,tbl_name))", 0); } else { appendText(sSelect, "lower(tbl_name)", 0); } appendText(sSelect, bGlob ? " GLOB " : " LIKE ", 0); appendText(sSelect, zQarg, 0); if (!bGlob) { appendText(sSelect, " ESCAPE '\\' ", 0); } appendText(sSelect, " AND ", 0); sqlite3_free(zQarg); } appendText(sSelect, "type!='meta' AND sql IS NOT NULL" " ORDER BY name", 0); if (bDebug) { utf8_printf(out, "SQL: %s;\n", sSelect.c_str()); } else { rc = sqlite3_exec(db, sSelect.c_str(), callback, renderer.get(), &zErrMsg); } } if (zErrMsg) { PrintDatabaseError(zErrMsg); sqlite3_free(zErrMsg); return false; } else if (rc != SQLITE_OK) { raw_printf(stderr, "Error: querying schema information\n"); return false; } else { return true; } } MetadataResult DisplaySchemas(ShellState &state, const char **azArg, idx_t nArg) { if (!state.DisplaySchemas(azArg, nArg)) { return MetadataResult::FAIL; } return MetadataResult::SUCCESS; } MetadataResult RunShellCommand(ShellState &state, const char **azArg, idx_t nArg) { if (safe_mode) { utf8_printf(stderr, ".sh/.system cannot be used in -safe mode\n"); return MetadataResult::FAIL; } char *zCmd; int x; if (nArg < 2) { return MetadataResult::PRINT_USAGE; } zCmd = sqlite3_mprintf(strchr(azArg[1], ' ') == 0 ? "%s" : "\"%s\"", azArg[1]); for (idx_t i = 2; i < nArg; i++) { zCmd = sqlite3_mprintf(strchr(azArg[i], ' ') == 0 ? "%z %s" : "%z \"%s\"", zCmd, azArg[i]); } x = system(zCmd); sqlite3_free(zCmd); if (x) { raw_printf(stderr, "System command returns %d\n", x); } return MetadataResult::SUCCESS; } void ShellState::ShowConfiguration() { utf8_printf(out, "%12.12s: %s\n", "echo", ShellHasFlag(SHFLG_Echo) ? "on" : "off"); utf8_printf(out, "%12.12s: %s\n", "headers", showHeader ? "on" : "off"); utf8_printf(out, "%12.12s: %s\n", "mode", modeDescr[int(mode)]); utf8_printf(out, "%12.12s: ", "nullvalue"); OutputCString(nullValue.c_str()); raw_printf(out, "\n"); utf8_printf(out, "%12.12s: %s\n", "output", !outfile.empty() ? outfile.c_str() : "stdout"); utf8_printf(out, "%12.12s: ", "colseparator"); OutputCString(colSeparator.c_str()); raw_printf(out, "\n"); utf8_printf(out, "%12.12s: ", "rowseparator"); OutputCString(rowSeparator.c_str()); raw_printf(out, "\n"); utf8_printf(out, "%12.12s: ", "width"); for (auto w : colWidth) { raw_printf(out, "%d ", w); } raw_printf(out, "\n"); utf8_printf(out, "%12.12s: %s\n", "filename", zDbFilename.c_str()); } MetadataResult ShowConfiguration(ShellState &state, const char **azArg, idx_t nArg) { state.ShowConfiguration(); return MetadataResult::SUCCESS; } MetadataResult ToggleTimer(ShellState &state, const char **azArg, idx_t nArg) { enableTimer = booleanValue(azArg[1]); if (enableTimer && !HAS_TIMER) { raw_printf(stderr, "Error: timer not available on this system.\n"); enableTimer = false; } return MetadataResult::SUCCESS; } MetadataResult ShowVersion(ShellState &state, const char **azArg, idx_t nArg) { utf8_printf(state.out, "DuckDB %s (%s) %s\n" /*extra-version-info*/, duckdb::DuckDB::LibraryVersion(), duckdb::DuckDB::ReleaseCodename(), duckdb::DuckDB::SourceID()); #define CTIMEOPT_VAL_(opt) #opt #define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt) #if defined(__clang__) && defined(__clang_major__) utf8_printf(state.out, "clang-" CTIMEOPT_VAL(__clang_major__) "." CTIMEOPT_VAL(__clang_minor__) "." CTIMEOPT_VAL( __clang_patchlevel__) "\n"); #elif defined(_MSC_VER) utf8_printf(state.out, "msvc-" CTIMEOPT_VAL(_MSC_VER) "\n"); #elif defined(__GNUC__) && defined(__VERSION__) utf8_printf(state.out, "gcc-" __VERSION__ "\n"); #endif return MetadataResult::SUCCESS; } MetadataResult SetWidths(ShellState &state, const char **azArg, idx_t nArg) { state.colWidth.clear(); for (idx_t j = 1; j < nArg; j++) { state.colWidth.push_back((int)integerValue(azArg[j])); } return MetadataResult::SUCCESS; } MetadataResult ShellState::DisplayEntries(const char **azArg, idx_t nArg, char type) { sqlite3_stmt *pStmt; char **azResult; int nRow, nAlloc; int ii; string s; OpenDB(0); if (nArg > 2) { return MetadataResult::PRINT_USAGE; } // Parse the filter pattern to check for schema qualification string filter_pattern = nArg > 1 ? azArg[1] : "%"; string schema_filter = ""; string table_filter = filter_pattern; // Parse the filter pattern to check for schema qualification try { auto components = duckdb::QualifiedName::ParseComponents(filter_pattern); if (components.size() >= 2) { // e.g : "schema.table" or "schema.%" schema_filter = components[0]; table_filter = components[1]; // e.g : "schema." if (table_filter.empty()) { table_filter = "%"; } } } catch (const duckdb::ParserException &) { // If parsing fails, treat as a simple table pattern schema_filter = ""; table_filter = filter_pattern; } // Use DuckDB's system tables instead of SQLite's sqlite_schema if (type == 't') { // For tables, we need to handle schema disambiguation appendText(s, "WITH all_objects AS (", 0); appendText(s, " SELECT schema_name, table_name as name FROM duckdb_tables", 0); if (!schema_filter.empty()) { appendText(s, " WHERE schema_name LIKE ?1", 0); } appendText(s, " UNION ALL", 0); appendText(s, " SELECT schema_name, view_name as name FROM duckdb_views", 0); if (!schema_filter.empty()) { appendText(s, " WHERE schema_name LIKE ?1", 0); } appendText(s, "),", 0); appendText(s, "name_counts AS (", 0); appendText(s, " SELECT name, COUNT(*) as count FROM all_objects", 0); appendText(s, " GROUP BY name", 0); appendText(s, "),", 0); appendText(s, "disambiguated AS (", 0); appendText(s, " SELECT", 0); appendText(s, " CASE", 0); appendText(s, " WHEN nc.count > 1 THEN ao.schema_name || '.' || ao.name", 0); appendText(s, " ELSE ao.name", 0); appendText(s, " END as display_name", 0); appendText(s, " FROM all_objects ao", 0); appendText(s, " JOIN name_counts nc ON ao.name = nc.name", 0); if (!schema_filter.empty()) { appendText(s, " WHERE ao.name LIKE ?2", 0); } else { appendText(s, " WHERE ao.name LIKE ?1", 0); } appendText(s, ")", 0); appendText(s, "SELECT DISTINCT display_name FROM disambiguated ORDER BY display_name", 0); } else { // For indexes, use the original SQLite approach appendText(s, "SELECT name FROM ", 0); appendText(s, "sqlite_schema ", 0); appendText(s, " WHERE type='index'" " AND tbl_name LIKE ?1", 0); appendText(s, " ORDER BY 1", 0); } int rc = sqlite3_prepare_v2(db, s.c_str(), -1, &pStmt, 0); if (rc) { return MetadataResult::FAIL; } /* Run the SQL statement prepared by the above block. Store the results ** as an array of nul-terminated strings in azResult[]. */ nRow = nAlloc = 0; azResult = nullptr; if (type == 't') { // Bind parameters for the new DuckDB query if (!schema_filter.empty()) { sqlite3_bind_text(pStmt, 1, schema_filter.c_str(), -1, SQLITE_TRANSIENT); sqlite3_bind_text(pStmt, 2, table_filter.c_str(), -1, SQLITE_TRANSIENT); } else { sqlite3_bind_text(pStmt, 1, filter_pattern.c_str(), -1, SQLITE_TRANSIENT); } } else { // Original binding for indexes if (nArg > 1) { sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT); } else { sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC); } } while (sqlite3_step(pStmt) == SQLITE_ROW) { if (nRow >= nAlloc) { char **azNew; int n2 = nAlloc * 2 + 10; azNew = (char **)sqlite3_realloc64(azResult, sizeof(azResult[0]) * n2); if (azNew == 0) shell_out_of_memory(); nAlloc = n2; azResult = azNew; } azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); if (0 == azResult[nRow]) shell_out_of_memory(); nRow++; } if (sqlite3_finalize(pStmt) != SQLITE_OK) { rc = ShellDatabaseError(db); } /* 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 = 0; i < nRow; i++) { len = StringLength(azResult[i]); if (len > maxlen) maxlen = len; } nPrintCol = 80 / (maxlen + 2); if (nPrintCol < 1) nPrintCol = 1; nPrintRow = (nRow + nPrintCol - 1) / nPrintCol; for (i = 0; i < nPrintRow; i++) { for (j = i; j < nRow; j += nPrintRow) { const char *zSp = j < nPrintRow ? "" : " "; utf8_printf(out, "%s%-*s", zSp, maxlen, azResult[j] ? azResult[j] : ""); } raw_printf(out, "\n"); } } for (ii = 0; ii < nRow; ii++) { sqlite3_free(azResult[ii]); } sqlite3_free(azResult); return rc == 0 ? MetadataResult::SUCCESS : MetadataResult::FAIL; } MetadataResult ShowIndexes(ShellState &state, const char **azArg, idx_t nArg) { return state.DisplayEntries(azArg, nArg, 'i'); } MetadataResult ShowTables(ShellState &state, const char **azArg, idx_t nArg) { return state.DisplayEntries(azArg, nArg, 't'); } MetadataResult SetUICommand(ShellState &state, const char **azArg, idx_t nArg) { if (nArg < 1) { return MetadataResult::PRINT_USAGE; } string command; for (idx_t i = 1; i < nArg; i++) { if (i > 1) { command += " "; } command += azArg[i]; } state.ui_command = "CALL " + command; return MetadataResult::SUCCESS; } #if defined(_WIN32) || defined(WIN32) MetadataResult SetUTF8Mode(ShellState &state, const char **azArg, idx_t nArg) { win_utf8_mode = 1; return MetadataResult::SUCCESS; } #endif static const MetadataCommand metadata_commands[] = { {"backup", 0, nullptr, "?DB? FILE", "Backup DB (default \"main\") to FILE", 3}, {"bail", 2, ToggleBail, "on|off", "Stop after hitting an error. Default OFF", 3}, {"binary", 2, ToggleBinary, "on|off", "Turn binary output on or off. Default OFF", 3}, {"cd", 2, ChangeDirectory, "DIRECTORY", "Change the working directory to DIRECTORY", 0}, {"changes", 2, ToggleChanges, "on|off", "Show number of rows changed by SQL", 3}, {"columns", 1, SetColumnRendering, "", "Column-wise rendering of query results", 0}, {"decimal_sep", 0, SetDecimalSep, "SEP", "Sets the decimal separator used when rendering numbers. Only for duckbox mode.", 3}, {"databases", 1, ShowDatabases, "", "List names and files of attached databases", 2}, {"dump", 0, DumpTable, "?TABLE?", "Render database content as SQL\n Options:\n --newlines Allow unescaped newline characters in " "output\n TABLE is a LIKE pattern for the tables to dump\n Additional LIKE patterns can be given in " "subsequent arguments", 0}, {"echo", 2, ToggleEcho, "on|off", "Turn command echo on or off", 3}, {"excel", 0, SetOutputExcel, "", "Display the output of next command in spreadsheet", 0}, {"exit", 0, ExitProcess, "?CODE?", "Exit this program with return-code CODE", 0}, {"fullschema", 0, nullptr, "", "", 0}, {"headers", 2, ToggleHeaders, "on|off", "Turn display of headers on or off", 0}, {"help", 0, ShowHelp, "?-all? ?PATTERN?", "Show help text for PATTERN", 0}, {"highlight_colors", 0, SetHighlightColors, "[element] [color] ([bold])?", "Configure highlighting colors", 0}, {"highlight_errors", 2, ToggleHighlighErrors, "on|off", "Turn highlighting of errors on or off", 0}, {"highlight_results", 2, ToggleHighlightResult, "on|off", "Turn highlighting of results on or off", 0}, {"import", 0, ImportData, "FILE TABLE", "Import data from FILE into TABLE", 0}, {"indexes", 0, ShowIndexes, "?TABLE?", "Show names of indexes", 0}, {"indices", 0, ShowIndexes, "?TABLE?", "Show names of indexes", 0}, {"large_number_rendering", 2, SetLargeNumberRendering, "all|footer|off", "Toggle readable rendering of large numbers (duckbox only)", 0}, {"log", 2, ToggleLog, "FILE|off", "Turn logging on or off. FILE can be stderr/stdout", 0}, {"maxrows", 0, SetMaxRows, "COUNT", "Sets the maximum number of rows for display (default: 40). Only for duckbox mode.", 0}, {"maxwidth", 0, SetMaxWidth, "COUNT", "Sets the maximum width in characters. 0 defaults to terminal width. Only for duckbox mode.", 0}, {"mode", 0, SetOutputMode, "MODE ?TABLE?", "Set output mode", 0}, {"nullvalue", 2, SetNullValue, "STRING", "Use STRING in place of NULL values", 0}, {"open", 0, OpenDatabase, "?OPTIONS? ?FILE?", "Close existing database and reopen FILE", 2}, {"once", 0, SetOutputOnce, "?FILE?", "Output for the next SQL command only to FILE", 0}, {"output", 0, SetOutput, "?FILE?", "Send output to FILE or stdout if FILE is omitted", 0}, {"print", 0, PrintArguments, "STRING...", "Print literal STRING", 3}, {"prompt", 0, SetPrompt, "MAIN CONTINUE", "Replace the standard prompts", 0}, {"quit", 0, QuitProcess, "", "Exit this program", 0}, {"read", 2, ReadFromFile, "FILE", "Read input from FILE", 3}, {"rows", 1, SetRowRendering, "", "Row-wise rendering of query results (default)", 0}, {"restore", 0, nullptr, "", "", 3}, {"save", 0, nullptr, "?DB? FILE", "Backup DB (default \"main\") to FILE", 3}, {"safe_mode", 0, EnableSafeMode, "", "enable safe-mode", 0}, {"separator", 0, SetSeparator, "COL ?ROW?", "Change the column and row separators", 0}, {"schema", 0, DisplaySchemas, "?PATTERN?", "Show the CREATE statements matching PATTERN", 0}, {"shell", 0, RunShellCommand, "CMD ARGS...", "Run CMD ARGS... in a system shell", 0}, {"show", 1, ShowConfiguration, "", "Show the current values for various settings", 0}, {"system", 0, RunShellCommand, "CMD ARGS...", "Run CMD ARGS... in a system shell", 0}, {"tables", 0, ShowTables, "?TABLE?", "List names of tables matching LIKE pattern TABLE", 2}, {"thousand_sep", 0, SetThousandSep, "SEP", "Sets the thousand separator used when rendering numbers. Only for duckbox mode.", 4}, {"timeout", 0, nullptr, "", "", 5}, {"timer", 2, ToggleTimer, "on|off", "Turn SQL timer on or off", 0}, {"ui_command", 0, SetUICommand, "[command]", "Set the UI command", 0}, {"version", 1, ShowVersion, "", "Show the version", 0}, {"width", 0, SetWidths, "NUM1 NUM2 ...", "Set minimum column widths for columnar output", 0}, #if defined(_WIN32) || defined(WIN32) {"utf8", 1, SetUTF8Mode, "", "Enable experimental UTF-8 console output mode", 0}, #endif {nullptr, 0, nullptr}}; /* ** If an input line begins with "." then invoke this routine to ** process that line. ** ** Return 1 on error, 2 to exit, and 0 otherwise. */ int ShellState::DoMetaCommand(char *zLine) { int h = 1; int nArg = 0; int n, c; int rc = 0; char *azArg[52]; /* Parse the input line into tokens. */ while (zLine[h] && nArg < ArraySize(azArg) - 1) { while (IsSpace(zLine[h])) { h++; } if (zLine[h] == 0) break; if (zLine[h] == '\'' || zLine[h] == '"') { int delim = zLine[h++]; azArg[nArg++] = &zLine[h]; while (zLine[h] && zLine[h] != delim) { if (zLine[h] == '\\' && delim == '"' && zLine[h + 1] != 0) h++; h++; } if (zLine[h] == delim) { zLine[h++] = 0; } if (delim == '"') resolve_backslashes(azArg[nArg - 1]); } else { azArg[nArg++] = &zLine[h]; while (zLine[h] && !IsSpace(zLine[h])) { h++; } if (zLine[h]) zLine[h++] = 0; resolve_backslashes(azArg[nArg - 1]); } } azArg[nArg] = 0; /* Process the input line. */ if (nArg == 0) { return 0; /* no tokens, no error */ } n = StringLength(azArg[0]); c = azArg[0][0]; ClearTempFile(); bool found_argument = false; for (idx_t command_idx = 0; metadata_commands[command_idx].command; command_idx++) { auto &command = metadata_commands[command_idx]; idx_t match_size = command.match_size ? command.match_size : n; if (n < int(match_size) || c != *command.command || strncmp(azArg[0], command.command, n) != 0) { continue; } found_argument = true; MetadataResult result = MetadataResult::PRINT_USAGE; if (!command.callback) { raw_printf(stderr, "Command \"%s\" is unsupported in the current version of the CLI\n", command.command); result = MetadataResult::FAIL; } else if (command.argument_count == 0 || int(command.argument_count) == nArg) { result = command.callback(*this, (const char **)azArg, nArg); } if (result == MetadataResult::PRINT_USAGE) { raw_printf(stderr, "Usage: .%s %s\n", command.command, command.usage); result = MetadataResult::FAIL; } rc = int(result); break; } if (found_argument) { } else { #ifdef HAVE_LINENOISE const char *error = NULL; if (linenoiseParseOption((const char **)azArg, nArg, &error)) { if (error) { PrintDatabaseError(error); rc = 1; } } else { #endif utf8_printf(stderr, "Error: unknown command or invalid arguments: " " \"%s\". Enter \".help\" for help\n", azArg[0]); rc = 1; #ifdef HAVE_LINENOISE } #endif } if (outCount) { outCount--; if (outCount == 0) ResetOutput(); } return rc; } /* ** Return TRUE if a semicolon occurs anywhere in the first N characters ** of string z[]. */ static bool line_contains_semicolon(const char *z, idx_t N) { for (idx_t i = 0; i < N; i++) { if (z[i] == ';') { return true; } } return false; } /* ** Test to see if a line consists entirely of whitespace. */ static bool _all_whitespace(const char *z) { for (; *z; z++) { if (IsSpace(z[0])) { continue; } if (*z == '/' && z[1] == '*') { z += 2; while (*z && (*z != '*' || z[1] != '/')) { z++; } if (*z == 0) { return false; } z++; continue; } if (*z == '-' && z[1] == '-') { z += 2; while (*z && *z != '\n') { z++; } if (*z == 0) return true; continue; } return false; } return true; } /* ** We need a default sqlite3_complete() implementation to use in case ** the shell is compiled with SQLITE_OMIT_COMPLETE. The default assumes ** any arbitrary text is a complete SQL statement. This is not very ** user-friendly, but it does seem to work. */ #ifdef SQLITE_OMIT_COMPLETE #define sqlite3_complete(x) 1 #endif /* ** Run a single line of SQL. Return the number of errors. */ int ShellState::RunOneSqlLine(InputMode mode, char *zSql) { int rc; char *zErrMsg = nullptr; OpenDB(0); if (ShellHasFlag(SHFLG_Backslash)) { resolve_backslashes(zSql); } #ifndef SHELL_USE_LOCAL_GETLINE if (mode == InputMode::STANDARD && zSql && *zSql && *zSql != '\3') { shell_add_history(zSql); } #endif BEGIN_TIMER; rc = ExecuteSQL(zSql, &zErrMsg); END_TIMER; if (rc || zErrMsg) { if (zErrMsg != 0) { PrintDatabaseError(zErrMsg); sqlite3_free(zErrMsg); zErrMsg = 0; } else { ShellDatabaseError(db); } return 1; } else if (ShellHasFlag(SHFLG_CountChanges)) { raw_printf(out, "changes: %3lld total_changes: %lld\n", sqlite3_changes64(db), sqlite3_total_changes64(db)); } return 0; } /* ** Read input from *in and process it. If *in==0 then input ** is interactive - the user is typing it it. Otherwise, input ** is coming from a file or device. A prompt is issued and history ** is saved only if input is interactive. An interrupt signal will ** cause this routine to exit immediately, unless input is interactive. ** ** Return the number of errors. */ int ShellState::ProcessInput(InputMode mode) { char *zLine = nullptr; /* A single input line */ char *zSql = nullptr; /* Accumulated SQL text */ idx_t nLine; /* Length of current line */ idx_t nSql = 0; /* Bytes of zSql[] used */ idx_t nAlloc = 0; /* Allocated zSql[] space */ idx_t nSqlPrior = 0; /* Bytes of zSql[] used by prior line */ int rc; /* Error code */ idx_t errCnt = 0; /* Number of errors seen */ idx_t numCtrlC = 0; lineno = 0; while (errCnt == 0 || !bail_on_error || (!in && stdin_is_interactive)) { fflush(out); zLine = one_input_line(in, zLine, nSql > 0); if (!zLine) { /* End of input */ if (!in && stdin_is_interactive) { printf("\n"); } break; } if (*zLine == '\3') { // ctrl c: reset sql statement if (nSql == 0 && zLine[1] == '\0' && stdin_is_interactive) { // if in interactive mode and we press ctrl c twice // on an empty line, we exit numCtrlC++; if (numCtrlC >= 2) { break; } } nSql = 0; continue; } else { numCtrlC = 0; } if (seenInterrupt) { if (in) { break; } seenInterrupt = 0; } lineno++; if (nSql == 0 && _all_whitespace(zLine)) { if (ShellHasFlag(SHFLG_Echo)) { printf("%s\n", zLine); } continue; } if (zLine && (zLine[0] == '.' || zLine[0] == '#') && nSql == 0) { if (ShellHasFlag(SHFLG_Echo)) { printf("%s\n", zLine); } if (zLine[0] == '.') { #ifndef SHELL_USE_LOCAL_GETLINE if (mode == InputMode::STANDARD && zLine && *zLine && *zLine != '\3') shell_add_history(zLine); #endif rc = DoMetaCommand(zLine); if (rc == 2) { /* exit requested */ break; } else if (rc) { errCnt++; } } continue; } nLine = StringLength(zLine); if (nSql + nLine + 2 >= nAlloc) { nAlloc = nSql + nLine + 100; zSql = (char *)realloc(zSql, nAlloc); if (!zSql) { shell_out_of_memory(); } } nSqlPrior = nSql; if (nSql == 0) { int i; for (i = 0; zLine[i] && IsSpace(zLine[i]); i++) { } assert(nAlloc > 0 && zSql); memcpy(zSql, zLine + i, nLine + 1 - i); nSql = nLine - i; } else { zSql[nSql++] = '\n'; memcpy(zSql + nSql, zLine, nLine + 1); nSql += nLine; } if (nSql && line_contains_semicolon(&zSql[nSqlPrior], nSql - nSqlPrior) && sqlite3_complete(zSql)) { errCnt += RunOneSqlLine(mode, zSql); nSql = 0; if (outCount) { ResetOutput(); outCount = 0; } else { ClearTempFile(); } } else if (nSql && _all_whitespace(zSql)) { if (ShellHasFlag(SHFLG_Echo)) { printf("%s\n", zSql); } nSql = 0; } } if (nSql && !_all_whitespace(zSql)) { errCnt += RunOneSqlLine(mode, zSql); } free(zSql); free(zLine); return errCnt > 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 = nullptr; if (clearFlag) { free(home_dir); home_dir = nullptr; return nullptr; } if (home_dir) { return home_dir; } #if !defined(_WIN32) && !defined(WIN32) && !defined(_WIN32_WCE) && !defined(__RTP__) && !defined(_WRS_KERNEL) { 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; idx_t n; zDrive = getenv("HOMEDRIVE"); zPath = getenv("HOMEPATH"); if (zDrive && zPath) { n = ShellState::StringLength(zDrive) + ShellState::StringLength(zPath) + 1; home_dir = (char *)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 = ShellState::StringLength(home_dir) + 1; char *z = (char *)malloc(n); if (z) { memcpy(z, home_dir, n); } home_dir = z; } return home_dir; } string ShellState::GetDefaultDuckDBRC() { auto home_dir = find_home_dir(0); if (!home_dir) { return string(); } return string(home_dir) + "/.duckdbrc"; } /* ** Read input from the file given by sqliterc_override. Or if that ** parameter is NULL, take input from ~/.duckdbrc ** ** Returns true if successful, false otherwise. */ bool ShellState::ProcessFile(const string &file, bool is_duckdb_rc) { FILE *inSaved = in; int savedLineno = lineno; int rc = 0; in = fopen(file.c_str(), "rb"); if (in) { if (stdin_is_interactive && is_duckdb_rc) { utf8_printf(stderr, "-- Loading resources from %s\n", file.c_str()); } rc = ProcessInput(InputMode::FILE); fclose(in); } else if (!is_duckdb_rc) { utf8_printf(stderr, "Failed to read file \"%s\"\n", file.c_str()); rc = 1; } in = inSaved; lineno = savedLineno; return rc == 0; } bool ShellState::ProcessDuckDBRC(const char *file) { string path; if (!file) { // use default .duckdbrc path path = ShellState::GetDefaultDuckDBRC(); if (path.empty()) { // could not find home directory - return raw_printf(stderr, "-- warning: cannot find home directory;" " cannot read ~/.duckdbrc\n"); return true; } file = path.c_str(); } return ProcessFile(file, true); } /* ** Show available command line options */ static const char zOptions[] = " -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" " -c COMMAND run \"COMMAND\" and exit\n" " -csv set output mode to 'csv'\n" " -echo print commands before execution\n" " -f FILENAME read/process named file and exit\n" " -init FILENAME read/process named file\n" " -[no]header turn headers on or off\n" " -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" " -markdown set output mode to 'markdown'\n" " -newline SEP set output row separator. Default: '\\n'\n" " -no-stdin exit after processing options instead of reading stdin\n" " -nullvalue TEXT set text string for NULL values. Default 'NULL'\n" " -quote set output mode to 'quote'\n" " -readonly open the database read-only\n" " -s COMMAND run \"COMMAND\" and exit\n" " -safe enable safe-mode\n" " -separator SEP set output column separator. Default: '|'\n" " -storage-version V database storage compatibility version to use. Default: 'v0.10.0'\n" " -table set output mode to 'table'\n" " -ui launches a web interface using the ui extension (configurable with .ui_command)\n" " -unredacted allow printing unredacted secrets\n" " -unsigned allow loading of unsigned extensions\n" " -version show DuckDB version\n"; static void usage(int showDetail) { utf8_printf(stdout, "Usage: %s [OPTIONS] FILENAME [SQL]\n" "FILENAME is the name of a DuckDB database. A new database is created\n" "if the file does not previously exist.\n", program_name); if (showDetail) { utf8_printf(stdout, "OPTIONS include:\n%s", zOptions); } else { raw_printf(stdout, "Use the -help option for additional information\n"); } exit(0); } /* ** 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(stdout, "WARNING: attempt to configure SQLite after" " initialization.\n"); } } /* ** Initialize the state information in data */ static void main_init(ShellState *data) { data->normalMode = data->cMode = data->mode = RenderMode::DUCKBOX; data->max_rows = 40; data->colSeparator = SEP_Column; data->rowSeparator = SEP_Row; data->showHeader = true; data->shellFlgs = SHFLG_Lookaside; verify_uninitialized(); sqlite3_config(SQLITE_CONFIG_URI, 1); sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data); sqlite3_config(SQLITE_CONFIG_MULTITHREAD); sqlite3_snprintf(sizeof(mainPrompt), mainPrompt, "D "); sqlite3_snprintf(sizeof(continuePrompt), continuePrompt, "· "); sqlite3_snprintf(sizeof(continuePromptSelected), continuePromptSelected, "‣ "); #ifdef HAVE_LINENOISE linenoiseSetPrompt(continuePrompt, continuePromptSelected); #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(stderr, "%s: Error: missing argument to %s\n", argv[0], argv[argc - 1]); exit(1); } return argv[i]; } #ifndef SQLITE_SHELL_IS_UTF8 #if (defined(_WIN32) || defined(WIN32)) && defined(_MSC_VER) #define SQLITE_SHELL_IS_UTF8 (0) #else #define SQLITE_SHELL_IS_UTF8 (1) #endif #endif #if SQLITE_SHELL_IS_UTF8 int SQLITE_CDECL main(int argc, char **argv) { #else int SQLITE_CDECL wmain(int argc, wchar_t **wargv) { char **argv; #endif char *zErrMsg = nullptr; ShellState data; const char *zInitFile = nullptr; int i; int rc = 0; bool warnInmemoryDb = false; bool readStdin = true; int nCmd = 0; char **azCmd = nullptr; #if !SQLITE_SHELL_IS_UTF8 char **argvToFree = 0; int argcToFree = 0; #endif setBinaryMode(stdin, 0); setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */ stdin_is_interactive = isatty(0); stdout_is_console = isatty(1); stderr_is_console = isatty(2); #if USE_SYSTEM_SQLITE + 0 != 1 if (strncmp(sqlite3_sourceid(), SQLITE_SOURCE_ID, 60) != 0) { utf8_printf(stderr, "SQLite header and source version mismatch\n%s\n%s\n", sqlite3_sourceid(), SQLITE_SOURCE_ID); exit(1); } #endif main_init(&data); /* On Windows, we must translate command-line arguments into UTF-8. ** The SQLite memory allocator subsystem has to be enabled in order to ** do this. But we want to run an sqlite3_shutdown() afterwards so that ** subsequent sqlite3_config() calls will work. So copy all results into ** memory that does not come from the SQLite memory allocator. */ #if !SQLITE_SHELL_IS_UTF8 sqlite3_initialize(); argvToFree = (char **)malloc(sizeof(argv[0]) * argc * 2); argcToFree = argc; argv = argvToFree + argc; if (argv == 0) shell_out_of_memory(); for (i = 0; i < argc; i++) { char *z = sqlite3_win32_unicode_to_utf8(wargv[i]); int n; if (z == 0) shell_out_of_memory(); n = (int)strlen(z); argv[i] = (char *)malloc(n + 1); if (argv[i] == 0) shell_out_of_memory(); memcpy(argv[i], z, n + 1); argvToFree[i] = argv[i]; sqlite3_free(z); } sqlite3_shutdown(); #endif assert(argc >= 1 && argv && argv[0]); program_name = argv[0]; /* Make sure we have a valid signal handler early, before anything ** else is done. */ #ifdef SIGINT signal(SIGINT, interrupt_handler); #elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE) SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE); #endif #ifdef SQLITE_SHELL_DBNAME_PROC { /* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name ** of a C-function that will provide the name of the database file. Use ** this compile-time option to embed this shell program in larger ** applications. */ extern void SQLITE_SHELL_DBNAME_PROC(const char **); SQLITE_SHELL_DBNAME_PROC(&data.zDbFilename); warnInmemoryDb = false; } #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. */ verify_uninitialized(); for (i = 1; i < argc; i++) { char *z; z = argv[i]; if (z[0] != '-') { if (data.zDbFilename.empty()) { data.zDbFilename = z; } else { /* Excesss arguments are interpreted as SQL (or dot-commands) and ** mean that nothing is read from stdin */ readStdin = false; nCmd++; azCmd = (char **)realloc(azCmd, sizeof(azCmd[0]) * nCmd); if (azCmd == 0) { shell_out_of_memory(); } azCmd[nCmd - 1] = z; } } if (z[1] == '-') { z++; } if (strcmp(z, "-separator") == 0 || strcmp(z, "-nullvalue") == 0 || strcmp(z, "-newline") == 0 || strcmp(z, "-cmd") == 0) { (void)cmdline_option_value(argc, argv, ++i); } else if (strcmp(z, "-c") == 0 || strcmp(z, "-s") == 0 || strcmp(z, "-f") == 0) { (void)cmdline_option_value(argc, argv, ++i); stdin_is_interactive = false; } else if (strcmp(z, "-init") == 0) { zInitFile = cmdline_option_value(argc, argv, ++i); } else if (strcmp(z, "-batch") == 0) { /* Need to check for batch mode here to so we can avoid printing ** informational messages (like from process_sqliterc) before ** we do the actual processing of arguments later in a second pass. */ stdin_is_interactive = false; } else if (strcmp(z, "-readonly") == 0) { data.openMode = SHELL_OPEN_READONLY; } else if (strcmp(z, "-unredacted") == 0) { data.openFlags |= DUCKDB_UNREDACTED_SECRETS; } else if (strcmp(z, "-unsigned") == 0) { data.openFlags |= DUCKDB_UNSIGNED_EXTENSIONS; } else if (strcmp(z, "-safe") == 0) { safe_mode = true; } else if (strcmp(z, "-storage_version") == 0 || strcmp(z, "-storage-version") == 0) { auto storage_version = string(cmdline_option_value(argc, argv, ++i)); if (storage_version != "latest") { utf8_printf( stderr, "%s: Error: unknown argument (%s) for '-storage-version', only 'latest' is supported currently\n", program_name, storage_version.c_str()); } else { data.openFlags |= DUCKDB_LATEST_STORAGE_VERSION; } } else if (strcmp(z, "-bail") == 0) { bail_on_error = true; } } verify_uninitialized(); #ifdef SQLITE_SHELL_INIT_PROC { /* If the SQLITE_SHELL_INIT_PROC macro is defined, then it is the name ** of a C-function that will perform initialization actions on SQLite that ** occur just before or after sqlite3_initialize(). Use this compile-time ** option to embed this shell program in larger applications. */ extern void SQLITE_SHELL_INIT_PROC(void); SQLITE_SHELL_INIT_PROC(); } #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 (data.zDbFilename.empty()) { #ifndef SQLITE_OMIT_MEMORYDB data.zDbFilename = ":memory:"; warnInmemoryDb = argc == 1; #else utf8_printf(stderr, "%s: Error: no database filename specified\n", program_name); return 1; #endif } data.out = stdout; /* Go ahead and open the database file if it already exists. If the ** file does not exist, delay opening it. This prevents empty database ** files from being created if a user mistypes the database name argument ** to the sqlite command-line tool. */ if (access(data.zDbFilename.c_str(), 0) == 0) { data.OpenDB(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. */ if (!data.ProcessDuckDBRC(zInitFile) && bail_on_error) { return 1; } /* Make a second pass through the command-line argument and set ** options. This second pass is delayed until after the initialization ** file is processed so that the command-line arguments will override ** settings in the initialization file. */ for (i = 1; i < argc; i++) { char *z = argv[i]; if (z[0] != '-') { continue; } if (z[1] == '-') { z++; } if (strcmp(z, "-init") == 0) { i++; } else if (strcmp(z, "-html") == 0) { data.mode = RenderMode::HTML; } else if (strcmp(z, "-list") == 0) { data.mode = RenderMode::LIST; } else if (strcmp(z, "-quote") == 0) { data.mode = RenderMode::QUOTE; } else if (strcmp(z, "-line") == 0) { data.mode = RenderMode::LINE; } else if (strcmp(z, "-column") == 0) { data.mode = RenderMode::COLUMN; } else if (strcmp(z, "-json") == 0) { data.mode = RenderMode::JSON; } else if (strcmp(z, "-jsonlines") == 0) { data.mode = RenderMode::JSONLINES; } else if (strcmp(z, "-markdown") == 0) { data.mode = RenderMode::MARKDOWN; } else if (strcmp(z, "-table") == 0) { data.mode = RenderMode::TABLE; } else if (strcmp(z, "-box") == 0) { data.mode = RenderMode::BOX; } else if (strcmp(z, "-latex") == 0) { data.mode = RenderMode::LATEX; } else if (strcmp(z, "-csv") == 0) { data.mode = RenderMode::CSV; data.colSeparator = ","; } else if (strcmp(z, "-readonly") == 0) { data.openMode = SHELL_OPEN_READONLY; } else if (strcmp(z, "-ascii") == 0) { data.mode = RenderMode::ASCII; data.colSeparator = SEP_Unit; data.rowSeparator = SEP_Record; } else if (strcmp(z, "-separator") == 0) { data.colSeparator = cmdline_option_value(argc, argv, ++i); } else if (strcmp(z, "-newline") == 0) { data.rowSeparator = cmdline_option_value(argc, argv, ++i); } else if (strcmp(z, "-nullvalue") == 0) { data.nullValue = cmdline_option_value(argc, argv, ++i); } else if (strcmp(z, "-header") == 0) { data.showHeader = 1; } else if (strcmp(z, "-noheader") == 0) { data.showHeader = 0; } else if (strcmp(z, "-echo") == 0) { data.ShellSetFlag(SHFLG_Echo); } else if (strcmp(z, "-unsigned") == 0) { data.openFlags |= DUCKDB_UNSIGNED_EXTENSIONS; } else if (strcmp(z, "-unredacted") == 0) { data.openFlags |= DUCKDB_UNREDACTED_SECRETS; } else if (strcmp(z, "-bail") == 0) { bail_on_error = true; } else if (strcmp(z, "-version") == 0) { printf("%s (%s) %s\n", duckdb::DuckDB::LibraryVersion(), duckdb::DuckDB::ReleaseCodename(), duckdb::DuckDB::SourceID()); free(azCmd); return 0; } else if (strcmp(z, "-interactive") == 0) { stdin_is_interactive = true; } else if (strcmp(z, "-batch") == 0) { stdin_is_interactive = false; } else if (strcmp(z, "-help") == 0) { usage(1); } else if (strcmp(z, "-no-stdin") == 0) { readStdin = false; } else if (strcmp(z, "-f") == 0) { readStdin = false; if (i == argc - 1) { break; } auto old_bail = bail_on_error; bail_on_error = true; z = cmdline_option_value(argc, argv, ++i); if (!data.ProcessFile(string(z))) { free(azCmd); return 1; } bail_on_error = old_bail; } else if (strcmp(z, "-cmd") == 0 || strcmp(z, "-c") == 0 || strcmp(z, "-s") == 0) { if (strcmp(z, "-c") == 0 || strcmp(z, "-s") == 0) { readStdin = false; } /* Run commands that follow -cmd first and separately from commands ** that simply appear on the command-line. This seems goofy. It would ** be better if all commands ran in the order that they appear. But ** we retain the goofy behavior for historical compatibility. */ if (i == argc - 1) { break; } // Always bail if -c or -s fail bool bail = bail_on_error || !strcmp(z, "-c") || !strcmp(z, "-s"); z = cmdline_option_value(argc, argv, ++i); rc = data.RunInitialCommand(z, bail); if (rc != 0) { free(azCmd); return rc; } } else if (strcmp(z, "-safe") == 0) { // safe mode has been set before } else if (strcmp(z, "-ui") == 0) { // run the UI command rc = data.RunInitialCommand((char *)data.ui_command.c_str(), true); if (rc != 0) { free(azCmd); return rc; } } else if (strcmp(z, "-storage_version") == 0) { // already processed on start-up } else { utf8_printf(stderr, "%s: Error: unknown option: %s\n", program_name, z); raw_printf(stderr, "Use -help for a list of options.\n"); free(azCmd); return 1; } data.cMode = data.mode; } if (!readStdin) { /* Run all arguments that do not begin with '-' as if they were separate ** command-line inputs, except for the argToSkip argument which contains ** the database filename. */ for (i = 0; i < nCmd; i++) { if (azCmd[i][0] == '.') { rc = data.DoMetaCommand(azCmd[i]); if (rc) { free(azCmd); return rc == 2 ? 0 : rc; } } else { data.OpenDB(0); rc = data.ExecuteSQL(azCmd[i], &zErrMsg); if (zErrMsg != 0) { data.PrintDatabaseError(zErrMsg); sqlite3_free(zErrMsg); free(azCmd); return rc != 0 ? rc : 1; } else if (rc != 0) { utf8_printf(stderr, "Error: unable to process SQL: %s\n", azCmd[i]); free(azCmd); return rc; } } } free(azCmd); } else { /* Run commands received from standard input */ if (stdin_is_interactive) { char *zHome; char *zHistory; int nHistory; printf("DuckDB %s (%s) %.19s\n" /*extra-version-info*/ "Enter \".help\" for usage hints.\n", duckdb::DuckDB::LibraryVersion(), duckdb::DuckDB::ReleaseCodename(), duckdb::DuckDB::SourceID()); if (warnInmemoryDb) { printf("Connected to a "); ShellHighlight highlighter(data); highlighter.PrintText("transient in-memory database", PrintOutput::STDOUT, PrintColor::STANDARD, PrintIntensity::BOLD); printf(".\nUse \".open FILENAME\" to reopen on a " "persistent database.\n"); } zHistory = getenv("DUCKDB_HISTORY"); if (zHistory) { zHistory = strdup(zHistory); } else if ((zHome = find_home_dir(0)) != 0) { nHistory = ShellState::StringLength(zHome) + 20; if ((zHistory = (char *)malloc(nHistory)) != 0) { sqlite3_snprintf(nHistory, zHistory, "%s/.duckdb_history", zHome); } } if (zHistory) { shell_read_history(zHistory); } #if HAVE_READLINE || HAVE_EDITLINE rl_attempted_completion_function = readline_completion; #elif HAVE_LINENOISE linenoise_open_flags = data.openFlags; linenoiseSetCompletionCallback(linenoise_completion); #endif data.in = 0; rc = data.ProcessInput(InputMode::STANDARD); if (zHistory) { shell_stifle_history(2000); shell_write_history(zHistory); free(zHistory); } } else { data.in = stdin; rc = data.ProcessInput(InputMode::STANDARD); } } data.SetTableName(0); if (data.db) { close_db(data.db); } find_home_dir(1); data.ResetOutput(); data.doXdgOpen = 0; data.ClearTempFile(); #if !SQLITE_SHELL_IS_UTF8 for (i = 0; i < argcToFree; i++) free(argvToFree[i]); free(argvToFree); #endif return rc; }