From: larrybr Date: Tue, 15 Feb 2022 17:04:37 +0000 (+0000) Subject: Sync with trunk X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f7394cf9a9c837adb704df58183a9e8cd7e4b63e;p=thirdparty%2Fsqlite.git Sync with trunk FossilOrigin-Name: 2b4a295c58a908288840157c3e08289af8c1d287f72e5f14b7adfd98065bf241 --- f7394cf9a9c837adb704df58183a9e8cd7e4b63e diff --cc manifest index 33ec5c364d,220535e5db..9aeb5cef06 --- a/manifest +++ b/manifest @@@ -1,5 -1,5 +1,5 @@@ - C CLI\sextended\sparsing\sand\s.x\sfeatures\smostly\stested - D 2022-02-06T14:30:16.552 -C Improved\srendering\sof\sfloating\spoint\snumbers\swithout\sa\sfractional\spart\sin\n".dump"\soutput\sfrom\sthe\sCLI.\n[forum:/forumpost/550d877659f37cb2|Forum\spost\s550d877659f37cb2]. -D 2022-02-15T13:23:09.386 ++C Sync\swith\strunk ++D 2022-02-15T17:04:37.699 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@@ -548,16 -548,16 +548,16 @@@ F src/pcache1.c 54881292a9a5db202b2c0ac F src/pragma.c 7c024d690a3dc93f61830f11f900e4af2357f31d081b0c79099ca5e28919cba7 F src/pragma.h 87330ed2fbfa2a1274de93ca0ab850fba336189228cb256089202c3b52766fad F src/prepare.c a187dade741c1f09ae118fcbbf0302511807bfc0355880927d7152eb75b8260d - F src/printf.c ceadf2ee3ad2f4bb22d4557611b514f51be3ac0ce2b6ebd1d17aed26ce023dc9 + F src/printf.c 05d8dfd2018bc4fc3ddb8b37eb97ccef7abf985643fa1caebdcf2916ca90fa32 F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c - F src/resolve.c 868a88d47b11f4f6b8b413db72c9290c211972ee91316c1ece318846600ff725 + F src/resolve.c ea935b87d6fb36c78b70cdc7b28561dc8f33f2ef37048389549c7b5ef9b0ba5e F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/select.c 3baa9dd8cf240654773c7974e2bcce398ac9dd24419c36684156963defe43b35 - F src/shell.c.in 683c927a205999a3a13b268762de0309427d2f474ac1bd44d97c22b8064b1128 x - F src/sqlite.h.in 800d6509517d383d38188e71bb5f1802c7db097a1282af7bf5d6c9a6da5a15ed -F src/shell.c.in b5b44c2ebfd3942e60dbcc47b94a74337a482a5f1c3766fbfb9f578a605ecf50 ++F src/shell.c.in c87d57f80663efb5aff3935bda7e6264d25636f41108a58edca9b628d9bc3b02 + F src/sqlite.h.in 7047c4b60fa550264d6363bb1d983540e7828fb19d2d1e5aa43b52ca13144807 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h a95cb9ed106e3d39e2118e4dcc15a14faec3fa50d0093425083d340d9dfd96e6 - F src/sqliteInt.h b6619030ed13b2a8d49c0b5cb0525db1f727966b65ab1ec40b5f11102af7254d + F src/sqliteInt.h f8814239fb1f95056555e2d7fa475750e64681cac4221fb03610d1fde0b79d53 F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@@ -1387,15 -1388,14 +1388,15 @@@ F test/sharedA.test 49d87ec54ab640fbbc3 F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 - F test/shell1.test ce2f370886645f38fabdde44976c14a004400f166edea8fdd9741079b645fef6 - F test/shell2.test f00a0501c00583cbc46f7510e1d713366326b2b3e63d06d15937284171a8787c - F test/shell3.test c073c2adda6d1aabe4450024ade4d45607f34d2fb76c2551cc89e70871191a3c + F test/shell1.test b224e0793c5f48aa3749e65d8c64b93a30731bd206f2e41e6c5f1bee1bdb16c6 + F test/shell2.test 89e4b2db062d52baed75022227b462d085cff495809de1699652779d8e0257d6 -F test/shell3.test a50628ab1d78d90889d9d3f32fb2c084ee15674771e96afe954aaa0accd1de3c -F test/shell4.test 8f6c0fce4abed19a8a7f7262517149812a04caa905d01bdc8f5e92573504b759 ++F test/shell3.test 4ddea2bd182e7e03249911b23ae249e7cb8a91cdc86e695198725affabe8ecd3 +F test/shell4.test 867e0675d7b096d6b93de534541e07c7f5ffafa3e6612695ecf55180802e1115 - F test/shell5.test b85069bfcf3159b225228629ab2c3e69aa923d098fea8ea074b5dcd743522e2c + F test/shell5.test 3be444397eb1e91619ce289a6216a8df9ac690cc45d5e9595f60e750a944161f F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3 F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f F test/shell8.test 388471d16e4de767333107e30653983f186232c0e863f4490bb230419e830aae - F test/shell9.test 7fbfb85a2a9c2ea29d96cfa9ae2a116ecba7b0261ee366ed0d650c4c9054ba5c ++F test/shell9.test b6f07789fef57b5d194a3b2b8aba8292d363f23273cdc4541de7626ea659bc91 F test/shmlock.test 3dbf017d34ab0c60abe6a44e447d3552154bd0c87b41eaf5ceacd408dd13fda5 F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5 @@@ -1944,8 -1944,8 +1945,8 @@@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a9 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 - P 2e72dc4621bcccd6ef4f01d059ff052bc39b8ea122b006d842be6102736460f4 - R 25fb77e100f746489d50fee668ec40ea -P d74ec88c2f9b9e056988add8322186750229e14a609d1a6969ba393a5b8c5174 -R d44a4dfcecd9d59ae3b06dca8b590f27 -U drh -Z 28d22f7919d651ae0bfc81a1c20dfd1c ++P 9c664984fd61d5858b436952d876bf3560333ee0edd8e1956cfe74cf9649511d 9edaeed56f2282fd4da935454178c38ab49d259aed96d4e720aae09050a53006 ++R 241a1161ccd2087a62b9ac8bd7212d06 +U larrybr - Z 7b3a3cb6deab772402e5b78d5f0789c5 ++Z bc8dcf303171aacc369bf09b47a22c1e # Remove this line to create a well-formed Fossil manifest. diff --cc manifest.uuid index 80ab8ac2b8,cf24b67f55..92ab286c2c --- a/manifest.uuid +++ b/manifest.uuid @@@ -1,1 -1,1 +1,1 @@@ - 9c664984fd61d5858b436952d876bf3560333ee0edd8e1956cfe74cf9649511d -9edaeed56f2282fd4da935454178c38ab49d259aed96d4e720aae09050a53006 ++2b4a295c58a908288840157c3e08289af8c1d287f72e5f14b7adfd98065bf241 diff --cc src/shell.c.in index feee1211c6,3ff1f62c72..35b5d75c8f mode 100755,100644..100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@@ -17,13 -17,9 +17,13 @@@ #define _CRT_SECURE_NO_WARNINGS #endif +/* Define stringification of a bare word (or anything without commas.) */ +# define SHELL_STRINGIFY_(f) #f +# define SHELL_STRINGIFY(f) SHELL_STRINGIFY_(f) + /* ** Optionally #include a user-defined header, whereby compilation options --** may be set prior to where they take effect, but after platform setup. ++** may be set prior to where they take effect, but after platform setup. ** If SQLITE_CUSTOM_INCLUDE=? is defined, its value names the #include ** file. Note that this macro has a like effect on sqlite3.c compilation. */ @@@ -1195,22 -1075,6 +1197,22 @@@ typedef struct ColModeOpts #define ColModeOpts_default { 60, 0, 0 } #define ColModeOpts_default_qbox { 60, 1, 0 } +/* +** Stored output mode state, for partial save and later restore. +** Returned by: +** outputModeSave *outputModeSave(ShellState *p, SaveModeWhat ws). - ** Accepted by: ++** Accepted by: +** outputModeRestore(ShellState *p, OutputModeSave *pSaved). +** See enum SaveWhatMode regarding what to save and restore. +** Also see outputModePush(...), outputModePushSome(...) and +** outputModePop(...) for usages spanning more than one call. +*/ +typedef struct OutputModeSave{ + u16 what; /* Set upon creation. See SaveWhatMode for values. */ + char itsValues[1]; /* This size is inaccurate unless nothing is saved. */ +} OutputModeSave; +#define MODE_STACK_MAX 3 /* How many levels of saved output mode to allow. */ + /* ** State information about the database connection is contained in an ** instance of the following structure. @@@ -1584,7 -1444,7 +1586,7 @@@ static void editFunc } sz = j; p[sz] = 0; -- } ++ } sqlite3_result_text64(context, (const char*)p, sz, sqlite3_free, SQLITE_UTF8); } @@@ -1599,97 -1459,19 +1601,97 @@@ edit_func_end #endif /* SQLITE_NOHAVE_SYSTEM */ /* -** Save or restore the current output mode +** Save or restore the current output mode, partially per spec. (OM_STATE) +*/ +typedef enum { + SWM_showHeader = 1, SWM_shellFlags = 2, SWM_mode = 4, SWM_cmOpts = 8, + SWM_colSeparator = 0x10, SWM_rowSeparator = 0x20, SWM_everything = 0x3F, + SWM_CountOf = 6 +} SaveWhatMode; + +/* This is available in most C89+ C compilers as offsetof(...), but since we + * cater to the most arcane C89-like compilers around, define it for sure: + */ +#define MEMBER_OFFSET(stype, member) ((size_t)&(((stype*)0)->member)) +#define MEMBER_SIZEOF(stype, member) (sizeof(((stype*)0)->member)) +static struct { + size_t offset; + size_t size; +} outputModeCopy[] = { +#define SS_MEMBER_COPY(mn) \ + { MEMBER_OFFSET(ShellState,mn), MEMBER_SIZEOF(ShellState,mn) } + SS_MEMBER_COPY(showHeader), SS_MEMBER_COPY(shellFlgs), + SS_MEMBER_COPY(mode), SS_MEMBER_COPY(cmOpts), + SS_MEMBER_COPY(colSeparator), SS_MEMBER_COPY(rowSeparator) +#undef SS_MEMBER_COPY +}; + +/* Allocate a buffer, copy requested output mode data to it, and return it. */ +static OutputModeSave *outputModeSave(ShellState *p, SaveWhatMode w){ + u16 what = (u16)w; + int i, nAlloc = sizeof(what)+1, mask = 1; + char *pSaved = 0, *pFill; + for( i=0; isizeof(what))? *((u16 *)pSaved) : 0; + int i, nAlloc = sizeof(what)+1, mask = 1; + char *pTake = (char *)pSaved + sizeof(what); + for( i=0; inSavedModesnSavedModes>=MODE_STACK_MAX ) return; + pOMS = outputModeSave(p, w); + shell_check_oom(pOMS); + p->pModeStack[p->nSavedModes++] = pOMS; +} static void outputModePush(ShellState *p){ - p->modePrior = p->mode; - p->priorShFlgs = p->shellFlgs; - memcpy(p->colSepPrior, p->colSeparator, sizeof(p->colSeparator)); - memcpy(p->rowSepPrior, p->rowSeparator, sizeof(p->rowSeparator)); + outputModePushSome(p, SWM_everything); } static void outputModePop(ShellState *p){ - p->mode = p->modePrior; - p->shellFlgs = p->priorShFlgs; - memcpy(p->colSeparator, p->colSepPrior, sizeof(p->colSeparator)); - memcpy(p->rowSeparator, p->rowSepPrior, sizeof(p->rowSeparator)); + OutputModeSave *pOMS; + assert(p->nSavedModes>0); /* Should not be here unless something pushed. */ + if( p->nSavedModes==0 ) return; + pOMS = p->pModeStack[--p->nSavedModes]; + assert(pOMS!=0); + p->pModeStack[p->nSavedModes] = 0; + outputModeRestore(p, pOMS); } /* @@@ -3416,7 -3151,7 +3430,7 @@@ static void bind_prepared_stmt(sqlite3 ** characters */ static void print_box_line(FILE *out, int N){ -- const char zDash[] = ++ const char zDash[] = BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24; const int nDash = sizeof(zDash) - 1; @@@ -3545,7 -3280,7 +3559,7 @@@ static char *translateForDisplayAndDup break; } zOut[j] = 0; -- return (char*)zOut; ++ return (char*)zOut; } /* Extract the value of the i-th current column for pStmt as an SQL literal @@@ -3899,8 -3634,8 +3913,8 @@@ static void exec_prepared_stmt ** caller to eventually free this buffer using sqlite3_free(). */ static int expertHandleSQL( -- ShellState *pState, -- const char *zSql, ++ ShellState *pState, ++ const char *zSql, char **pzErr ){ assert( pState->expert.pExpert ); @@@ -3910,7 -3645,7 +3924,7 @@@ /* ** This function is called either to silently clean up the object --** created by the ".expert" command (if bCancel==1), or to generate a ++** created by the ".expert" command (if bCancel==1), or to generate a ** report from it and then clean it up (if bCancel==0). ** ** If successful, SQLITE_OK is returned. Otherwise, an SQLite error @@@ -4174,9 -3903,10 +4188,9 @@@ static int shell_exec rc2 = sqlite3_finalize(pStmt); if( rc!=SQLITE_NOMEM ) rc = rc2; if( rc==SQLITE_OK ){ - zSql = zLeftover; - while( IsSpace(zSql[0]) ) zSql++; + zSql = skipWhite(zLeftover); }else if( pzErrMsg ){ - *pzErrMsg = save_err_msg(db, "stepping, %s (%d)", rc, 0); + *pzErrMsg = save_err_msg(db, "stepping", rc, 0); } /* clear saved stmt handle */ @@@ -4997,7 -4672,7 +5011,7 @@@ u8 deduceDatabaseType(const char *zName } } fclose(f); -- return rc; ++ return rc; } #ifndef SQLITE_OMIT_DESERIALIZE @@@ -5098,8 -4771,8 +5112,8 @@@ static unsigned char *readHexDb(ShellSt ** offset (4*) of the blob. */ static void shellInt32( -- sqlite3_context *context, -- int argc, ++ sqlite3_context *context, ++ int argc, sqlite3_value **argv ){ const unsigned char *pBlob; @@@ -5126,8 -4799,8 +5140,8 @@@ ** using "..." with internal double-quote characters doubled. */ static void shellIdQuote( -- sqlite3_context *context, -- int argc, ++ sqlite3_context *context, ++ int argc, sqlite3_value **argv ){ const char *zName = (const char*)sqlite3_value_text(argv[0]); @@@ -5142,8 -4815,8 +5156,8 @@@ ** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X. */ static void shellUSleepFunc( -- sqlite3_context *context, -- int argcUnused, ++ sqlite3_context *context, ++ int argcUnused, sqlite3_value **argv ){ int sleep = sqlite3_value_int(argv[0]); @@@ -5155,7 -4828,7 +5169,7 @@@ /* ** Scalar function "shell_escape_crnl" used by the .recover command. ** The argument passed to this function is the output of built-in --** function quote(). If the first character of the input is "'", ++** function quote(). If the first character of the input is "'", ** indicating that the value passed to quote() was a text value, ** then this function searches the input for "\n" and "\r" characters ** and adds a wrapper similar to the following: @@@ -5166,8 -4839,8 +5180,8 @@@ ** of the input is returned. */ static void shellEscapeCrnl( -- sqlite3_context *context, -- int argc, ++ sqlite3_context *context, ++ int argc, sqlite3_value **argv ){ const char *zText = (const char*)sqlite3_value_text(argv[0]); @@@ -5273,7 -4946,7 +5287,7 @@@ static void open_db(ShellState *p, int } switch( p->openMode ){ case SHELL_OPEN_APPENDVFS: { -- sqlite3_open_v2(zDbFilename, &p->db, ++ sqlite3_open_v2(zDbFilename, &p->db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, "apndvfs"); break; } @@@ -5392,7 -5065,7 +5406,7 @@@ void close_db(sqlite3 *db) if( rc ){ utf8_printf(stderr, "Error: sqlite3_close() returns %d: %s\n", rc, sqlite3_errmsg(db)); -- } ++ } } #if HAVE_READLINE || HAVE_EDITLINE @@@ -6654,16 -6303,16 +6668,16 @@@ static int lintDotCommand #if !defined SQLITE_OMIT_VIRTUALTABLE static void shellPrepare( -- sqlite3 *db, -- int *pRc, -- const char *zSql, ++ sqlite3 *db, ++ int *pRc, ++ const char *zSql, sqlite3_stmt **ppStmt ){ *ppStmt = 0; if( *pRc==SQLITE_OK ){ int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0); if( rc!=SQLITE_OK ){ -- raw_printf(stderr, "sql error: %s (%d)\n", ++ raw_printf(stderr, "sql error: %s (%d)\n", sqlite3_errmsg(db), sqlite3_errcode(db) ); *pRc = rc; @@@ -6679,10 -6328,10 +6693,10 @@@ ** nuisance compiler warnings about "defined but not used". */ void shellPreparePrintf( -- sqlite3 *db, -- int *pRc, ++ sqlite3 *db, ++ int *pRc, sqlite3_stmt **ppStmt, -- const char *zFmt, ++ const char *zFmt, ... ){ *ppStmt = 0; @@@ -6708,7 -6357,7 +6722,7 @@@ ** nuisance compiler warnings about "defined but not used". */ void shellFinalize( -- int *pRc, ++ int *pRc, sqlite3_stmt *pStmt ){ if( pStmt ){ @@@ -6730,7 -6379,7 +6744,7 @@@ ** nuisance compiler warnings about "defined but not used". */ void shellReset( -- int *pRc, ++ int *pRc, sqlite3_stmt *pStmt ){ int rc = sqlite3_reset(pStmt); @@@ -6778,7 -6427,7 +6792,7 @@@ static int arUsage(FILE *f) } /* --** Print an error message for the .ar command to stderr and return ++** Print an error message for the .ar command to stderr and return ** SQLITE_ERROR. */ static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){ @@@ -6859,7 -6508,7 +6873,7 @@@ static int arProcessSwitch(ArCommand *p /* ** Parse the command line for an ".ar" command. The results are written into ** structure (*pAr). SQLITE_OK is returned if the command line is parsed --** successfully, otherwise an error message is written to stderr and ++** successfully, otherwise an error message is written to stderr and ** SQLITE_ERROR returned. */ static int arParseCommand( @@@ -7055,7 -6704,7 +7069,7 @@@ static int arCheckEntries(ArCommand *pA ** when pAr->bGlob is false and GLOB match when pAr->bGlob is true. */ static void arWhereClause( -- int *pRc, ++ int *pRc, ArCommand *pAr, char **pzWhere /* OUT: New WHERE clause */ ){ @@@ -7070,7 -6719,7 +7084,7 @@@ for(i=0; inArg; i++){ const char *z = pAr->azArg[i]; zWhere = sqlite3_mprintf( -- "%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'", ++ "%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'", zWhere, zSep, zSameOp, z, strlen30(z)+1, zSameOp, z ); if( zWhere==0 ){ @@@ -7085,10 -6734,10 +7099,10 @@@ } /* --** Implementation of .ar "lisT" command. ++** Implementation of .ar "lisT" command. */ static int arListCommand(ArCommand *pAr){ -- const char *zSql = "SELECT %s FROM %s WHERE %s"; ++ const char *zSql = "SELECT %s FROM %s WHERE %s"; const char *azCols[] = { "name", "lsmode(mode), sz, datetime(mtime, 'unixepoch'), name" @@@ -7110,7 -6759,7 +7124,7 @@@ if( pAr->bVerbose ){ utf8_printf(pAr->p->out, "%s % 10d %s %s\n", sqlite3_column_text(pSql, 0), -- sqlite3_column_int(pSql, 1), ++ sqlite3_column_int(pSql, 1), sqlite3_column_text(pSql, 2), sqlite3_column_text(pSql, 3) ); @@@ -7167,17 -6816,17 +7181,17 @@@ static int arRemoveCommand(ArCommand *p } /* --** Implementation of .ar "eXtract" command. ++** Implementation of .ar "eXtract" command. */ static int arExtractCommand(ArCommand *pAr){ -- const char *zSql1 = ++ const char *zSql1 = "SELECT " " ($dir || name)," " writefile(($dir || name), %s, mode, mtime) " "FROM %s WHERE (%s) AND (data IS NULL OR $dirOnly = 0)" " AND name NOT GLOB '*..[/\\]*'"; -- const char *azExtraArg[] = { ++ const char *azExtraArg[] = { "sqlar_uncompress(data, sz)", "data" }; @@@ -7203,7 -6852,7 +7217,7 @@@ if( zDir==0 ) rc = SQLITE_NOMEM; } -- shellPreparePrintf(pAr->db, &rc, &pSql, zSql1, ++ shellPreparePrintf(pAr->db, &rc, &pSql, zSql1, azExtraArg[pAr->bZip], pAr->zSrcTable, zWhere ); @@@ -7281,7 -6930,7 +7295,7 @@@ static int arCreateOrUpdateCommand int bUpdate, /* true for a --create. */ int bOnlyIfChanged /* Only update if file has changed */ ){ -- const char *zCreate = ++ const char *zCreate = "CREATE TABLE IF NOT EXISTS sqlar(\n" " name TEXT PRIMARY KEY, -- name of the file\n" " mode INT, -- access permissions\n" @@@ -7323,7 -6972,7 +7337,7 @@@ arExecSql(pAr, "PRAGMA page_size=512"); rc = arExecSql(pAr, "SAVEPOINT ar;"); if( rc!=SQLITE_OK ) return rc; -- zTemp[0] = 0; ++ zTemp[0] = 0; if( pAr->bZip ){ /* Initialize the zipfile virtual table, if necessary */ if( pAr->zFile ){ @@@ -7417,7 -7066,7 +7431,7 @@@ static int arDotCommand }else if( cmd.zFile ){ int flags; if( cmd.bAppend ) eDbType = SHELL_OPEN_APPENDVFS; -- if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_INSERT ++ if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_INSERT || cmd.eCmd==AR_CMD_REMOVE || cmd.eCmd==AR_CMD_UPDATE ){ flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE; }else{ @@@ -7428,10 -7077,10 +7442,10 @@@ utf8_printf(pState->out, "-- open database '%s'%s\n", cmd.zFile, eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : ""); } -- rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, ++ rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0); if( rc!=SQLITE_OK ){ -- utf8_printf(stderr, "cannot open file: %s (%s)\n", ++ utf8_printf(stderr, "cannot open file: %s (%s)\n", cmd.zFile, sqlite3_errmsg(cmd.db) ); goto end_ar_command; @@@ -7557,12 -7206,12 +7571,12 @@@ static void *shellMalloc(int *pRc, sqli /* ** If *pRc is not SQLITE_OK when this function is called, it is a no-op. ** Otherwise, zFmt is treated as a printf() style string. The result of --** formatting it along with any trailing arguments is written into a ++** formatting it along with any trailing arguments is written into a ** buffer obtained from sqlite3_malloc(), and pointer to which is returned. ** It is the responsibility of the caller to eventually free this buffer ** using a call to sqlite3_free(). --** --** If an OOM error occurs, (*pRc) is set to SQLITE_NOMEM and a NULL ++** ++** If an OOM error occurs, (*pRc) is set to SQLITE_NOMEM and a NULL ** pointer returned. */ static char *shellMPrintf(int *pRc, const char *zFmt, ...){ @@@ -7621,7 -7271,7 +7636,7 @@@ static RecoverTable *recoverNewTable int *pRc, /* IN/OUT: Error code */ const char *zName, /* Name of table */ const char *zSql, /* CREATE TABLE statement */ -- int bIntkey, ++ int bIntkey, int nCol ){ sqlite3 *dbtmp = 0; /* sqlite3 handle for testing CREATE TABLE */ @@@ -7633,7 -7283,7 +7648,7 @@@ int nSqlCol = 0; int bSqlIntkey = 0; sqlite3_stmt *pStmt = 0; -- ++ rc = sqlite3_open("", &dbtmp); if( rc==SQLITE_OK ){ sqlite3_create_function(dbtmp, "shell_idquote", 1, SQLITE_UTF8, 0, @@@ -7649,7 -7299,7 +7664,7 @@@ goto finished; } } -- shellPreparePrintf(dbtmp, &rc, &pStmt, ++ shellPreparePrintf(dbtmp, &rc, &pStmt, "SELECT count(*) FROM pragma_table_info(%Q)", zName ); if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ @@@ -7661,7 -7311,7 +7676,7 @@@ goto finished; } -- shellPreparePrintf(dbtmp, &rc, &pStmt, ++ shellPreparePrintf(dbtmp, &rc, &pStmt, "SELECT (" " SELECT substr(data,1,1)==X'0D' FROM sqlite_dbpage WHERE pgno=rootpage" ") FROM sqlite_schema WHERE name = %Q", zName @@@ -7683,7 -7333,7 +7698,7 @@@ ** leave zPk as "_rowid_" and pTab->iPk at -2. */ pTab->iPk = -2; if( bIntkey ){ -- shellPreparePrintf(dbtmp, &rc, &pPkFinder, ++ shellPreparePrintf(dbtmp, &rc, &pPkFinder, "SELECT cid, name FROM pragma_table_info(%Q) " " WHERE pk=1 AND type='integer' COLLATE nocase" " AND NOT EXISTS (SELECT cid FROM pragma_table_info(%Q) WHERE pk=2)" @@@ -7706,11 -7356,11 +7721,11 @@@ pTab->azlCol[0] = shellMPrintf(&rc, ""); } i = 1; -- shellPreparePrintf(dbtmp, &rc, &pStmt, ++ shellPreparePrintf(dbtmp, &rc, &pStmt, "SELECT %Q || group_concat(shell_idquote(name), ', ') " " FILTER (WHERE cid!=%d) OVER (ORDER BY %s cid) " -- "FROM pragma_table_info(%Q)", -- bIntkey ? ", " : "", pTab->iPk, ++ "FROM pragma_table_info(%Q)", ++ bIntkey ? ", " : "", pTab->iPk, bIntkey ? "" : "(CASE WHEN pk=0 THEN 1000000 ELSE pk END), ", zName ); @@@ -7744,7 -7394,7 +7759,7 @@@ ** those. ** ** If a table is found, a (RecoverTable*) object is returned. Or, if --** no such table is found, but bIntkey is false and iRoot is the ++** no such table is found, but bIntkey is false and iRoot is the ** root page of an index in the recovered schema, then (*pbNoop) is ** set to true and NULL returned. Or, if there is no such table or ** index, NULL is returned and (*pbNoop) set to 0, indicating that @@@ -7840,7 -7490,7 +7855,7 @@@ static RecoverTable *recoverOrphanTable recoverFreeTable(pTab); pTab = 0; }else{ -- raw_printf(pState->out, ++ raw_printf(pState->out, "CREATE TABLE %s(rootpgno INTEGER, " "pgno INTEGER, nfield INTEGER, id INTEGER", pTab->zQuoted ); @@@ -7893,14 -7543,14 +7908,14 @@@ static int recoverDatabaseCmd(ShellStat bRowids = 0; } else{ -- utf8_printf(stderr, "unexpected option: %s\n", azArg[i]); ++ utf8_printf(stderr, "unexpected option: %s\n", azArg[i]); showHelp(pState->out, azArg[0]); return 1; } } shellExecPrintf(pState->db, &rc, -- /* Attach an in-memory database named 'recovery'. Create an indexed ++ /* Attach an in-memory database named 'recovery'. Create an indexed ** cache of the sqlite_dbptr virtual table. */ "PRAGMA writable_schema = on;" "ATTACH %Q AS recovery;" @@@ -7934,9 -7584,9 +7949,9 @@@ } /* If this is an auto-vacuum database, add all pointer-map pages to -- ** the freelist table. Do this regardless of whether or not ++ ** the freelist table. Do this regardless of whether or not ** --freelist-corrupt was specified. */ -- shellExec(pState->db, &rc, ++ shellExec(pState->db, &rc, "WITH ptrmap(pgno) AS (" " SELECT 2 WHERE shell_int32(" " (SELECT data FROM sqlite_dbpage WHERE pgno=1), 13" @@@ -7948,7 -7598,7 +7963,7 @@@ "REPLACE INTO recovery.freelist SELECT pgno FROM ptrmap" ); -- shellExec(pState->db, &rc, ++ shellExec(pState->db, &rc, "CREATE TABLE recovery.dbptr(" " pgno, child, PRIMARY KEY(child, pgno)" ") WITHOUT ROWID;" @@@ -7968,7 -7618,7 +7983,7 @@@ ");" /* Create the "map" table that will (eventually) contain instructions -- ** for dealing with each page in the db that contains one or more ++ ** for dealing with each page in the db that contains one or more ** records. */ "CREATE TABLE recovery.map(" "pgno INTEGER PRIMARY KEY, maxlen INT, intkey, root INT" @@@ -8017,7 -7667,7 +8032,7 @@@ "CREATE INDEX recovery.schema_rootpage ON schema(rootpage);" ); -- /* Open a transaction, then print out all non-virtual, non-"sqlite_%" ++ /* Open a transaction, then print out all non-virtual, non-"sqlite_%" ** CREATE TABLE statements that extracted from the existing schema. */ if( rc==SQLITE_OK ){ sqlite3_stmt *pStmt = 0; @@@ -8034,7 -7684,7 +8049,7 @@@ ); while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ const char *zCreateTable = (const char*)sqlite3_column_text(pStmt, 0); -- raw_printf(pState->out, "CREATE TABLE IF NOT EXISTS %s;\n", ++ raw_printf(pState->out, "CREATE TABLE IF NOT EXISTS %s;\n", &zCreateTable[12] ); } @@@ -8043,7 -7693,7 +8058,7 @@@ /* Figure out if an orphan table will be required. And if so, how many ** user columns it should contain */ -- shellPrepare(pState->db, &rc, ++ shellPrepare(pState->db, &rc, "SELECT coalesce(max(maxlen), -2) FROM recovery.map WHERE root>1" , &pLoop ); @@@ -8067,8 -7717,8 +8082,8 @@@ ); /* Loop through each root page. */ -- shellPrepare(pState->db, &rc, -- "SELECT root, intkey, max(maxlen) FROM recovery.map" ++ shellPrepare(pState->db, &rc, ++ "SELECT root, intkey, max(maxlen) FROM recovery.map" " WHERE root>1 GROUP BY root, intkey ORDER BY root=(" " SELECT rootpage FROM recovery.schema WHERE name='sqlite_sequence'" ")", &pLoop @@@ -8121,13 -7771,13 +8136,13 @@@ nField = nField+1; if( pTab2==pOrphan ){ -- raw_printf(pState->out, ++ raw_printf(pState->out, "INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n", pTab2->zQuoted, iRoot, iPgno, nField, iMin<0 ? "" : "NULL, ", zVal, pTab2->azlCol[nField] ); }else{ -- raw_printf(pState->out, "INSERT INTO %s(%s) VALUES( %s );\n", ++ raw_printf(pState->out, "INSERT INTO %s(%s) VALUES( %s );\n", pTab2->zQuoted, pTab2->azlCol[nField], zVal ); } @@@ -8145,7 -7795,7 +8160,7 @@@ /* The rest of the schema */ if( rc==SQLITE_OK ){ sqlite3_stmt *pStmt = 0; -- shellPrepare(pState->db, &rc, ++ shellPrepare(pState->db, &rc, "SELECT sql, name FROM recovery.schema " "WHERE sql NOT LIKE 'create table%'", &pStmt ); @@@ -8153,7 -7803,7 +8168,7 @@@ const char *zSql = (const char*)sqlite3_column_text(pStmt, 0); if( sqlite3_strnicmp(zSql, "create virt", 11)==0 ){ const char *zName = (const char*)sqlite3_column_text(pStmt, 1); -- char *zPrint = shellMPrintf(&rc, ++ char *zPrint = shellMPrintf(&rc, "INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)", zName, zName, zSql ); @@@ -8175,542 -7825,221 +8190,757 @@@ } #endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */ - /* ++/* +** The .shxopts command, for setting or listing shell extension options. + */ +#if SHELL_EXTENSIONS +static int shxoptsCommand(char *azArg[], int nArg, ShellState *p, char **pzE){ + static struct { const char *name; u8 mask; } shopts[] = { +#if SHELL_DYNAMIC_COMMANDS + {"dyn_cmds", 1<1 ){ + for( ia=1; iabExtendedDotCmds |= shopts[io].mask; + else p->bExtendedDotCmds &= ~shopts[io].mask; + break; + } + } + if( io==ArraySize(shopts) ){ + zAbout = azArg[ia]; + zMoan = "is not a recognized option name"; + goto moan_error; + } + } + }else{ + raw_printf(p->out, + " name value \"-shxopts set\"\n" + " -------- ----- ---------------\n"); + for( io=0; iobExtendedDotCmds & m) == m)? 1 : 0; + raw_printf(p->out, + " %9s %2d \"-shxopts 0x%02X\"\n", + shopts[io].name, v, m); + } + } + return 0; + moan_error: + raw_printf(stderr, "Error: %s %s\n", zAbout, zMoan); + return 1; +} +#endif + +static int execute_variables(char *azArg[], int nArg, ShellState *p){ + int ia, rc, nErrors = 0; + sqlite3_stmt *pStmt = 0; + open_db(p, 0); + if( p->db==0 ){ + utf8_printf(stderr, ".x can only be done with a database open.\n"); + return 1; + } + if( sqlite3_table_column_metadata(p->db, PARAM_TABLE_SCHEMA, + PARAM_TABLE_NAME, + "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){ + utf8_printf(stderr, "No "PARAM_TABLE_SNAME" table exists.\n"); + return 1; + } + rc = sqlite3_prepare_v2 + (p->db, "SELECT value FROM "PARAM_TABLE_SNAME + " WHERE key=$1 AND uses=1", + -1, &pStmt, 0); + if( rc!=SQLITE_OK ){ + utf8_printf(stderr, PARAM_TABLE_SNAME" is wrongly created.\n"); + return 1; + } + for( ia=1; ia < nArg; ++ia ){ + if( isalpha(azArg[ia][0]) ){ + rc = sqlite3_reset(pStmt); + rc = sqlite3_bind_text(pStmt, 1, azArg[ia], -1, 0); + rc = sqlite3_step(pStmt); + if( rc==SQLITE_ROW ){ + const unsigned char *zValue = sqlite3_column_text(pStmt, 0); + int nb = sqlite3_column_bytes(pStmt, 0); + while( nb>0 && IsSpace(zValue[nb-1]) ) --nb; + if( nb>0 ){ + /* The trailing newline (or some other placeholder) is important + * because one (or some other character) will likely be put in + * its place during process_input() line/group handling, along + * with a terminating NUL character. Without it, the NULL could + * land past the end of the allocation made just below. + */ + int nle = zValue[nb-1]=='\n'; + char *zSubmit = sqlite3_mprintf( "%.*s%s", nb, zValue, "\n"+nle ); + InSource inRedir + = INSOURCE_STR_REDIR(zSubmit, azArg[ia], p->pInSource); + shell_check_oom(zSubmit); + p->pInSource = &inRedir; + rc = process_input(p); + sqlite3_free(zSubmit); + p->pInSource = inRedir.pFrom; + }else{ + continue; /* All white, ignore. */ + } + }else{ + utf8_printf(stderr, + "Skipping parameter '%s' (not set and executable.)\n", + azArg[ia]); + ++nErrors; + } + }else{ + utf8_printf(stderr, + "Skipping badly named %s. Run \".help x\"\n", azArg[ia]); + ++nErrors; + } + } + sqlite3_finalize(pStmt); + return (rc==2)? 2 : nErrors>0; +} + +struct param_row { char * value; int uses; int hits; }; + +static int param_find_callback(void *pData, int nc, char **pV, char **pC){ + assert(nc>=1); + assert(strcmp(pC[0],"value")==0); + struct param_row *pParam = (struct param_row *)pData; + assert(pParam->value==0); /* key values are supposedly unique. */ + if( pParam->value!=0 ) sqlite3_free( pParam->value ); + pParam->value = sqlite3_mprintf("%s", pV[0]); /* source owned by statement */ + if( nc>1 ) pParam->uses = (int)integerValue(pV[1]); + ++pParam->hits; + return 0; +} + +static void append_in_clause(sqlite3_str *pStr, + const char **azBeg, const char **azLim); +static char *find_home_dir(int clearFlag); + +/* Create a home-relative pathname from ~ prefixed path. + * Return it, or 0 for any error. + * Caller must sqlite3_free() it. + */ +static char *home_based_path( const char *zPath ){ + char *zHome = find_home_dir(0); + char *zErr = 0; + assert( zPath[0]=='~' ); + if( zHome==0 ){ + zErr = "Cannot find home directory."; + }else if( zPath[0]==0 || (zPath[1]!='/' +#if defined(_WIN32) || defined(WIN32) + && zPath[1]!='\\' +#endif + ) ){ + zErr = "Malformed pathname"; + }else{ + return sqlite3_mprintf("%s%s", zHome, zPath+1); + } + utf8_printf(stderr, "Error: %s\n", zErr); + return 0; +} + +/* Transfer selected parameters between two parameter tables, for save/load. + * Argument bSaveNotLoad determines transfer direction and other actions. + * If it is true, the store DB will be created if not existent, and its + * table for keeping parameters will be created. Or failure is returned. + * If it is false, the store DB will be opened for read and its presumed + * table for keeping parameters will be read. Or failure is returned. + * + * Arguments azNames and nNames reference the ?NAMES? save/load arguments. + * If it is an empty list, all parameters will be saved or loaded. - * Otherwise, only the named parameters are transferred, if they exist. ++ * Otherwise, only the named parameters are transferred, if they exist. + * It is not an error to specify a name that cannot be transferred + * because it does not exist in the source table. + * + * Returns are SQLITE_OK for success, or other codes for failure. + */ +static int param_xfr_table(sqlite3 *db, const char *zStoreDbName, + int bSaveNotLoad, const char *azNames[], int nNames){ + int rc = 0; + char *zSql = 0; /* to be sqlite3_free()'ed */ + sqlite3_str *sbCopy = 0; + const char *zHere = PARAM_TABLE_SNAME; + const char *zThere = PARAM_STORE_SNAME; + const char *zTo = (bSaveNotLoad)? zThere : zHere; + const char *zFrom = (bSaveNotLoad)? zHere : zThere; + sqlite3 *dbStore = 0; + int openFlags = (bSaveNotLoad) + ? SQLITE_OPEN_URI|SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE + : SQLITE_OPEN_READONLY; + + /* Ensure store DB can be opened and/or created appropriately. */ + rc = sqlite3_open_v2(zStoreDbName, &dbStore, openFlags, 0); + if( rc!=SQLITE_OK ){ + utf8_printf(stderr, "Error: Cannot %s parameter store DB %s\n", + bSaveNotLoad? "open/create" : "read", zStoreDbName); + return rc; + } + /* Ensure it has the parameter store table, or handle its absence. */ + assert(dbStore!=0); + if( sqlite3_table_column_metadata + (dbStore, "main", PARAM_STORE_NAME, 0, 0, 0, 0, 0, 0)!=SQLITE_OK ){ + if( !bSaveNotLoad ){ + utf8_printf(stderr, "Error: No parameters ever stored in DB %s\n", + zStoreDbName); + rc = 1; + }else{ + /* The saved parameters table is not there yet; create it. */ + const char *zCT = + "CREATE TABLE IF NOT EXISTS "PARAM_STORE_NAME"(\n" + " key TEXT PRIMARY KEY,\n" + " value,\n" + " uses INT\n" + ") WITHOUT ROWID;"; + rc = sqlite3_exec(dbStore, zCT, 0, 0, 0); + if( rc!=SQLITE_OK ){ + utf8_printf(stderr, "Cannot create table %s. Nothing saved.", zThere); + } + } + } + sqlite3_close(dbStore); + if( rc!=0 ) return rc; + + zSql = sqlite3_mprintf("ATTACH %Q AS %s;", zStoreDbName, PARAM_STORE_SCHEMA); + shell_check_oom(zSql); + rc = sqlite3_exec(db, zSql, 0, 0, 0); + sqlite3_free(zSql); + if( rc!=SQLITE_OK ) return rc; + + sbCopy = sqlite3_str_new(db); + sqlite3_str_appendf + (sbCopy, "INSERT OR REPLACE INTO %s(key,value,uses)" + "SELECT key, value, uses FROM %s WHERE key ", zTo, zFrom); + append_in_clause(sbCopy, azNames, azNames+nNames); + zSql = sqlite3_str_finish(sbCopy); + shell_check_oom(zSql); + rc = sqlite3_exec(db, zSql, 0, 0, 0); + sqlite3_free(zSql); + + sqlite3_exec(db, "DETACH "PARAM_STORE_SCHEMA";", 0, 0, 0); + return rc; +} + +/* Default location of parameters store DB for .parameters save/load. */ +static const char *zDefaultParamStore = "~/sqlite_params.sdb"; + +/* Possibly generate a derived path from input spec, with defaulting + * and conversion of leading (or only) tilde as home directory. + * The above-set default is used for zSpec NULL, "" or "~". + * When return is 0, there is an error; what needs doing cannnot be done. + * If the return is exactly the input, it must not be sqlite3_free()'ed. + * If the return differs from the input, it must be sqlite3_free()'ed. + */ +static const char *params_store_path(const char *zSpec){ + if( zSpec==0 || zSpec[0]==0 || strcmp(zSpec,"~")==0 ){ + return home_based_path(zDefaultParamStore); + }else if ( zSpec[0]=='~' ){ + return home_based_path(zSpec); + } + return zSpec; +} + +/* Load some or all parameters. Arguments are "load FILE ?NAMES?". */ +static int parameters_load(sqlite3 *db, const char *azArg[], int nArg){ + const char *zStore = params_store_path((nArg>1)? azArg[1] : 0); + if( zStore==0 ){ + utf8_printf(stderr, "Cannot form parameter load path. Nothing loaded.\n"); + return 1; + }else{ + const char **pzFirst = (nArg>2)? azArg+2 : 0; + int nNames = (nArg>2)? nArg-2 : 0; + int rc = param_xfr_table(db, zStore, 0, pzFirst, nNames); + if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore); + return rc; + } +} + +/* Save some or all parameters. Arguments are "save FILE ?NAMES?". */ +static int parameters_save(sqlite3 *db, const char *azArg[], int nArg){ + const char *zStore = params_store_path((nArg>1)? azArg[1] : 0); + if( zStore==0 ){ + utf8_printf(stderr, "Cannot form parameter save path. Nothing saved.\n"); + return 1; + }else{ + const char **pzFirst = (nArg>2)? azArg+2 : 0; + int nNames = (nArg>2)? nArg-2 : 0; + int rc = param_xfr_table(db, zStore, 1, pzFirst, nNames); + if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore); + return rc; + } +} + +#ifndef SQLITE_NOHAVE_SYSTEM +/* + * Edit one named parameter in the parameters table. If it does not + * yet exist, create it. If eval is true, the value is treated as a + * bare expression, otherwise it is a text value. The uses argument + * sets the 3rd column in the parameters table, and may also serve + * to partition the key namespace. (This is not done now.) + */ +static int edit_one_param(sqlite3 *db, char *name, int eval, + ParamTableUse uses, const char * zEditor){ + struct param_row paramVU = {0,0,0}; + int rc; + char * zVal = 0; + char * zSql = sqlite3_mprintf + ("SELECT value, uses FROM " PARAM_TABLE_SNAME " WHERE key=%Q", name); + shell_check_oom(zSql); + sqlite3_exec(db, zSql, param_find_callback, ¶mVU, 0); + sqlite3_free(zSql); + assert(paramVU.hits<2); + if( paramVU.hits==1 && paramVU.uses==uses){ + /* Editing an existing value of same kind. */ + sqlite3_free(paramVU.value); + if( eval!=0 ){ + zSql = sqlite3_mprintf + ("SELECT edit(value, %Q) FROM " PARAM_TABLE_SNAME + " WHERE key=%Q AND uses=%d", zEditor, name, uses); + zVal = db_text(db, zSql, 1); + sqlite3_free(zSql); + zSql = sqlite3_mprintf + ("UPDATE "PARAM_TABLE_SNAME" SET value=(SELECT %s) WHERE" + " key=%Q AND uses=%d", zVal, name, uses); + }else{ + zSql = sqlite3_mprintf + ("UPDATE "PARAM_TABLE_SNAME" SET value=edit(value, %Q) WHERE" + " key=%Q AND uses=%d", zEditor, name, uses); + } + }else{ + /* Editing a new value of same kind. */ + assert(paramVU.value==0 || paramVU.uses!=uses); + if( eval!=0 ){ + zSql = sqlite3_mprintf + ("SELECT edit('-- %q%s', %Q)", name, "\n", zEditor); + zVal = db_text(db, zSql, 1); + sqlite3_free(zSql); + zSql = sqlite3_mprintf + ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)" + " VALUES (%Q,(SELECT %s LIMIT 1),%d)", + name, zVal, uses); + }else{ + zSql = sqlite3_mprintf + ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)" + " VALUES (%Q,edit('-- %q%s', %Q),%d)", + name, name, "\n", zEditor, uses); + } + } + shell_check_oom(zSql); + if( eval!=0 ){ + } + rc = sqlite3_exec(db, zSql, 0, 0, 0); + sqlite3_free(zSql); + sqlite3_free(zVal); + return rc!=SQLITE_OK; +} +#endif + +/* Space-join values in an argument list. *valLim is not included. */ +char *values_join( char **valBeg, char **valLim ){ + char *z = 0; + const char *zSep = 0; + while( valBeg < valLim ){ + z = sqlite3_mprintf("%z%s%s", z, zSep, *valBeg); + zSep = " "; + ++valBeg; + } + return z; +} + +/* Get a named parameter value in form of stepped prepared statement, + * ready to have its value taken from the 0th column. If the name + * cannot be found for the given ParamTableUse, 0 is returned. + * The caller is responsible for calling sqlite3_finalize(pStmt), + * where pStmt is the return from this function. + */ +static sqlite3_stmt *get_param_value(sqlite3 *db, char *name, + ParamTableUse ptu){ + sqlite3_stmt *rv = 0; + int rc; + char *zSql = sqlite3_mprintf + ( "SELECT value FROM "PARAM_TABLE_SNAME + " WHERE key=%Q AND uses=%d", name, ptu ); + shell_check_oom(zSql); + rc = sqlite3_prepare_v2(db, zSql, -1, &rv, 0); + sqlite3_free(zSql); + if( SQLITE_OK==rc ){ + if( SQLITE_ROW==sqlite3_step(rv) ) return rv; + sqlite3_finalize(rv); + } + return 0; +} + +static struct ParamSetOpts { + const char cCast; + const char *zTypename; + int evalKind; +} param_set_opts[] = { + /* { 'q', 0, 2 }, */ + /* { 'x', 0, 1 }, */ + { 'i', "INT", 1 }, + { 'r', "REAL", 1 }, + { 'b', "BLOB", 1 }, + { 't', "TEXT", 0 }, + { 'n', "NUMERIC", 1 } +}; + +/* Return an option character if it is single and prefixed by - or --, + * else return 0. + */ +static char option_char(char *zArg){ + if( zArg[0]=='-' ){ + ++zArg; + if( zArg[0]=='-' ) ++zArg; + if( zArg[0]!=0 && zArg[1]==0 ) return zArg[0]; + } + return 0; +} + +static int param_set(sqlite3 *db, char cCast, + char *name, char **valBeg, char **valLim, + ParamTableUse ptu){ + char *zSql = 0; + int rc = SQLITE_OK, retries = 0, needsEval = 1; + char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0; + sqlite3_stmt *pStmtSet = 0; + const char *zCastTo = 0; + char *zValue = (zValGlom==0)? *valBeg : zValGlom; + if( cCast ){ + struct ParamSetOpts *pSO = param_set_opts; + for(; pSO-param_set_opts < ArraySize(param_set_opts); ++pSO ){ + if( cCast==pSO->cCast ){ + zCastTo = pSO->zTypename; + needsEval = pSO->evalKind > 0; + break; + } + } + } + if( needsEval ){ + if( zCastTo!=0 ){ + zSql = sqlite3_mprintf + ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" + " VALUES(%Q,CAST((%s) AS %s),%d);", name, zValue, zCastTo, ptu ); + }else{ + zSql = sqlite3_mprintf + ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" + "VALUES(%Q,(%s),%d);", name, zValue, ptu ); + } + shell_check_oom(zSql); + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0); + sqlite3_free(zSql); + } + if( !needsEval || rc!=SQLITE_OK ){ + /* Reach here when value either requested to be cast to text, or must be. */ + sqlite3_finalize(pStmtSet); + pStmtSet = 0; + zSql = sqlite3_mprintf + ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" + "VALUES(%Q,%Q,%d);", name, zValue, ptu ); + shell_check_oom(zSql); + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0); + assert(rc==SQLITE_OK); + sqlite3_free(zSql); + } + sqlite3_step(pStmtSet); + sqlite3_finalize(pStmtSet); + sqlite3_free(zValGlom); + return rc; +} + +/* list subcommand for .parameter dot-command */ +static void list_params(ShellState *p, ParamTableUse ptu){ + sqlite3_stmt *pStmt = 0; + int len = 0; + int rc = sqlite3_prepare_v2 + (p->db, "SELECT max(length(key)) FROM " + PARAM_TABLE_SNAME" WHERE ?1=3 OR uses=?1", -1, &pStmt, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int(pStmt, 1, ptu); + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + len = sqlite3_column_int(pStmt, 0); + if( len>40 ) len = 40; + if( len<4 ) len = 4; + } + } + sqlite3_finalize(pStmt); + pStmt = 0; + if( len ){ + utf8_printf(p->out, "%-*s %-8s %s\n", len, "name", "usage", "value"); + rc = sqlite3_prepare_v2 + (p->db, "SELECT key, uses, quote(value) FROM " PARAM_TABLE_SNAME + " WHERE ?1=3 OR uses=?1 ORDER BY key", -1, &pStmt, 0); + sqlite3_bind_int(pStmt, 1, ptu); + while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ + ParamTableUse ptux = sqlite3_column_int(pStmt,1); + const char *zUse; + switch( ptux ){ + case PTU_Binding: zUse = "binding"; break; + case PTU_Script: zUse = "script"; break; + default: zUse = "unknown"; + } + utf8_printf(p->out, "%-*s %-8s %s\n", len, sqlite3_column_text(pStmt,0), + zUse, sqlite3_column_text(pStmt,2)); + } + sqlite3_finalize(pStmt); + } - } ++} + +/* Append either an IN clause or an always true test to some SQL. + * + * An empty IN list is the same as always true (for non-NULL LHS) + * for this clause, which assumes a trailing LHS operand and space. + * If that is not the right result, guard the call against it. + * This is used for ".parameter dostuff ?NAMES?" options, + * where a missing list means all the qualifying entries. + * + * The empty list may be signified by azBeg and azLim both 0. + */ +static void append_in_clause(sqlite3_str *pStr, + const char **azBeg, const char **azLim){ + if( azBeg==azLim ) sqlite3_str_appendf(pStr, "NOT NULL"); + else{ + char cSep = '('; + sqlite3_str_appendf(pStr, "IN"); + while( azBeg Maybe init db, add column zCol to it. + * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE, + * close db and set it to 0, and return the columns spec, to later + * be sqlite3_free()'ed by the caller. + * The return is 0 when either: + * (a) The db was not initialized and zCol==0 (There are no columns.) + * (b) zCol!=0 (Column was added, db initialized as needed.) + * The 3rd argument, pRenamed, references an out parameter. If the + * pointer is non-zero, its referent will be set to a summary of renames + * done if renaming was necessary, or set to 0 if none was done. The out + * string (if any) must be sqlite3_free()'ed by the caller. + */ + #ifdef SHELL_DEBUG + #define rc_err_oom_die(rc) \ + if( rc==SQLITE_NOMEM ) shell_check_oom(0); \ + else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \ + fprintf(stderr,"E:%d\n",rc), assert(0) + #else + static void rc_err_oom_die(int rc){ + if( rc==SQLITE_NOMEM ) shell_check_oom(0); + assert(rc==SQLITE_OK||rc==SQLITE_DONE); + } + #endif + + #ifdef SHELL_COLFIX_DB /* If this is set, the DB can be in a file. */ + static char zCOL_DB[] = SHELL_STRINGIFY(SHELL_COLFIX_DB); + #else /* Otherwise, memory is faster/better for the transient DB. */ + static const char *zCOL_DB = ":memory:"; + #endif + + /* Define character (as C string) to separate generated column ordinal + * from protected part of incoming column names. This defaults to "_" + * so that incoming column identifiers that did not need not be quoted + * remain usable without being quoted. It must be one character. + */ + #ifndef SHELL_AUTOCOLUMN_SEP + # define AUTOCOLUMN_SEP "_" + #else + # define AUTOCOLUMN_SEP SHELL_STRINGIFY(SHELL_AUTOCOLUMN_SEP) + #endif + + static char *zAutoColumn(const char *zColNew, sqlite3 **pDb, char **pzRenamed){ + /* Queries and D{D,M}L used here */ + static const char * const zTabMake = "\ + CREATE TABLE ColNames(\ + cpos INTEGER PRIMARY KEY,\ + name TEXT, nlen INT, chop INT, reps INT, suff TEXT);\ + CREATE VIEW RepeatedNames AS \ + SELECT DISTINCT t.name FROM ColNames t \ + WHERE t.name COLLATE NOCASE IN (\ + SELECT o.name FROM ColNames o WHERE o.cpos<>t.cpos\ + );\ + "; + static const char * const zTabFill = "\ + INSERT INTO ColNames(name,nlen,chop,reps,suff)\ + VALUES(iif(length(?1)>0,?1,'?'),max(length(?1),1),0,0,'')\ + "; + static const char * const zHasDupes = "\ + SELECT count(DISTINCT (substring(name,1,nlen-chop)||suff) COLLATE NOCASE)\ + 1, printf('%c%0*d', '"AUTOCOLUMN_SEP"', $1, cpos), '')" + #else /* ...RENAME_MINIMAL_ONE_PASS */ + "WITH Lzn(nlz) AS (" /* Find minimum extraneous leading 0's for uniqueness */ + " SELECT 0 AS nlz" + " UNION" + " SELECT nlz+1 AS nlz FROM Lzn" + " WHERE EXISTS(" + " SELECT 1" + " FROM ColNames t, ColNames o" + " WHERE" + " iif(t.name IN (SELECT * FROM RepeatedNames)," + " printf('%s"AUTOCOLUMN_SEP"%s'," + " t.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,t.cpos),2))," + " t.name" + " )" + " =" + " iif(o.name IN (SELECT * FROM RepeatedNames)," + " printf('%s"AUTOCOLUMN_SEP"%s'," + " o.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,o.cpos),2))," + " o.name" + " )" + " COLLATE NOCASE" + " AND o.cpos<>t.cpos" + " GROUP BY t.cpos" + " )" + ") UPDATE Colnames AS t SET" + " chop = 0," /* No chopping, never touch incoming names. */ + " suff = iif(name IN (SELECT * FROM RepeatedNames)," + " printf('"AUTOCOLUMN_SEP"%s', substring(" + " printf('%.*c%0.*d',(SELECT max(nlz) FROM Lzn)+1,'0',1,t.cpos),2))," + " ''" + " )" + #endif + ; + static const char * const zCollectVar = "\ + SELECT\ + '('||x'0a'\ + || group_concat(\ + cname||' TEXT',\ + ','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\ + ||')' AS ColsSpec \ + FROM (\ + SELECT cpos, printf('\"%w\"',printf('%.*s%s', nlen-chop,name,suff)) AS cname \ + FROM ColNames ORDER BY cpos\ + )"; + static const char * const zRenamesDone = + "SELECT group_concat(" + " printf('\"%w\" to \"%w\"',name,printf('%.*s%s', nlen-chop, name, suff))," + " ','||x'0a')" + "FROM ColNames WHERE suff<>'' OR chop!=0" + ; + int rc; + sqlite3_stmt *pStmt = 0; + assert(pDb!=0); + if( zColNew ){ + /* Add initial or additional column. Init db if necessary. */ + if( *pDb==0 ){ + if( SQLITE_OK!=sqlite3_open(zCOL_DB, pDb) ) return 0; + #ifdef SHELL_COLFIX_DB + if(*zCOL_DB!=':') + sqlite3_exec(*pDb,"drop table if exists ColNames;" + "drop view if exists RepeatedNames;",0,0,0); + #endif + rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0); + rc_err_oom_die(rc); + } + assert(*pDb!=0); + rc = sqlite3_prepare_v2(*pDb, zTabFill, -1, &pStmt, 0); + rc_err_oom_die(rc); + rc = sqlite3_bind_text(pStmt, 1, zColNew, -1, 0); + rc_err_oom_die(rc); + rc = sqlite3_step(pStmt); + rc_err_oom_die(rc); + sqlite3_finalize(pStmt); + return 0; + }else if( *pDb==0 ){ + return 0; + }else{ + /* Formulate the columns spec, close the DB, zero *pDb. */ + char *zColsSpec = 0; + int hasDupes = db_int(*pDb, zHasDupes); + #ifdef SQLITE_ENABLE_MATH_FUNCTIONS + int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0; + #else + # define nDigits 2 + #endif + if( hasDupes ){ + #ifdef SHELL_COLUMN_RENAME_CLEAN + rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0); + rc_err_oom_die(rc); + #endif + rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0); + rc_err_oom_die(rc); + rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0); + rc_err_oom_die(rc); + sqlite3_bind_int(pStmt, 1, nDigits); + rc = sqlite3_step(pStmt); + sqlite3_finalize(pStmt); + assert(rc==SQLITE_DONE); + } + assert(db_int(*pDb, zHasDupes)==0); /* Consider: remove this */ + rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0); + rc_err_oom_die(rc); + rc = sqlite3_step(pStmt); + if( rc==SQLITE_ROW ){ + zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); + }else{ + zColsSpec = 0; + } + if( pzRenamed!=0 ){ + if( !hasDupes ) *pzRenamed = 0; + else{ + sqlite3_finalize(pStmt); + if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0) + && SQLITE_ROW==sqlite3_step(pStmt) ){ + *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); + }else + *pzRenamed = 0; + } + } + sqlite3_finalize(pStmt); + sqlite3_close(*pDb); + *pDb = 0; + return zColsSpec; + } + } + /* ** If an input line begins with "." then invoke this routine to ** process that line. @@@ -8832,7 -8157,7 +9062,7 @@@ static int do_meta_command(char *zLine return 1; } if( zDb==0 ) zDb = "main"; -- rc = sqlite3_open_v2(zDestFile, &pDest, ++ rc = sqlite3_open_v2(zDestFile, &pDest, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs); if( rc!=SQLITE_OK ){ utf8_printf(stderr, "Error: cannot open \"%s\"\n", zDestFile); @@@ -9075,7 -8400,7 +9305,7 @@@ if( nArg>1 && ii==ArraySize(aDbConfig) ){ utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]); utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n"); -- } ++ } }else if( c=='d' && n>=3 && strncmp(azArg[0], "dbinfo", n)==0 ){ @@@ -9096,7 -8420,7 +9326,7 @@@ int i; int savedShowHeader = p->showHeader; int savedShellFlags = p->shellFlgs; -- ShellClearFlag(p, ++ ShellClearFlag(p, SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo |SHFLG_DumpDataOnly|SHFLG_DumpNoSys); for(i=1; ibSafeMode ){ -- raw_printf(stderr, ++ raw_printf(stderr, "Cannot run experimental commands such as \"%s\" in safe mode\n", azArg[0]); rc = 1; @@@ -9295,7 -8617,7 +9525,7 @@@ } aCtrl[] = { { "chunk_size", SQLITE_FCNTL_CHUNK_SIZE, "SIZE" }, { "data_version", SQLITE_FCNTL_DATA_VERSION, "" }, -- { "has_moved", SQLITE_FCNTL_HAS_MOVED, "" }, ++ { "has_moved", SQLITE_FCNTL_HAS_MOVED, "" }, { "lock_timeout", SQLITE_FCNTL_LOCK_TIMEOUT, "MILLISEC" }, { "persist_wal", SQLITE_FCNTL_PERSIST_WAL, "[BOOLEAN]" }, /* { "pragma", SQLITE_FCNTL_PRAGMA, "NAME ARG" },*/ @@@ -9316,7 -8638,7 +9546,7 @@@ open_db(p, 0); zCmd = nArg>=2 ? azArg[1] : "help"; -- if( zCmd[0]=='-' ++ if( zCmd[0]=='-' && (strcmp(zCmd,"--schema")==0 || strcmp(zCmd,"-schema")==0) && nArg>=4 ){ @@@ -9596,7 -8910,7 +9826,7 @@@ goto meta_command_exit; } if( nSep>1 ){ -- raw_printf(stderr, ++ raw_printf(stderr, "Error: multi-character column separators not allowed" " for import\n"); rc = 1; @@@ -9674,13 -8988,22 +9904,22 @@@ if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){ char *zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"", zSchema, zTable); - char cSep = '('; + sqlite3 *dbCols = 0; + char *zRenames = 0; + char *zColDefs; while( xRead(&sCtx) ){ - zCreate = sqlite3_mprintf("%z%c\n \"%w\" TEXT", zCreate, cSep, sCtx.z); - cSep = ','; + zAutoColumn(sCtx.z, &dbCols, 0); if( sCtx.cTerm!=sCtx.cColSep ) break; } - if( cSep=='(' ){ + zColDefs = zAutoColumn(0, &dbCols, &zRenames); + if( zRenames!=0 ){ - utf8_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr, ++ utf8_printf(INSOURCE_IS_INTERACTIVE(p->pInSource)? p->out : stderr, + "Columns renamed during .import %s due to duplicates:\n" + "%s\n", sCtx.zFile, zRenames); + sqlite3_free(zRenames); + } + assert(dbCols==0); + if( zColDefs==0 ){ sqlite3_free(zCreate); import_cleanup(&sCtx); utf8_printf(stderr,"%s: empty file\n", sCtx.zFile); @@@ -10498,20 -9743,33 +10737,20 @@@ ** VALUE can be in either SQL literal notation, or if not it will be ** understood to be a text string. */ - if( nArg==4 && strcmp(azArg[1],"set")==0 ){ - int rx; - char *zSql; - sqlite3_stmt *pStmt; - const char *zKey = azArg[2]; - const char *zValue = azArg[3]; - bind_table_init(p); - zSql = sqlite3_mprintf( - "REPLACE INTO temp.sqlite_parameters(key,value)" - "VALUES(%Q,%s);", zKey, zValue); - shell_check_oom(zSql); - pStmt = 0; - rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); - if( rx!=SQLITE_OK ){ - sqlite3_finalize(pStmt); - pStmt = 0; - zSql = sqlite3_mprintf( - "REPLACE INTO temp.sqlite_parameters(key,value)" - "VALUES(%Q,%Q);", zKey, zValue); - shell_check_oom(zSql); - rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); - if( rx!=SQLITE_OK ){ + if( nArg>=4 && strcmp(azArg[1],"set")==0 ){ + char cCast = option_char(azArg[2]); + int inv = 2 + (cCast != 0); + ParamTableUse ptu = classify_param_name(azArg[inv]); + if( ptu==PTU_Nil ){ + utf8_printf(stderr, + "Error: %s is not a usable parameter name.\n", azArg[inv]); + rc = 1; + }else{ + param_table_init(p); + rc = param_set(p->db, cCast, azArg[inv], - &azArg[inv+1], &azArg[nArg], ptu); ++ &azArg[inv+1], &azArg[nArg], ptu); + if( rc!=SQLITE_OK ){ utf8_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db)); - sqlite3_finalize(pStmt); - pStmt = 0; rc = 1; } } @@@ -10838,15 -10103,8 +11077,15 @@@ } }else + if( c=='s' && n==7 && strncmp(azArg[0], "seeargs", n)==0 ){ + for( n=1; nout, "%s%s", azArg[n], (n==nArg-1)? "|\n" : "|"); - } ++ } + }else + if( c=='s' && n==11 && strncmp(azArg[0], "selecttrace", n)==0 ){ - unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff; + unsigned int x + = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff; sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x); }else @@@ -12114,9 -11351,9 +12353,9 @@@ static void sql_prescan(char *zLine, Sq /* fall thru */ case ']': cWait = 0; - qss = QSS_SETV(qss, 0); + sss = SSS_SETV(sss, 0); goto PlainScan; -- default: assert(0); ++ default: assert(0); } } } @@@ -12185,19 -11416,30 +12424,29 @@@ static int runOneSqlLine(ShellState *p END_TIMER; if( rc || zErrMsg ){ char zPrefix[100]; - if( bAltIn || !stdin_is_interactive ){ - sqlite3_snprintf(sizeof(zPrefix), zPrefix, - "Error: near line %d:", startline); + const char *zErrorTail; + const char *zErrorType; + if( zErrMsg==0 ){ + zErrorType = "Error"; + zErrorTail = sqlite3_errmsg(p->db); + }else if( strncmp(zErrMsg, "in prepare, ",12)==0 ){ + zErrorType = "Parse error"; + zErrorTail = &zErrMsg[12]; + }else if( strncmp(zErrMsg, "stepping, ", 10)==0 ){ + zErrorType = "Runtime error"; + zErrorTail = &zErrMsg[10]; }else{ - sqlite3_snprintf(sizeof(zPrefix), zPrefix, "Error:"); + zErrorType = "Error"; + zErrorTail = zErrMsg; } - if( zErrMsg!=0 ){ - utf8_printf(stderr, "%s %s\n", zPrefix, zErrMsg); - sqlite3_free(zErrMsg); - zErrMsg = 0; - if( in!=0 || !stdin_is_interactive ){ ++ if( bAltIn || !stdin_is_interactive ){ + sqlite3_snprintf(sizeof(zPrefix), zPrefix, + "%s near line %d:", zErrorType, startline); }else{ - utf8_printf(stderr, "%s %s\n", zPrefix, sqlite3_errmsg(p->db)); + sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType); } + utf8_printf(stderr, "%s %s\n", zPrefix, zErrorTail); + sqlite3_free(zErrMsg); - zErrMsg = 0; return 1; }else if( ShellHasFlag(p, SHFLG_CountChanges) ){ char zLineBuf[2000]; @@@ -12209,187 -11451,31 +12458,187 @@@ return 0; } +#if SHELL_EXTENDED_PARSING +/* Resumable line classsifier for dot-commands +** +** Determines if a dot-command is open, having either an unclosed +** quoted argument or an escape sequence opener ('\') at its end. +** +** The FSM design/behavior assumes/requires that a terminating '\' +** is not part of the character sequence being classified -- that +** it represents an escaped newline which is removed as physical +** lines are spliced to accumulate logical lines. +** +** The line or added line-portion is passed as zCmd. +** The pScanState pointer must reference an (opaque) DCmd_ScanState, +** which must be set to DCSS_Start to initialize the scanner state. +** Resumed scanning should always be done with zCmd logically just +** past the last non-0 char of the text previously passed in, with +** any previously scanned, trailing newline escape first trimmed. +** Returns are: 0 => not open (aka complete), 1 => is open (incomplete) +** The following macros may be applied to the scan state: +*/ +#define DCSS_InDarkArg(dcss) (((dcss)&argPosMask)==inDqArg) +#define DCSS_EndEscaped(dcss) (((dcss)&endEscaped)!=0) +#define DCSS_IsOpen(dcss) (((dcss)&isOpenMask)!=0) +typedef enum { + DCSS_Start = 0, + twixtArgs = 0, inSqArg = 1, inDarkArg = 2, inDqArg = 3, /* ordered */ + endEscaped = 4, /* bit used */ + argPosMask = 3, /* bits used */ + isOpenMask = 1|4 /* bit test */ +} DCmd_ScanState; + +static void dot_command_scan(char *zCmd, DCmd_ScanState *pScanState){ + DCmd_ScanState ss = *pScanState & ~endEscaped; + char c = (ss&isOpenMask)? 1 : *zCmd++; + while( c!=0 ){ + switch( ss ){ + case twixtArgs: + while( IsSpace(c) ){ - if( (c=*zCmd++)==0 ) goto atEnd; ++ if( (c=*zCmd++)==0 ) goto atEnd; + } + switch( c ){ + case '\\': + if( *zCmd==0 ){ + ss |= endEscaped; + goto atEnd; + }else goto inDark; + case '\'': ss = inSqArg; goto inSq; + case '"': ss = inDqArg; goto inDq; + default: ss = inDarkArg; goto inDark; + } + inSq: + case inSqArg: + while( (c=*zCmd++)!='\'' ){ + if( c==0 ) goto atEnd; + if( c=='\\' && *zCmd==0 ){ + ss |= endEscaped; + goto atEnd; + } + } + ss = twixtArgs; + c = *zCmd++; + continue; + inDq: + case inDqArg: + do { + if( (c=*zCmd++)==0 ) goto atEnd; + if( c=='\\' ){ + if( (c=*zCmd++)==0 ){ + ss |= endEscaped; + goto atEnd; + } + if( (c=*zCmd++)==0 ) goto atEnd; + } + } while( c!='"' ); + ss = twixtArgs; + c = *zCmd++; + continue; + inDark: + case inDarkArg: + while( !IsSpace(c) ){ + if( c=='\\' && *zCmd==0 ){ + ss |= endEscaped; + goto atEnd; + } - if( (c=*zCmd++)==0 ) goto atEnd; ++ if( (c=*zCmd++)==0 ) goto atEnd; + } + ss = twixtArgs; + c = *zCmd++; + continue; + } + } + atEnd: + *pScanState = ss; +} +#else +# define dot_command_scan(x,y) +#endif + +/* Utility functions for process_input. */ + +#if SHELL_EXTENDED_PARSING +/* +** Process dot-command line with its scan state to: +** 1. Setup for requested line-splicing; and +** 2. Say whether it is complete. +** The last two out parameters are the line's length, which may be +** adjusted, and the char to be used for joining a subsequent line. +** This is broken out of process_input() mainly for readability. +** The return is TRUE for dot-command ready to run, else false. +*/ +static int line_join_ends(DCmd_ScanState dcss, char *zLine, + int *pnLength, char *pcLE){ + /* It is ready only if has no open argument or escaped newline. */ + int bOpen = DCSS_IsOpen(dcss); + if( !DCSS_EndEscaped(dcss) ){ + *pcLE = '\n'; + return !bOpen; + }else{ + *pcLE = (bOpen || DCSS_InDarkArg(dcss))? 0 : ' '; + /* Swallow the trailing escape character. */ + zLine[--*pnLength] = 0; + return 0; + } +} +#endif + - /* ++/* +** Grow the accumulation line buffer to accommodate ncNeed chars. +** In/out parameters pz and pna reference the buffer and its size. +** The buffer must eventually be sqlite3_free()'ed by the caller. +*/ +static void grow_line_buffer(char **pz, int *pna, int ncNeed){ + if( ncNeed > *pna ){ + *pna += *pna + (*pna>>1) + 100; + *pz = sqlite3_realloc(*pz, *pna); + shell_check_oom(*pz); + } +} /* -** Read input from *in and process it. If *in==0 then input -** is interactive - the user is typing it it. Otherwise, input -** is coming from a file or device. A prompt is issued and history -** is saved only if input is interactive. An interrupt signal will -** cause this routine to exit immediately, unless input is interactive. +** Read input from designated source (p->pInSource) and process it. +** If pInSource==0 then input is interactive - the user is typing it. +** Otherwise, input is coming from a file, stream device or string. +** Prompts issue and history is saved only for interactive input. +** An interrupt signal will cause this routine to exit immediately, +** with "exit demanded" code returned, unless input is interactive. ** -** Return the number of errors. +** Returns: +** 0 => no errors +** 1 => errors>0 +** 2 => exit demanded, no errors. +** 3 => exit demanded, errors>0 */ static int process_input(ShellState *p){ - char *zLine = 0; /* A single input line */ - char *zSql = 0; /* Accumulated SQL text */ - int nLine; /* Length of current line */ - int nSql = 0; /* Bytes of zSql[] used */ - int nAlloc = 0; /* Allocated zSql[] space */ - int rc; /* Error code */ - int errCnt = 0; /* Number of errors seen */ - int startline = 0; /* Line number for start of current input */ - QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */ - + char *zLineInput = 0; /* a line-at-a-time input buffer or usable result */ + char *zLineAccum = 0; /* accumulation buffer, used for multi-line input */ + /* Above two pointers could be local to the group handling loop, but are + * not so that the number of memory allocations can be reduced. They are + * reused from one incoming group to another, realloc()'ed as needed. */ + int naAccum = 0; /* tracking how big zLineAccum buffer has become */ + /* Some flags for ending the overall group processing loop, always 1 or 0 */ + u8 bErrorBail=0, bInputEnd=0, bExitDemand=0, bInterrupted=0; + /* Flag to affect prompting and interrupt action */ + u8 bInteractive = (p->pInSource==&stdInSource && stdin_is_interactive); + int nErrors = 0; /* count of errors during execution or its prep */ + + /* Block overly-recursive or absurdly nested input redirects. */ if( p->inputNesting==MAX_INPUT_NESTING ){ - /* This will be more informative in a later version. */ - utf8_printf(stderr,"Input nesting limit (%d) reached at line %d." - " Check recursion.\n", MAX_INPUT_NESTING, p->lineno); + InSource *pInSrc = p->pInSource->pFrom; + const char *zLead = "Input nesting limit (" + SHELL_STRINGIFY(MAX_INPUT_NESTING)") reached,"; + int i = 3; + assert(pInSrc!=0 && MAX_INPUT_NESTING>0); + while( i-->0 && pInSrc!=0 ){ + utf8_printf(stderr, + "%s from line %d of \"%s\"", + zLead, pInSrc->lineno, pInSrc->zSourceSay); + zLead = (i%2==0)? "\n" : ""; + pInSrc=pInSrc->pFrom; + } + utf8_printf(stderr, " ...\nERROR: Check recursion.\n"); return 1; } ++p->inputNesting; diff --cc test/shell3.test index b42f2d534d,e5a0c124e0..e0791fb29d --- a/test/shell3.test +++ b/test/shell3.test @@@ -97,9 -97,9 +97,10 @@@ do_test shell3-2.6 catchcmd "foo.db" ".tables" } {0 {}} do_test shell3-2.7 { -- catchcmd "foo.db" "CREATE TABLE" - } {1 {Error: Input incomplete at line 1 of ""}} -} {1 {Parse error near line 1: incomplete input}} -- ++ catchcmd "foo.db" "CREATE TABLE;" ++} {1 {Parse error near line 1: near ";": syntax error ++ CREATE TABLE; ++ ^--- error here}} #---------------------------------------------------------------------------- # shell3-3.*: Basic tests for processing odd SQL constructs. diff --cc test/shell9.test index 5012183297,0000000000..d5df53d586 mode 100644,000000..100644 --- a/test/shell9.test +++ b/test/shell9.test @@@ -1,249 -1,0 +1,253 @@@ +# 2022 Feb 5 +# +# 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. +# +#*********************************************************************** +# +# The focus of this file is testing the CLI shell tool enhanced parsing, +# new .parameter subcommands and uses, and the new .x meta-command. +# +# + +# Test plan: +# +# shell9-1.*: command line parsing and acting accordingly +# shell9-2.*: Basic "dot" command, cross-line token parsing +# shell9-3.*: .parameter set options and types +# shell9-4.*: .parameter save/load operation +# shell9-5.*: Ensure "dot" commands and SQL intermix ok. +# shell9-6.*: .x command operation and refusal +# +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set CLI [test_find_cli] +db close +forcedelete test.db test.db-journal test.db-wal +forcedelete x.db xn.db + +sqlite3 db test.db + +#---------------------------------------------------------------------------- +# Test cases shell9-1.*: command line parsing and acting accordingly + +do_test shell9-1.1 { + set res [catchcmd ":memory: -cmd .quit" ""] +} {0 {}} + +do_test shell9-1.2 { + set res [catchcmd ":memory: -shxopts 1 -cmd .shxopts -cmd .quit" ""] +} {0 { name value "-shxopts set" + -------- ----- --------------- + parsing 1 "-shxopts 0x01" + all_opts 0 "-shxopts 0x07"}} + +do_test shell9-1.3 { + set res [catchcmd ":memory: -cmd .shxopts -cmd .quit" ""] +} {0 { name value "-shxopts set" + -------- ----- --------------- + parsing 0 "-shxopts 0x01" + all_opts 0 "-shxopts 0x07"}} + +#---------------------------------------------------------------------------- +# Test cases shell9-2.*: Basic "dot" command, cross-line token parsing + +set cmds ".print 'l1\nl2'\n.print 'a\\\nb'" +do_test shell9-2.1 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {l1 +l2 +ab}} + +set cmds " .print \"l1\nl2\"\n .print \"a\\\nb\" \n# c\n ## c" +do_test shell9-2.2 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {l1 +l2 +ab}} + +set cmds ".echo on\n.seeargs 'a'\\\n'b'\n#!" +do_test shell9-2.3 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {.seeargs 'a''b' +a|b|}} + +set cmds ".echo on\n.seeargs a\\\nb\n#!" +do_test shell9-2.4 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {.seeargs ab +ab|}} + +set cmds ".echo 1\n.print \"\\\"\nq\\\"\"" +do_test shell9-2.5 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {.print "\" +q\"" +" +q"}} + +#---------------------------------------------------------------------------- +# Test cases shell9-3.*: .parameter set options and types + +set cmds { +.pa set -b b x'a5a5' +.pa set -i ii 33-11 +.pa set -i ir 3.3-1.1 +.pa set -n ni 3-1 +.pa set -n nr 3.3-1.1 +.pa set -r ri 1 +.pa set -r rr 1.2 +.pa set -t t 123 +.mode list +select typeof(value) from temp.sqlite_parameters order by key; +} +do_test shell9-3.1 { + set res [catchcmd ":memory:" $cmds] +} {0 {blob +integer +integer +integer +real +real +real +text}} + +set cmds { +.pa set expr 1 + 2 * 3 +.pa set text "'1 + 2*3'" +.pa set -t ttext 1 + 2*3 +.pa list +} +do_test shell9-3.2 { + set res [catchcmd ":memory:" $cmds] +} {0 {name usage value +expr script 7 +text script '1 + 2*3' +ttext script '1 + 2*3'}} + +set cmds { +.pa set a "'a'" +.pa set b "'b'" +.pa set c "'c'" +.pa unset +.pa list +.pa clear a +.pa list +.pa unset b +.pa list +.pa clear +.pa list +.pa set d "'e'" +.pa set e "'e'" +.pa unset d e +.pa list +} +do_test shell9-3.3 { + set res [catchcmd ":memory:" $cmds] +} {0 {name usage value +a script 'a' +b script 'b' +c script 'c' +name usage value +b script 'b' +c script 'c' +name usage value +c script 'c' +name usage value +name usage value}} + +if {$::tcl_platform(platform)=="unix"} { + proc set_ed {sayWhat} { + global env + set env(VISUAL) "echo SELECT $sayWhat ';' >" + return 1 + } +} elseif {$::tcl_platform(platform)=="windows"} { + proc set_ed {sayWhat} { + global env + set env(VISUAL) "echo SELECT $sayWhat ; >" + return 1 + } +} else { return 0 } + +if {[set_ed @name]} { + set cmds { - .pa set @name Fido - .pa edit -t dog - .x dog - } ++.pa set @name Fido ++.pa edit -t dog ++.x dog ++ } + do_test shell9-3.4 { + set res [catchcmd ":memory: -quiet 1 -shxopts 1 -interactive" $cmds] - } {0 Fido} ++ } {0 {.pa set @name Fido ++.pa edit -t dog ++.x dog ++Fido ++ }} +} + +#---------------------------------------------------------------------------- +# Test cases shell9-4.*: .parameter save/load operation + +set cmds { + .pa set -t x '.print Ex' + .pa set -i $n 7 + .pa save xn.db + .pa save x.db x + .pa clear + .pa load xn.db + .pa list + .pa clear + .pa load x.db + .pa list + .pa clear + .pa load xn.db $n + .pa list +} +do_test shell9-4.1 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {name usage value +$n binding 7 +x script '.print Ex' +name usage value +x script '.print Ex' +name usage value +$n binding 7}} + +forcedelete x.db xn.db + +#---------------------------------------------------------------------------- +# Test cases shell9-5.*: Ensure "dot" commands and SQL intermix ok. + +set cmds { + .pa set -t mixed " + .print Hi. + select 'Hi.'; + .print 'Good\ + Bye.' + select 'Good'|| + ' Bye.'; + " + .x mixed +} +do_test shell9-5.1 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {Hi. +Hi. +Good Bye. +Good Bye.}} + +#---------------------------------------------------------------------------- +# Test cases shell9-6.*: .x command operation and refusal +set cmds { + .pa set -t $v '.print Ok' + .x $v +} +do_test shell9-6.1 { + set res [catchcmd ":memory: -bail -shxopts 1" $cmds] +} {1 {Skipping badly named $v. Run ".help x"}} + +finish_test