From: drh <> Date: Mon, 17 Nov 2025 16:44:13 +0000 (+0000) Subject: CLI comes up in legacy "list" mode if neither stdin or stdout are a tty, or X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=234d2e51ac984238a676c2d27e30eacb8431cf0b;p=thirdparty%2Fsqlite.git CLI comes up in legacy "list" mode if neither stdin or stdout are a tty, or if "--compat 20251114" or earlier is specified, or if "--batch" is used. New modes "batch" and "tty" select either legacy "list" mode or the newer "qbox" mode with limits. Make CVS output compatible with legacy. Break out ".import" into a separate subroutine in anticipation of forthcoming improvements. General code cleanup. FossilOrigin-Name: f6bfcea9a01493af182e9aa0d35df6f81bf9e36220df79139afa287fa43d9aa3 --- diff --git a/ext/qrf/README.md b/ext/qrf/README.md index b3acf5863c..a8d60f28ef 100644 --- a/ext/qrf/README.md +++ b/ext/qrf/README.md @@ -278,7 +278,7 @@ A value of QRF_BLOB_Sql means that BLOB values are shown as SQL BLOB literals: a prefix "`x'`" following by hexadecimal and ending with a final "`'`". -A value of QRF_BLOB_Hex means that BLOB values are shown as pure +A value of QRF_BLOB_Hex means that BLOB values are shown as hexadecimal text with no delimiters. A value of QRF_BLOB_Tcl means that BLOB values are shown as a @@ -604,10 +604,16 @@ Except the eEsp mode defaults to `QRF_ESC_On`, so that control characters are escaped, for safety. The **Csv** and **Quote** styles are simply variations on **List** -with different defaults. -**Csv** sets things up to generate valid CSV file output. -**Quote** displays a comma-separated list of SQL -value literals. +with hard-coded values for some of the sqlite3_qrf_spec settings: + + +
 QuoteCsv +
zColumnSep",""," +
zRowSep"\\n""\\r\\n" +
zNull"NULL""" +
eTextQRF_TEXT_SqlQRF_TEXT_Csv +
eBlobQRF_BLOB_SqlQRF_BLOB_Text +
The **Html** style generates HTML table content, just without the `..
` around the outside. diff --git a/ext/qrf/qrf.c b/ext/qrf/qrf.c index ca3eb7d812..fdee93a86f 100644 --- a/ext/qrf/qrf.c +++ b/ext/qrf/qrf.c @@ -2261,9 +2261,10 @@ qrf_reinit: case QRF_STYLE_Csv: { p->spec.eStyle = QRF_STYLE_List; p->spec.eText = QRF_TEXT_Csv; - p->spec.eBlob = QRF_BLOB_Tcl; + p->spec.eBlob = QRF_BLOB_Text; p->spec.zColumnSep = ","; p->spec.zRowSep = "\r\n"; + p->spec.zNull = ""; break; } case QRF_STYLE_Quote: { diff --git a/manifest b/manifest index 7cb99c345f..6dc92154c2 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\stest\scases\simpacted\sby\sthe\suse\sof\s~/.sqliterc -D 2025-11-17T00:15:14.616 +C CLI\scomes\sup\sin\slegacy\s"list"\smode\sif\sneither\sstdin\sor\sstdout\sare\sa\stty,\sor\nif\s"--compat\s20251114"\sor\searlier\sis\sspecified,\sor\sif\s"--batch"\sis\sused.\nNew\smodes\s"batch"\sand\s"tty"\sselect\seither\slegacy\s"list"\smode\sor\sthe\snewer\n"qbox"\smode\swith\slimits.\nMake\sCVS\soutput\scompatible\swith\slegacy.\s\sBreak\sout\s".import"\sinto\sa\sseparate\nsubroutine\sin\santicipation\sof\sforthcoming\simprovements.\s\sGeneral\scode\ncleanup. +D 2025-11-17T16:44:13.271 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -416,8 +416,8 @@ F ext/misc/wholenumber.c 0fa0c082676b7868bf2fa918e911133f2b349bcdceabd1198bba5f6 F ext/misc/windirent.h 02211ce51f3034c675f2dbf4d228194d51b3ee05734678bad5106fff6292e60c F ext/misc/zipfile.c 09e6e3a3ff40a99677de3c0bc6569bd5f4709b1844ac3d1c1452a456c5a62f1c F ext/misc/zorder.c bddff2e1b9661a90c95c2a9a9c7ecd8908afab5763256294dd12d609d4664eee -F ext/qrf/README.md 09dd538966d8ee32598fc010e7fe6755bd7190494953a02960a9c81197d20cf3 -F ext/qrf/qrf.c 290b95fa8613e11a90d5a5a92c32fa22ce415b01fa9578ff5070427b057ac02e +F ext/qrf/README.md dd565fd1ca0c46ea37dbf4d496e368b9ecade768c92669640bc106e039629016 +F ext/qrf/qrf.c b62a16a4d380223c6fe90ae28c33aa27b44af128a2e1e39c496d3452ac6ae14f F ext/qrf/qrf.h b4b3489b3b3683523fd248d15cf5945830643b036943efacdb772a3e00367aa2 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8 F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255 @@ -735,7 +735,7 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 5616fbcf3b833c7c705b24371828215ad0925d0c0073216c4f153348d5753f0a F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c ba9cd07ffa3277883c1986085f6ddc4320f4d35d5f212ab58df79a7ecc1a576a -F src/shell.c.in 64f777047c972c46df3f04597367980732a58aa0b58a21e4862812a225edee12 +F src/shell.c.in 4cae6e0560037d4916f2819e3ca576e71e4a40adee9fc273f320cd28644e99c6 F src/sqlite.h.in 795ce84cc136b4e74d882cf4fab56d2927c20b9af9fd2fcea27760a6fe50851b F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 F src/sqlite3ext.h 5d5330f5f8461f5ce74960436ddcfa53ecd09c2b8b23901e22ae38aec3243998 @@ -1507,7 +1507,7 @@ F test/printf2.test 3f55c1871a5a65507416076f6eb97e738d5210aeda7595a74ee895f2224c F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc F test/pushdown.test 46a626ef1c0ca79b85296ff2e078b9da20a50e9b804b38f441590c3987580ddd -F test/qrf01.test f46f498e447d988b52dfa1cd45c683b7212bb2056741a4eff306df185b9c00b1 +F test/qrf01.test 1caa611b69e07c6c134c3749f633ef014a97640b8f57f03c8fc0ca4d064b6dd2 F test/qrf02.test 39b4afdc000bedccdafc0aecf17638df67a67aaa2d2942865ae6abcc48ba0e92 F test/qrf03.test 9d88aeb5cdd53f050b7ab9bd203281f7c9d063c33f22f8808e441b7ac0874ccf F test/queryonly.test 5f653159e0f552f0552d43259890c1089391dcca @@ -1603,11 +1603,11 @@ F test/sharedA.test 64bdd21216dda2c6a3bd3475348ccdc108160f34682c97f2f51c19fc0e21 F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707 F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 -F test/shell1.test 88acfa38c2ecfd5beb25825fdd3a93f4a81573a736c04de82e6e377efa6f6efd -F test/shell2.test 38c394063f9398bc559fbe5b232c8e7f0c817dd33edd21ee8c4c4c1b650530db +F test/shell1.test b88af002858e1923f90fccf07f841d853e8fba568cecf38efd29f5172c661735 +F test/shell2.test 103140814bdc7508aa41dd3462413cbc4aa84b4261112cb8d501d74275cb7d48 F test/shell3.test 840192774cc4edf7653520c0434a311c7477b9bc324abbc7bd2887915792fa8c F test/shell4.test e25580a792b7b54560c3a76b6968bd8189261f38979fe28e6bc6312c5db280db -F test/shell5.test ed3c2cf47330134ab91e5b16dbdc34a7fb955ff2999ff69bac908cc2238b2b7f +F test/shell5.test 7a249400bb2af59ac8524f357f8cf2844a62b6ac5ff8ecd69b045ceb688700ae F test/shell6.test e3b883b61d4916b6906678a35f9d19054861123ad91b856461e0a456273bdbb8 F test/shell7.test 43fd8e511c533bab5232e95c7b4be93b243451709e89582600d4b6e67693d5c3 F test/shell8.test 641cf21a99c59404c24e3062923734951c4099a6b6b6520de00cf7a1249ee871 @@ -2176,8 +2176,8 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 2aebd7bfecaaf1f75b52b05a0d3009fc0dc61289ae666d24cb4e3ddfaf251645 -R 5f06e6da6beb48984fc94266490a7906 +P 8fc05faef91186429c6c710991fd736b1df9a9af946c29d207db2518d6436b38 +R bb02fe9715521dfd12626a15ebffae99 U drh -Z 490dbb4a529fbc5b5a8e8da8dff783f8 +Z 405f1a2cbb60052057d2e27df0bf6378 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 891e6da316..437b001ca5 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -8fc05faef91186429c6c710991fd736b1df9a9af946c29d207db2518d6436b38 +f6bfcea9a01493af182e9aa0d35df6f81bf9e36220df79139afa287fa43d9aa3 diff --git a/src/shell.c.in b/src/shell.c.in index dc1ad60b87..0453c1bd00 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -534,12 +534,17 @@ static int bail_on_error = 0; static int stdin_is_interactive = 1; /* -** On Windows systems we need to know if standard output is a console -** in order to show that UTF-16 translation is done in the sign-on -** banner. The following variable is true if it is the console. +** Treat stdout like a TTY if true. */ static int stdout_is_console = 1; +/* +** Use this value as the width of the output device. Or, figure it +** out at runtime if the value is negative. Or use a default width +** if this value is zero. +*/ +static int stdout_tty_width = -1; + /* ** The following is the open SQLite database. We make a pointer ** to this database a static variable so that it can be accessed @@ -1251,12 +1256,16 @@ typedef struct Mode { u8 autoEQPtrace; /* autoEQP is in trace mode */ u8 scanstatsOn; /* True to display scan stats before each finalize */ u8 bAutoScreenWidth; /* Using the TTY to determine screen width */ - u8 bEcho; /* True to echo all SQL to output */ + u8 mFlags; /* MFLG_ECHO and/or MFLG_CRLF */ u8 eMode; /* One of the MODE_ values */ - u8 crlfMode; /* Do NL-to-CRLF translations when enabled (maybe) */ sqlite3_qrf_spec spec; /* Spec to be passed into QRF */ } Mode; +/* Flags for Mode.mFlags */ +#define MFLG_ECHO 0x01 /* Echo inputs to output */ +#define MFLG_CRLF 0x02 /* Use CR/LF output line endings */ + + /* ** State information about the database connection is contained in an ** instance of the following structure. @@ -1426,8 +1435,14 @@ static const char *qrfQuoteNames[] = #define MODE_Tcl 19 /* Space-separated list of TCL strings */ #define MODE_Www 20 /* Full web-page output */ +#define MODE_BUILTIN 20 /* Maximum built-in mode */ +#define MODE_BATCH 50 /* Default mode for batch processing */ +#define MODE_TTY 51 /* Default mode for interactive processing */ +#define MODE_USER 75 /* First user-defined mode */ +#define MODE_N_USER 25 /* Maximum number of user-defined modes */ + /* -** Information about how each display-mode behaves. +** Information about built-in display modes */ typedef struct ModeInfo ModeInfo; struct ModeInfo { @@ -1442,6 +1457,8 @@ struct ModeInfo { unsigned char eStyle; /* Underlying QRF style */ unsigned char eCx; /* 0: other, 1: line, 2: columnar */ }; + +/* String constants used by built-in modes */ static const char *aModeStr[] = /* 0 1 2 3 4 5 6 7 8 */ { 0, "\n", "|", " ", ",", "\r\n", "\036", "\037", "\t", @@ -1455,7 +1472,7 @@ static const ModeInfo aModeInfo[] = { { "c", 4, 1, 10, 5, 5, 4, 1, 12, 0 }, { "column", 0, 0, 9, 1, 1, 1, 2, 2, 2 }, { "count", 0, 0, 0, 0, 0, 0, 0, 3, 0 }, - { "csv", 4, 5, 9, 3, 3, 1, 1, 12, 0 }, + { "csv", 4, 5, 9, 3, 3, 3, 1, 12, 0 }, { "html", 0, 0, 9, 4, 4, 1, 2, 7, 0 }, { "insert", 0, 0, 10, 2, 2, 2, 1, 8, 0 }, { "jatom", 4, 1, 11, 6, 6, 5, 1, 12, 0 }, @@ -1593,9 +1610,22 @@ static void modeChange(ShellState *p, unsigned char eMode){ pM->spec.eBlob = pI->eBlob; pM->spec.bTitles = pI->bHdr; pM->spec.eTitle = pI->eHdr; - }else if( eMode>=100 && eMode-100nSavedModes ){ + }else if( eMode>=MODE_USER && eMode-MODE_USERnSavedModes ){ + modeFree(&p->mode); + modeDup(&p->mode, &p->aSavedModes[eMode-MODE_USER].mode); + }else if( eMode==MODE_BATCH ){ + u8 mFlags = p->mode.mFlags; modeFree(&p->mode); - modeDup(&p->mode, &p->aSavedModes[eMode-100].mode); + modeChange(p, MODE_List); + p->mode.mFlags = mFlags; + }else if( eMode==MODE_TTY ){ + u8 mFlags = p->mode.mFlags; + modeFree(&p->mode); + modeChange(p, MODE_QBox); + p->mode.bAutoScreenWidth = 1; + p->mode.spec.nCharLimit = 300; + p->mode.spec.nLineLimit = 5; + p->mode.mFlags = mFlags; } } @@ -1607,13 +1637,10 @@ static void modeChange(ShellState *p, unsigned char eMode){ static void modeDefault(ShellState *p){ p->mode.spec.iVersion = 1; p->mode.autoExplain = 1; - if( p->iCompat>=20251115 ){ - modeChange(p, MODE_QBox); - p->mode.bAutoScreenWidth = 1; - p->mode.spec.nCharLimit = 300; - p->mode.spec.nLineLimit = 5; + if( p->iCompat>=20251115 && (stdin_is_interactive || stdout_is_console) ){ + modeChange(p, MODE_TTY); }else{ - modeChange(p, MODE_List); + modeChange(p, MODE_BATCH); } } @@ -1622,7 +1649,12 @@ static void modeDefault(ShellState *p){ ** the name does not match any mode. ** ** Saved modes are also searched if p!=NULL. The number returned -** for a saved mode is the index into the p->aSavedModes[] array plus 100. +** for a saved mode is the index into the p->aSavedModes[] array +** plus MODE_USER. +** +** Two special mode names are also available: "batch" and "tty". +** evaluate to the default mode for batch operation and interactive +** operation on a TTY, respectively. */ static int modeFind(ShellState *p, const char *zName){ int i; @@ -1630,8 +1662,10 @@ static int modeFind(ShellState *p, const char *zName){ if( cli_strcmp(aModeInfo[i].zName,zName)==0 ) return i; } for(i=0; inSavedModes; i++){ - if( cli_strcmp(p->aSavedModes[i].zTag,zName)==0 ) return i+100; + if( cli_strcmp(p->aSavedModes[i].zTag,zName)==0 ) return i+MODE_USER; } + if( strcmp(zName,"batch")==0 ) return MODE_BATCH; + if( strcmp(zName,"tty")==0 ) return MODE_TTY; return -1; } @@ -1901,7 +1935,7 @@ edit_func_end: */ static void setCrlfMode(ShellState *p){ #ifdef _WIN32 - if( p->mode.crlfMode ){ + if( p->mode.mFlags & MFLG_CRLF ){ sqlite3_fsetmode(p->out, _O_TEXT); }else{ sqlite3_fsetmode(p->out, _O_BINARY); @@ -2065,7 +2099,8 @@ static void interrupt_handler(int NotUsed){ /* Try to determine the screen width. Use the default if unable. */ -int shellScreenWidth(int dfltWidth){ +int shellScreenWidth(void){ + if( stdout_tty_width>0 ) return stdout_tty_width; #if defined(TIOCGSIZE) struct ttysize ts; if( ioctl(STDIN_FILENO, TIOCGSIZE, &ts)>=0 @@ -2091,7 +2126,8 @@ int shellScreenWidth(int dfltWidth){ return csbi.srWindow.Right - csbi.srWindow.Left + 1; } #endif - return dfltWidth; +#define DEFAULT_SCREEN_WIDTH 80 + return DEFAULT_SCREEN_WIDTH; } #if (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE) @@ -3142,7 +3178,7 @@ static int shell_exec( assert( pArg->mode.eMode>=0 && pArg->mode.eModemode.eMode].eStyle; if( pArg->mode.bAutoScreenWidth ){ - spec.nScreenWidth = shellScreenWidth(80); + spec.nScreenWidth = shellScreenWidth(); } #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) @@ -3660,19 +3696,6 @@ static const char *(azHelp[]) = { ".help ?-all? ?PATTERN? Show help text for PATTERN", #ifndef SQLITE_SHELL_FIDDLE ".import FILE TABLE Import data from FILE into TABLE", - " Options:", - " --ascii Use \\037 and \\036 as column and row separators", - " --csv Use , and \\n as column and row separators", - " --skip N Skip the first N rows of input", - " --schema S Target table to be S.TABLE", - " -v \"Verbose\" - increase auxiliary output", - " Notes:", - " * If TABLE does not exist, it is created. The first row of input", - " determines the column names.", - " * If neither --csv or --ascii are used, the input mode is derived", - " from the \".mode\" output mode", - " * If FILE begins with \"|\" then it is a command that generates the", - " input text.", #endif #ifndef SQLITE_OMIT_TEST_CONTROL ".imposter INDEX TABLE Create imposter table TABLE on index INDEX", @@ -7217,6 +7240,352 @@ static int pickStr(const char *zArg, char **pzErr, ...){ return -1; } +/* +** DOT-COMMAND: .import +** +** USAGE: .import [OPTIONS] FILE TABLE +** +** Import CSV or similar text from FILE into TABLE. If TABLE does +** not exist, it is created using the first row of FILE as the column +** names. If FILE begins with "|" then it is a command that is run +** and the output from the command is used as the input data. +** +** FILE is assumed to be in a CSV format, unless the current mode +** is "ascii" or "tabs" or unless one of the options below specify +** an alternative. +** +** Options: +** --ascii Use \037 and \036 as column and row separators on input +** --csv Input is standard RFC-4180 CSV. +** --schema S When creating TABLE, put it in schema S +** --skip N Ignore the first N rows of input +** -v Verbose mode +*/ +static int dotCmdImport(ShellState *p){ + int nArg = p->dot.nArg; /* Number of arguments */ + char **azArg = p->dot.azArg;/* Argument list */ + char *zTable = 0; /* Insert data into this table */ + char *zSchema = 0; /* Schema of zTable */ + char *zFile = 0; /* Name of file to extra content from */ + sqlite3_stmt *pStmt = NULL; /* A statement */ + int nCol; /* Number of columns in the table */ + i64 nByte; /* Number of bytes in an SQL string */ + int i, j; /* Loop counters */ + int needCommit; /* True to COMMIT or ROLLBACK at end */ + int nSep; /* Number of bytes in spec.zColumnSep */ + char *zSql = 0; /* 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 */ + i64 nSkip = 0; /* Initial lines to skip */ + int useOutputMode = 1; /* Use output mode to determine separators */ + char *zCreate = 0; /* CREATE TABLE statement text */ + int rc; /* Result code */ + + failIfSafeMode(p, "cannot run .import in safe mode"); + memset(&sCtx, 0, sizeof(sCtx)); + if( p->mode.eMode==MODE_Ascii ){ + xRead = ascii_read_one_field; + }else{ + xRead = csv_read_one_field; + } + for(i=1; iout, "ERROR: missing %s argument\n", + zFile==0 ? "FILE" : "TABLE"); + return 1; + } + seenInterrupt = 0; + open_db(p, 0); + if( useOutputMode ){ + /* If neither the --csv or --ascii options are specified, then set + ** the column and row separator characters from the output mode. */ + if( p->mode.spec.zColumnSep==0 ){ + modeSetStr(&p->mode.spec.zColumnSep, ","); + nSep = 1; + }else if( (nSep = strlen30(p->mode.spec.zColumnSep))==0 ){ + eputz("Error: non-null column separator required for import\n"); + return 1; + } + if( nSep>1 ){ + eputz("Error: multi-character column separators not allowed" + " for import\n"); + return 1; + } + if( p->mode.spec.zRowSep==0 ){ + modeSetStr(&p->mode.spec.zRowSep, "\n"); + nSep = 1; + }else if( (nSep = strlen30(p->mode.spec.zRowSep))==0 ){ + eputz("Error: non-null row separator required for import\n"); + return 1; + } + if( nSep==2 && p->mode.eMode==MODE_Csv + && cli_strcmp(p->mode.spec.zRowSep,SEP_CrLf)==0 + ){ + /* When importing CSV (only), if the row separator is set to the + ** default output row separator, change it to the default input + ** row separator. This avoids having to maintain different input + ** and output row separators. */ + modeSetStr(&p->mode.spec.zRowSep, SEP_Row); + nSep = strlen30(p->mode.spec.zRowSep); + } + if( nSep>1 ){ + eputz("Error: multi-character row separators not allowed" + " for import\n"); + return 1; + } + sCtx.cColSep = (u8)p->mode.spec.zColumnSep[0]; + sCtx.cRowSep = (u8)p->mode.spec.zRowSep[0]; + } + sCtx.zFile = zFile; + sCtx.nLine = 1; + if( sCtx.zFile[0]=='|' ){ +#ifdef SQLITE_OMIT_POPEN + eputz("Error: pipes are not supported in this OS\n"); + return 1; +#else + sCtx.in = sqlite3_popen(sCtx.zFile+1, "r"); + sCtx.zFile = ""; + sCtx.xCloser = pclose; +#endif + }else{ + sCtx.in = sqlite3_fopen(sCtx.zFile, "rb"); + sCtx.xCloser = fclose; + } + if( sCtx.in==0 ){ + cli_printf(stderr,"Error: cannot open \"%s\"\n", zFile); + return 1; + } + if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){ + char zSep[2]; + zSep[1] = 0; + zSep[0] = sCtx.cColSep; + cli_puts("Column separator ", p->out); + output_c_string(p->out, zSep); + cli_puts(", row separator ", p->out); + zSep[0] = sCtx.cRowSep; + output_c_string(p->out, zSep); + cli_puts("\n", p->out); + } + sCtx.z = sqlite3_malloc64(120); + if( sCtx.z==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + /* Below, resources must be freed before exit. */ + while( nSkip>0 ){ + nSkip--; + while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){} + } + import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ + if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0) + && 0==db_int(p->db, "SELECT count(*) FROM \"%w\".sqlite_schema" + " WHERE name=%Q AND type='view'", + zSchema ? zSchema : "main", zTable) + ){ + /* Table does not exist. Create it. */ + sqlite3 *dbCols = 0; + char *zRenames = 0; + char *zColDefs; + zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"", + zSchema ? zSchema : "main", zTable); + while( xRead(&sCtx) ){ + zAutoColumn(sCtx.z, &dbCols, 0); + if( sCtx.cTerm!=sCtx.cColSep ) break; + } + zColDefs = zAutoColumn(0, &dbCols, &zRenames); + if( zRenames!=0 ){ + cli_printf((stdin_is_interactive && p->in==stdin)? 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 ){ + cli_printf(stderr,"%s: empty file\n", sCtx.zFile); + import_cleanup(&sCtx); + sqlite3_free(zCreate); + return 1; + } + zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs); + if( zCreate==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + if( eVerbose>=1 ){ + cli_printf(p->out, "%s\n", zCreate); + } + rc = sqlite3_exec(p->db, zCreate, 0, 0, 0); + if( rc ){ + cli_printf(stderr, + "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db)); + } + sqlite3_free(zCreate); + zCreate = 0; + if( rc ){ + import_cleanup(&sCtx); + return 1; + } + } + zSql = sqlite3_mprintf("SELECT count(*) FROM pragma_table_info(%Q,%Q);", + zTable, zSchema); + if( zSql==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + zSql = 0; + if( rc ){ + if (pStmt) sqlite3_finalize(pStmt); + shellDatabaseError(p->db); + import_cleanup(&sCtx); + return 1; + } + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + nCol = sqlite3_column_int(pStmt, 0); + }else{ + nCol = 0; + } + sqlite3_finalize(pStmt); + pStmt = 0; + if( nCol==0 ) return 0; /* no columns, no error */ + + nByte = 64 /* space for "INSERT INTO", "VALUES(", ")\0" */ + + (zSchema ? strlen(zSchema)*2 + 2: 0) /* Quoted schema name */ + + strlen(zTable)*2 + 2 /* Quoted table name */ + + nCol*2; /* Space for ",?" for each column */ + zSql = sqlite3_malloc64( nByte ); + if( zSql==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + if( zSchema ){ + sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\".\"%w\" VALUES(?", + zSchema, zTable); + }else{ + sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\" VALUES(?", zTable); + } + j = strlen30(zSql); + for(i=1; i=2 ){ + cli_printf(p->out, "Insert using: %s\n", zSql); + } + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + zSql = 0; + if( rc ){ + shellDatabaseError(p->db); + if (pStmt) sqlite3_finalize(pStmt); + import_cleanup(&sCtx); + return 1; + } + needCommit = sqlite3_get_autocommit(p->db); + if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0); + do{ + int startLine = sCtx.nLine; + for(i=0; imode.eMode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; + /* + ** For CSV mode, per RFC 4180, accept EOF in lieu of final + ** record terminator but only for last field of multi-field row. + ** (If there are too few fields, it's not valid CSV anyway.) + */ + if( z==0 && (xRead==csv_read_one_field) && i==nCol-1 && i>0 ){ + z = ""; + } + sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); + if( i=nCol ){ + sqlite3_step(pStmt); + rc = sqlite3_reset(pStmt); + if( rc!=SQLITE_OK ){ + cli_printf(stderr,"%s:%d: INSERT failed: %s\n", + sCtx.zFile, startLine, sqlite3_errmsg(p->db)); + sCtx.nErr++; + }else{ + sCtx.nRow++; + } + } + }while( sCtx.cTerm!=EOF ); + + import_cleanup(&sCtx); + sqlite3_finalize(pStmt); + if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0); + if( eVerbose>0 ){ + cli_printf(p->out, + "Added %d rows with %d errors using %d lines of input\n", + sCtx.nRow, sCtx.nErr, sCtx.nLine-1); + } + return 0; +} + + /* ** This function computes what to show the user about the configured ** titles (or column-names). Output is an integer between 0 and 3: @@ -7352,12 +7721,6 @@ static int dotCmdMode(ShellState *p){ modeSetStr(&p->mode.spec.zTableName, azArg[i]); } chng = 1; - }else if( z[0]=='2' && strlen(z)==8 && atoi(z)>20000101 ){ - modeFree(&p->mode); - p->iCompat = atoi(z); - memset(&p->mode, 0, sizeof(p->mode)); - modeDefault(p); - chng = 1; }else if( optionMatch(z,"align") ){ char *zAlign; int nAlign; @@ -7442,7 +7805,7 @@ static int dotCmdMode(ShellState *p){ for(ii=0; iinSavedModes; ii++){ cli_printf(p->out, " %s", p->aSavedModes[ii].zTag); } - cli_puts("\n", p->out); + cli_puts(" batch tty\n", p->out); }else if( optionMatch(z,"quote") ){ if( i+1nSavedModes > 100 ){ + if( p->nSavedModes > MODE_N_USER ){ dotCmdError(p, i-1, "cannot add more modes", 0); return 1; } @@ -7983,7 +8346,7 @@ static int dotCmdOutput(ShellState *p){ if( eMode=='x' ){ /* spreadsheet mode. Output as CSV. */ newTempFile(p, "csv"); - p->mode.bEcho = 0; + p->mode.mFlags &= ~MFLG_ECHO; p->mode.eMode = MODE_Csv; modeSetStr(&p->mode.spec.zColumnSep, SEP_Comma); modeSetStr(&p->mode.spec.zRowSep, SEP_CrLf); @@ -8382,12 +8745,17 @@ static int do_meta_command(const char *zLine, ShellState *p){ ){ if( nArg==2 ){ #ifdef _WIN32 - p->mode.crlfMode = booleanValue(azArg[1]); + if( booleanValue(azArg[1]) ){ + p->mode.mFlags |= MFLG_CRLF; + }else{ + p->mode.mFlags &= ~MFLG_CRLF; + } #else - p->mode.crlfMode = 0; + p->mode.mFlags &= ~MFLG_CRLF; #endif } - cli_printf(stderr, "crlf is %s\n", p->mode.crlfMode ? "ON" : "OFF"); + cli_printf(stderr, "crlf is %s\n", + (p->mode.mFlags & MFLG_CRLF)!=0 ? "ON" : "OFF"); }else if( c=='d' && n>1 && cli_strncmp(azArg[0], "databases", n)==0 ){ @@ -8602,7 +8970,11 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( c=='e' && cli_strncmp(azArg[0], "echo", n)==0 ){ if( nArg==2 ){ - p->mode.bEcho = booleanValue(azArg[1]); + if( booleanValue(azArg[1]) ){ + p->mode.mFlags |= MFLG_ECHO; + }else{ + p->mode.mFlags &= ~MFLG_ECHO; + } }else{ eputz("Usage: .echo on|off\n"); rc = 1; @@ -8910,331 +9282,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ #ifndef SQLITE_SHELL_FIDDLE if( c=='i' && cli_strncmp(azArg[0], "import", n)==0 ){ - char *zTable = 0; /* Insert data into this table */ - char *zSchema = 0; /* Schema of zTable */ - char *zFile = 0; /* Name of file to extra content from */ - sqlite3_stmt *pStmt = NULL; /* A statement */ - int nCol; /* Number of columns in the table */ - i64 nByte; /* Number of bytes in an SQL string */ - int i, j; /* Loop counters */ - int needCommit; /* True to COMMIT or ROLLBACK at end */ - int nSep; /* Number of bytes in spec.zColumnSep */ - char *zSql = 0; /* 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 */ - i64 nSkip = 0; /* Initial lines to skip */ - int useOutputMode = 1; /* Use output mode to determine separators */ - char *zCreate = 0; /* CREATE TABLE statement text */ - - failIfSafeMode(p, "cannot run .import in safe mode"); - memset(&sCtx, 0, sizeof(sCtx)); - if( p->mode.eMode==MODE_Ascii ){ - xRead = ascii_read_one_field; - }else{ - xRead = csv_read_one_field; - } - rc = 1; - for(i=1; iout, "ERROR: extra argument: \"%s\". Usage:\n",z); - showHelp(p->out, "import"); - goto meta_command_exit; - } - }else if( cli_strcmp(z,"-v")==0 ){ - eVerbose++; - }else if( cli_strcmp(z,"-schema")==0 && iout, "ERROR: unknown option: \"%s\". Usage:\n", z); - showHelp(p->out, "import"); - goto meta_command_exit; - } - } - if( zTable==0 ){ - cli_printf(p->out, "ERROR: missing %s argument. Usage:\n", - zFile==0 ? "FILE" : "TABLE"); - showHelp(p->out, "import"); - goto meta_command_exit; - } - seenInterrupt = 0; - open_db(p, 0); - if( useOutputMode ){ - /* If neither the --csv or --ascii options are specified, then set - ** the column and row separator characters from the output mode. */ - if( p->mode.spec.zColumnSep==0 ){ - modeSetStr(&p->mode.spec.zColumnSep, ","); - nSep = 1; - }else if( (nSep = strlen30(p->mode.spec.zColumnSep))==0 ){ - eputz("Error: non-null column separator required for import\n"); - goto meta_command_exit; - } - if( nSep>1 ){ - eputz("Error: multi-character column separators not allowed" - " for import\n"); - goto meta_command_exit; - } - if( p->mode.spec.zRowSep==0 ){ - modeSetStr(&p->mode.spec.zRowSep, "\n"); - nSep = 1; - }else if( (nSep = strlen30(p->mode.spec.zRowSep))==0 ){ - eputz("Error: non-null row separator required for import\n"); - goto meta_command_exit; - } - if( nSep==2 && p->mode.eMode==MODE_Csv - && cli_strcmp(p->mode.spec.zRowSep,SEP_CrLf)==0 - ){ - /* When importing CSV (only), if the row separator is set to the - ** default output row separator, change it to the default input - ** row separator. This avoids having to maintain different input - ** and output row separators. */ - modeSetStr(&p->mode.spec.zRowSep, SEP_Row); - nSep = strlen30(p->mode.spec.zRowSep); - } - if( nSep>1 ){ - eputz("Error: multi-character row separators not allowed" - " for import\n"); - goto meta_command_exit; - } - sCtx.cColSep = (u8)p->mode.spec.zColumnSep[0]; - sCtx.cRowSep = (u8)p->mode.spec.zRowSep[0]; - } - sCtx.zFile = zFile; - sCtx.nLine = 1; - if( sCtx.zFile[0]=='|' ){ -#ifdef SQLITE_OMIT_POPEN - eputz("Error: pipes are not supported in this OS\n"); - goto meta_command_exit; -#else - sCtx.in = sqlite3_popen(sCtx.zFile+1, "r"); - sCtx.zFile = ""; - sCtx.xCloser = pclose; -#endif - }else{ - sCtx.in = sqlite3_fopen(sCtx.zFile, "rb"); - sCtx.xCloser = fclose; - } - if( sCtx.in==0 ){ - cli_printf(stderr,"Error: cannot open \"%s\"\n", zFile); - goto meta_command_exit; - } - if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){ - char zSep[2]; - zSep[1] = 0; - zSep[0] = sCtx.cColSep; - cli_puts("Column separator ", p->out); - output_c_string(p->out, zSep); - cli_puts(", row separator ", p->out); - zSep[0] = sCtx.cRowSep; - output_c_string(p->out, zSep); - cli_puts("\n", p->out); - } - sCtx.z = sqlite3_malloc64(120); - if( sCtx.z==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - /* Below, resources must be freed before exit. */ - while( nSkip>0 ){ - nSkip--; - while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){} - } - import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ - if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0) - && 0==db_int(p->db, "SELECT count(*) FROM \"%w\".sqlite_schema" - " WHERE name=%Q AND type='view'", - zSchema ? zSchema : "main", zTable) - ){ - /* Table does not exist. Create it. */ - sqlite3 *dbCols = 0; - char *zRenames = 0; - char *zColDefs; - zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"", - zSchema ? zSchema : "main", zTable); - while( xRead(&sCtx) ){ - zAutoColumn(sCtx.z, &dbCols, 0); - if( sCtx.cTerm!=sCtx.cColSep ) break; - } - zColDefs = zAutoColumn(0, &dbCols, &zRenames); - if( zRenames!=0 ){ - cli_printf((stdin_is_interactive && p->in==stdin)? 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 ){ - cli_printf(stderr,"%s: empty file\n", sCtx.zFile); - import_cleanup(&sCtx); - rc = 1; - sqlite3_free(zCreate); - goto meta_command_exit; - } - zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs); - if( zCreate==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - if( eVerbose>=1 ){ - cli_printf(p->out, "%s\n", zCreate); - } - rc = sqlite3_exec(p->db, zCreate, 0, 0, 0); - if( rc ){ - cli_printf(stderr, - "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db)); - } - sqlite3_free(zCreate); - zCreate = 0; - if( rc ){ - import_cleanup(&sCtx); - rc = 1; - goto meta_command_exit; - } - } - zSql = sqlite3_mprintf("SELECT count(*) FROM pragma_table_info(%Q,%Q);", - zTable, zSchema); - if( zSql==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); - zSql = 0; - if( rc ){ - if (pStmt) sqlite3_finalize(pStmt); - shellDatabaseError(p->db); - import_cleanup(&sCtx); - rc = 1; - goto meta_command_exit; - } - if( sqlite3_step(pStmt)==SQLITE_ROW ){ - nCol = sqlite3_column_int(pStmt, 0); - }else{ - nCol = 0; - } - sqlite3_finalize(pStmt); - pStmt = 0; - if( nCol==0 ) return 0; /* no columns, no error */ - - nByte = 64 /* space for "INSERT INTO", "VALUES(", ")\0" */ - + (zSchema ? strlen(zSchema)*2 + 2: 0) /* Quoted schema name */ - + strlen(zTable)*2 + 2 /* Quoted table name */ - + nCol*2; /* Space for ",?" for each column */ - zSql = sqlite3_malloc64( nByte ); - if( zSql==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - if( zSchema ){ - sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\".\"%w\" VALUES(?", - zSchema, zTable); - }else{ - sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\" VALUES(?", zTable); - } - j = strlen30(zSql); - for(i=1; i=2 ){ - cli_printf(p->out, "Insert using: %s\n", zSql); - } - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); - zSql = 0; - if( rc ){ - shellDatabaseError(p->db); - if (pStmt) sqlite3_finalize(pStmt); - import_cleanup(&sCtx); - rc = 1; - goto meta_command_exit; - } - needCommit = sqlite3_get_autocommit(p->db); - if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0); - do{ - int startLine = sCtx.nLine; - for(i=0; imode.eMode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; - /* - ** For CSV mode, per RFC 4180, accept EOF in lieu of final - ** record terminator but only for last field of multi-field row. - ** (If there are too few fields, it's not valid CSV anyway.) - */ - if( z==0 && (xRead==csv_read_one_field) && i==nCol-1 && i>0 ){ - z = ""; - } - sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); - if( i=nCol ){ - sqlite3_step(pStmt); - rc = sqlite3_reset(pStmt); - if( rc!=SQLITE_OK ){ - cli_printf(stderr,"%s:%d: INSERT failed: %s\n", - sCtx.zFile, startLine, sqlite3_errmsg(p->db)); - sCtx.nErr++; - }else{ - sCtx.nRow++; - } - } - }while( sCtx.cTerm!=EOF ); - - import_cleanup(&sCtx); - sqlite3_finalize(pStmt); - if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0); - if( eVerbose>0 ){ - cli_printf(p->out, - "Added %d rows with %d errors using %d lines of input\n", - sCtx.nRow, sCtx.nErr, sCtx.nLine-1); - } + rc = dotCmdImport(p); }else #endif /* !defined(SQLITE_SHELL_FIDDLE) */ @@ -10629,7 +10677,8 @@ static int do_meta_command(const char *zLine, ShellState *p){ rc = 1; goto meta_command_exit; } - cli_printf(p->out, "%12.12s: %s\n","echo", azBool[p->mode.bEcho!=0]); + cli_printf(p->out, "%12.12s: %s\n","echo", + azBool[(p->mode.mFlags & MFLG_ECHO)!=0]); cli_printf(p->out, "%12.12s: %s\n","eqp", azBool[p->mode.autoEQP&3]); cli_printf(p->out, "%12.12s: %s\n","explain", p->mode.autoExplain ? "auto" : "off"); @@ -10784,7 +10833,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ len = strlen30(azResult[i]); if( len>maxlen ) maxlen = len; } - nPrintCol = 80/(maxlen+2); + nPrintCol = shellScreenWidth()/(maxlen+2); if( nPrintCol<1 ) nPrintCol = 1; nPrintRow = (nRow + nPrintCol - 1)/nPrintCol; for(i=0; imode.bEcho ){ + if( p->mode.mFlags & MFLG_ECHO ){ cli_printf(p->out, "%s\n", zDo); fflush(p->out); } @@ -12175,6 +12224,7 @@ static const char zOptions[] = " -quote set output mode to 'quote'\n" " -readonly open the database read-only\n" " -safe enable safe-mode\n" + " -screenwidth N use N as the default screenwidth \n" " -separator SEP set output column separator. Default: '|'\n" #ifdef SQLITE_ENABLE_SORTER_REFERENCES " -sorterref SIZE sorter references threshold size\n" @@ -12476,6 +12526,15 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ ** we do the actual processing of arguments later in a second pass. */ stdin_is_interactive = 0; + stdout_is_console = 0; + modeChange(&data, MODE_BATCH); + }else if( cli_strcmp(z,"-screenwidth")==0 ){ + int n = atoi(cmdline_option_value(argc, argv, ++i)); + if( n<2 ){ + sqlite3_fprintf(stderr,"minimum --screenwidth is 2\n"); + exit(1); + } + stdout_tty_width = n; }else if( cli_strcmp(z,"-compat")==0 ){ data.iCompat = atoi(cmdline_option_value(argc, argv, ++i)); modeFree(&data.mode); @@ -12761,7 +12820,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ }else if( cli_strcmp(z,"-noheader")==0 ){ data.mode.spec.bTitles = QRF_No; }else if( cli_strcmp(z,"-echo")==0 ){ - data.mode.bEcho = 1; + data.mode.mFlags |= MFLG_ECHO; }else if( cli_strcmp(z,"-eqp")==0 ){ data.mode.autoEQP = AUTOEQP_on; }else if( cli_strcmp(z,"-eqpfull")==0 ){ @@ -12790,6 +12849,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ stdin_is_interactive = 1; }else if( cli_strcmp(z,"-batch")==0 ){ /* already handled */ + }else if( cli_strcmp(z,"-screenwidth")==0 ){ + i++; }else if( cli_strcmp(z,"-compat")==0 ){ i++; }else if( cli_strcmp(z,"-utf8")==0 ){ diff --git a/test/qrf01.test b/test/qrf01.test index a9f6d4c83d..c79a32defe 100644 --- a/test/qrf01.test +++ b/test/qrf01.test @@ -132,15 +132,24 @@ do_test 1.50 { db format -style count {SELECT * FROM t1} } 2 -do_test 1.60 { - db format -style csv {SELECT * FROM t1} +do_test 1.60a { + db format -style list -columnsep , -rowsep \r\n -text csv -blob tcl {SELECT * FROM t1} } "1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n" -do_test 1.61 { - db format -style csv -title auto {SELECT * FROM t1} +do_test 1.60b { + db format -style csv -columnsep xyz -rowsep pqr -text sql -blob sql {SELECT * FROM t1} +} "1,2.5,three\r\nBLOB,,\"Ἀμήν\"\r\n" +do_test 1.61a { + db format -style list -columnsep , -rowsep \r\n -text csv -title auto -blob tcl {SELECT * FROM t1} } "a,b,c\r\n1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n" -do_test 1.62 { - db format -style csv -title csv {SELECT a AS 'a x y', b, c FROM t1} +do_test 1.61b { + db format -style csv -title auto -blob tcl {SELECT * FROM t1} +} "a,b,c\r\n1,2.5,three\r\nBLOB,,\"Ἀμήν\"\r\n" +do_test 1.62a { + db format -style list -columnsep , -rowsep \r\n -text csv -title csv -blob tcl {SELECT a AS 'a x y', b, c FROM t1} } "\"a x y\",b,c\r\n1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n" +do_test 1.62b { + db format -style csv -title csv -blob tcl {SELECT a AS 'a x y', b, c FROM t1} +} "\"a x y\",b,c\r\n1,2.5,three\r\nBLOB,,\"Ἀμήν\"\r\n" do_test 1.70 { set result "\n[db format -style html {SELECT * FROM t1}]" diff --git a/test/shell1.test b/test/shell1.test index 3f0e19e7be..1a502336c3 100644 --- a/test/shell1.test +++ b/test/shell1.test @@ -426,7 +426,8 @@ do_test shell1-3.11.2 { do_test shell1-3.11.3 { # too many arguments catchcmd "test.db" ".import FOO BAR BAD" -} {/1 .ERROR: extra argument: "BAD".*./} +} {1 {line 1: .import FOO BAR BAD +line 1: ^--- unknown argument}} # .indexes ?TABLE? Show names of all indexes # If TABLE specified, only show indexes for tables @@ -456,7 +457,7 @@ do_test shell1-3.12.3 { # tabs Tab-separated values # tcl TCL list elements do_test shell1-3.13.1 { - catchcmd "test.db" ".mode 20250101\n.mode" + catchcmd "test.db" ".mode batch\n.mode" } {0 {current output mode: list}} do_test shell1-3.13.2 { catchcmd "test.db" ".mode FOO" @@ -510,7 +511,7 @@ do_test shell1-3.15.1 { .print x" } {0 x} do_test shell1-3.15.2 { - catchcmd "test.db" ".mode 20250101\n.output FOO + catchcmd "test.db" ".mode batch\n.output FOO .print x .output SELECT readfile('FOO');" @@ -632,7 +633,7 @@ do_test shell1-3.22.4 { # .show Show the current values for various settings do_test shell1-3.23.1 { - set res [catchcmd "test.db" ".mode 20250101\n.show"] + set res [catchcmd "test.db" ".mode batch\n.show"] list [regexp {echo:} $res] \ [regexp {explain:} $res] \ [regexp {headers:} $res] \ @@ -668,7 +669,7 @@ do_test shell1-3.23b.4 { # Adverse interaction between .stats and .eqp # do_test shell1-3.23b.5 { - catchcmd "test.db" [string map {"\n " "\n"} {.mode 20250101 + catchcmd "test.db" [string map {"\n " "\n"} {.mode batch CREATE TEMP TABLE t1(x); INSERT INTO t1 VALUES(1),(2); .stats on @@ -753,7 +754,7 @@ do_test shell1-3.27.4 { do_test shell1-3.28.1 { catchcmd test.db \ - ".mode 20250101\n.log stdout\nSELECT coalesce(sqlite_log(123,'hello'),'456');" + ".mode batch\n.log stdout\nSELECT coalesce(sqlite_log(123,'hello'),'456');" } "0 {(123) hello\n456}" do_test shell1-3-29.1 { @@ -1263,7 +1264,7 @@ do_test shell1-7.1.7 { # information. # do_test shell1-8.1 { - catchcmd ":memory:" {.mode 20250101 + catchcmd ":memory:" {.mode batch -- The pow2 table will hold all the necessary powers of two. CREATE TABLE pow2(x INTEGER PRIMARY KEY, v TEXT); WITH RECURSIVE c(x,v) AS ( diff --git a/test/shell2.test b/test/shell2.test index dcf2942e18..274f758e2f 100644 --- a/test/shell2.test +++ b/test/shell2.test @@ -75,7 +75,7 @@ do_test shell2-1.3 { # NB. whitespace is important do_test shell2-1.4.1 { forcedelete foo.db - catchcmd "foo.db" {.mode 20250101 + catchcmd "foo.db" {.mode batch CREATE TABLE foo(a); INSERT INTO foo(a) VALUES(1); SELECT * FROM foo;} @@ -98,7 +98,7 @@ SELECT * FROM foo; do_test shell2-1.4.3 { forcedelete foo.db catchcmd "foo.db" { -.mode 20250101 +.mode batch .echo ON CREATE TABLE foo(a); INSERT INTO foo(a) VALUES(1); @@ -114,7 +114,7 @@ SELECT * FROM foo; do_test shell2-1.4.4 { forcedelete foo.db catchcmd "foo.db" { -.mode 20250101 +.mode batch .echo ON CREATE TABLE foo(a); .echo OFF @@ -130,7 +130,7 @@ SELECT * FROM foo;} do_test shell2-1.4.5 { forcedelete foo.db catchcmdex "foo.db" { -.mode 20250101 +.mode batch .echo ON CREATE TABLE foo1(a); INSERT INTO foo1(a) VALUES(1); @@ -161,7 +161,7 @@ SELECT * FROM foo1; SELECT * FROM foo2; do_test shell2-1.4.6 { forcedelete foo.db catchcmdex "foo.db" { -.mode 20250101 +.mode batch .echo ON .headers ON CREATE TABLE foo1(a); @@ -217,7 +217,7 @@ do_test shell2-1.4.9 { do_test shell2-1.4.9 { forcedelete clone.db set res [catchcmd :memory: [string trim { -.mode 20250101 +.mode batch CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT); INSERT INTO t VALUES (1),(2); .clone clone.db @@ -232,7 +232,7 @@ ifcapable vtab { # See overflow report at https://sqlite.org/forum/forumpost/5d34ce5280 do_test shell2-1.4.10 { set res [catchcmd :memory: [string trim { - .mode 20250101 + .mode batch SELECT * FROM generate_series(9223372036854775807,9223372036854775807,1); SELECT * FROM generate_series(9223372036854775807,9223372036854775807,-1); SELECT avg(value),min(value),max(value) FROM generate_series( @@ -261,7 +261,7 @@ do_test shell2-1.4.10 { 2}} do_test shell2-1.4.10b { set res [catchcmd :memory: [string trim { - .mode 20251116 + .mode tty .print SELECT * FROM generate_series(9223372036854775807,9223372036854775807,1); SELECT * FROM generate_series(9223372036854775807,9223372036854775807,-1); @@ -340,7 +340,7 @@ do_test shell2-1.4.11 { # Bug from forum post 7cbe081746dd3803 # Keywords as column names were producing an error message. do_test shell2-1.4.12 { - set res [catchcmd :memory: [string trim {.mode 20250101 + set res [catchcmd :memory: [string trim {.mode batch CREATE TABLE "group"("order" text); INSERT INTO "group" VALUES ('ABC'); .sha3sum}]] diff --git a/test/shell5.test b/test/shell5.test index de64c3198a..2c8181579b 100644 --- a/test/shell5.test +++ b/test/shell5.test @@ -40,7 +40,8 @@ do_test shell5-1.1.2 { do_test shell5-1.1.3 { # too many arguments catchcmd "test.db" ".import FOO BAR BAD" -} {/1 .ERROR: extra argument.*/} +} {1 {line 1: .import FOO BAR BAD +line 1: ^--- unknown argument}} # .separator STRING Change separator used by output mode and .import do_test shell5-1.2.1 {