CLI: Enhancements to columnar output modes. (See

[forum/forumpost/5b53cd851f66441f9|forum post 5b53cd851] for discussion.)
New output mode "qbox".  New output options "--quote" and "--wrap N".

FossilOrigin-Name: 539cef5214446a7181614793e9cf323e95ba00ba0f888585b14b598dd2ff0808
This commit is contained in:
drh 2022-01-31 14:14:29 +00:00
parent 92ecf306ec
commit e40f28618c
4 changed files with 167 additions and 65 deletions

View File

@ -1,5 +1,5 @@
C Fix\san\sobscure\sproblem\sin\ssqlite3_backup_init()\scaused\sby\n[6a45d8fe8bfbc11a|check-in\s6a45d8fe8bfbc11a].\s\sSee\n[forum:/forumpost/8b39fbf3e7b5c278|forum\spost\s8b39fbf3e7]\sfor\nthe\soriginal\sbug\sreport.
D 2022-01-31T12:29:14.283
C CLI:\sEnhancements\sto\scolumnar\soutput\smodes.\s\s(See\n[forum/forumpost/5b53cd851f66441f9|forum\spost\s5b53cd851]\sfor\sdiscussion.)\nNew\soutput\smode\s"qbox".\s\sNew\soutput\soptions\s"--quote"\sand\s"--wrap\sN".
D 2022-01-31T14:14:29.581
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -553,7 +553,7 @@ F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
F src/resolve.c 24032ae57aec10df2f3fa2e20be0aae7d256bc704124b76c52d763440c7c0fe9
F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
F src/select.c a6d2d4bed279d7fe4fcedaf297eaf6441e8e17c6e3947a32d24d23be52ac02f2
F src/shell.c.in c0fa21db3a4b0f6204b5cc6b14eb452933f5588ae0b4840144380ebd876e8e90
F src/shell.c.in d53f77fd4733a6cada8e0d2eb2936fcae3fbe885ac08e48d1e525f22206bf579
F src/sqlite.h.in eaade58049152dac850d57415bcced885ca27ae9582f8aea2cfb7f1db78a521b
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h 5d54cf13d3406d8eb65d921a0d3c349de6126b732e695e79ecd4830ce86b4f8a
@ -1386,7 +1386,7 @@ F test/sharedA.test 49d87ec54ab640fbbc3786ee3c01de94aaa482a3a9f834ad3fe92770eb69
F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e
F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939
F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
F test/shell1.test 70f46b5d07776a107335c3c2c9cbd0431d44637bfeae1f6b9ded5e33b4c7c0bf
F test/shell1.test ce2f370886645f38fabdde44976c14a004400f166edea8fdd9741079b645fef6
F test/shell2.test f00a0501c00583cbc46f7510e1d713366326b2b3e63d06d15937284171a8787c
F test/shell3.test cb4b835a901742c9719437a89171172ecc4a8823ad97349af8e4e841e6f82566
F test/shell4.test 8f6c0fce4abed19a8a7f7262517149812a04caa905d01bdc8f5e92573504b759
@ -1942,8 +1942,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 454af48724c78bf8e06379a426e01b1597f56fcc83211ee062ff0dbeb6a140b1
R 20eccfa39f79364ced00d16db0de3987
P 639fc7633bd740421d6b48617b9f68e6be525107e4049673fe720ea6158a393f
R fd24c84f38ee6d8d42b7ffa22962306f
U drh
Z 97bbf817faea1d9cef9cd8d644f59252
Z 929d8043c6f32e2330ec9eb4d8badd53
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
639fc7633bd740421d6b48617b9f68e6be525107e4049673fe720ea6158a393f
539cef5214446a7181614793e9cf323e95ba00ba0f888585b14b598dd2ff0808

View File

@ -1084,6 +1084,8 @@ struct ShellState {
u8 eTraceType; /* SHELL_TRACE_* value for type of trace */
u8 bSafeMode; /* True to prohibit unsafe operations */
u8 bSafeModePersist; /* The long-term value of bSafeMode */
u8 bQuote; /* Quote results for .mode box and table */
int iWrap; /* Wrap lines this long or longer in some output modes */
unsigned statsOn; /* True to display memory stats before each finalize */
unsigned mEqpLines; /* Mask of veritical lines in the EQP output graph */
int inputNesting; /* Track nesting level of .read and other redirects */
@ -3239,7 +3241,37 @@ static char *translateForDisplayAndDup(
return (char*)zOut;
}
/* Extract the value of the i-th current column for pStmt as an SQL literal
** value. Memory is obtained from sqlite3_malloc64() and must be freed by
** the caller.
*/
static char *quoted_column(sqlite3_stmt *pStmt, int i){
switch( sqlite3_column_type(pStmt, i) ){
case SQLITE_NULL: {
return sqlite3_mprintf("NULL");
}
case SQLITE_INTEGER:
case SQLITE_FLOAT: {
return sqlite3_mprintf("%s",sqlite3_column_text(pStmt,i));
}
case SQLITE_TEXT: {
return sqlite3_mprintf("%Q",sqlite3_column_text(pStmt,i));
}
case SQLITE_BLOB: {
int j;
sqlite3_str *pStr = sqlite3_str_new(0);
const unsigned char *a = sqlite3_column_blob(pStmt,i);
int n = sqlite3_column_bytes(pStmt,i);
sqlite3_str_append(pStr, "x'", 2);
for(j=0; j<n; j++){
sqlite3_str_appendf(pStr, "%02x", a[j]);
}
sqlite3_str_append(pStr, "'", 1);
return sqlite3_str_finish(pStr);
}
}
return 0; /* Not reached */
}
/*
** Run a prepared statement and output the result in one of the
@ -3262,6 +3294,7 @@ static void exec_prepared_stmt_columnar(
char *abRowDiv = 0;
const unsigned char *uz;
const char *z;
char **azQuoted = 0;
int rc;
sqlite3_int64 i, nData;
int j, nTotal, w, n;
@ -3281,6 +3314,11 @@ static void exec_prepared_stmt_columnar(
azNextLine = sqlite3_malloc64( nColumn*sizeof(char*) );
shell_check_oom(azNextLine);
memset(azNextLine, 0, nColumn*sizeof(char*) );
if( p->bQuote ){
azQuoted = sqlite3_malloc64( nColumn*sizeof(char*) );
shell_check_oom(azQuoted);
memset(azQuoted, 0, nColumn*sizeof(char*) );
}
abRowDiv = sqlite3_malloc64( nAlloc/nColumn );
shell_check_oom(abRowDiv);
if( nColumn>p->nWidth ){
@ -3312,13 +3350,18 @@ static void exec_prepared_stmt_columnar(
abRowDiv[nRow] = 1;
nRow++;
for(i=0; i<nColumn; i++){
int w = p->colWidth[i];
if( w==0 ) w = p->iWrap;
if( useNextLine ){
uz = azNextLine[i];
}else if( p->bQuote ){
sqlite3_free(azQuoted[i]);
azQuoted[i] = quoted_column(pStmt,i);
uz = (const unsigned char*)azQuoted[i];
}else{
uz = (const unsigned char*)sqlite3_column_text(pStmt,i);
}
azData[nRow*nColumn + i] =
translateForDisplayAndDup(uz, &azNextLine[i], p->colWidth[i]);
azData[nRow*nColumn + i] = translateForDisplayAndDup(uz, &azNextLine[i], w);
if( azNextLine[i] ){
bNextLine = 1;
abRowDiv[nRow-1] = 0;
@ -3435,6 +3478,10 @@ columnar_end:
sqlite3_free(azData);
sqlite3_free(azNextLine);
sqlite3_free(abRowDiv);
if( azQuoted ){
for(i=0; i<nColumn; i++) sqlite3_free(azQuoted[i]);
sqlite3_free(azQuoted);
}
}
/*
@ -4212,22 +4259,27 @@ static const char *(azHelp[]) = {
".load FILE ?ENTRY? Load an extension library",
#endif
".log FILE|off Turn logging on or off. FILE can be stderr/stdout",
".mode MODE ?TABLE? Set output mode",
".mode MODE ?TABLE? ?OPTIONS? Set output mode",
" MODE is one of:",
" ascii Columns/rows delimited by 0x1F and 0x1E",
" box Tables using unicode box-drawing characters",
" csv Comma-separated values",
" column Output in columns. (See .width)",
" html HTML <table> code",
" insert SQL insert statements for TABLE",
" json Results in a JSON array",
" line One value per line",
" list Values delimited by \"|\"",
" markdown Markdown table format",
" quote Escape answers as for SQL",
" table ASCII-art table",
" tabs Tab-separated values",
" tcl TCL list elements",
" ascii Columns/rows delimited by 0x1F and 0x1E",
" box Tables using unicode box-drawing characters",
" csv Comma-separated values",
" column Output in columns. (See .width)",
" html HTML <table> code",
" insert SQL insert statements for TABLE",
" json Results in a JSON array",
" line One value per line",
" list Values delimited by \"|\"",
" markdown Markdown table format",
" qbox Shorthand for \"box --width 60 --quote\"",
" quote Escape answers as for SQL",
" table ASCII-art table",
" tabs Tab-separated values",
" tcl TCL list elements",
" OPTIONS: (value for columnar modes only):",
" --wrap N Wrap output lines longer than N character",
" --quote Quote output text as SQL literals",
" --noquote Do not quote output text",
".nonce STRING Disable safe mode for one command if the nonce matches",
".nullvalue STRING Use STRING in place of NULL values",
".once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE",
@ -9008,64 +9060,118 @@ static int do_meta_command(char *zLine, ShellState *p){
}else
if( c=='m' && strncmp(azArg[0], "mode", n)==0 ){
const char *zMode = nArg>=2 ? azArg[1] : "";
int n2 = strlen30(zMode);
int c2 = zMode[0];
if( c2=='l' && n2>2 && strncmp(azArg[1],"lines",n2)==0 ){
const char *zMode = 0;
const char *zTabname = 0;
int i, n2;
int bQuoteChng = 0;
int bWrapChng = 0;
for(i=1; i<nArg; i++){
const char *z = azArg[i];
if( optionMatch(z,"wrap") && i+1<nArg ){
p->iWrap = integerValue(azArg[++i]);
bWrapChng = 1;
}else if( optionMatch(z,"quote") ){
p->bQuote = 1;
bQuoteChng = 1;
}else if( optionMatch(z,"noquote") ){
p->bQuote = 0;
bQuoteChng = 1;
}else if( zMode==0 ){
zMode = z;
}else if( zTabname==0 ){
zTabname = z;
}else if( z[0]=='-' ){
utf8_printf(stderr, "unknown option: %s\n", z);
utf8_printf(stderr, "options:\n"
" --noquote\n"
" --quote\n"
" --wrap N\n");
rc = 1;
goto meta_command_exit;
}else{
utf8_printf(stderr, "extra argument: \"%s\"\n", z);
rc = 1;
goto meta_command_exit;
}
}
if( zMode==0 ){
if( p->mode==MODE_Column
|| (p->mode>=MODE_Markdown && p->mode<=MODE_Box)
){
raw_printf(p->out, "current output mode: %s --wrap %d --%squote\n",
modeDescr[p->mode], p->iWrap, p->bQuote ? "" : "no");
}else{
raw_printf(p->out, "current output mode: %s\n", modeDescr[p->mode]);
}
bWrapChng = bQuoteChng = 1;
zMode = modeDescr[p->mode];
}
n2 = strlen30(zMode);
if( strncmp(zMode,"lines",n2)==0 ){
p->mode = MODE_Line;
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
}else if( c2=='c' && strncmp(azArg[1],"columns",n2)==0 ){
}else if( strncmp(zMode,"columns",n2)==0 ){
p->mode = MODE_Column;
if( (p->shellFlgs & SHFLG_HeaderSet)==0 ){
p->showHeader = 1;
}
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
}else if( c2=='l' && n2>2 && strncmp(azArg[1],"list",n2)==0 ){
if( !bWrapChng ) p->iWrap = 0;
if( !bQuoteChng ) p->bQuote = 0;
}else if( strncmp(zMode,"list",n2)==0 ){
p->mode = MODE_List;
sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Column);
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
}else if( c2=='h' && strncmp(azArg[1],"html",n2)==0 ){
}else if( strncmp(zMode,"html",n2)==0 ){
p->mode = MODE_Html;
}else if( c2=='t' && strncmp(azArg[1],"tcl",n2)==0 ){
}else if( strncmp(zMode,"tcl",n2)==0 ){
p->mode = MODE_Tcl;
sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Space);
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
}else if( c2=='c' && strncmp(azArg[1],"csv",n2)==0 ){
}else if( strncmp(zMode,"csv",n2)==0 ){
p->mode = MODE_Csv;
sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf);
}else if( c2=='t' && strncmp(azArg[1],"tabs",n2)==0 ){
}else if( strncmp(zMode,"tabs",n2)==0 ){
p->mode = MODE_List;
sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Tab);
}else if( c2=='i' && strncmp(azArg[1],"insert",n2)==0 ){
}else if( strncmp(zMode,"insert",n2)==0 ){
p->mode = MODE_Insert;
set_table_name(p, nArg>=3 ? azArg[2] : "table");
}else if( c2=='q' && strncmp(azArg[1],"quote",n2)==0 ){
set_table_name(p, zTabname ? zTabname : "table");
}else if( strncmp(zMode,"quote",n2)==0 ){
p->mode = MODE_Quote;
sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
}else if( c2=='a' && strncmp(azArg[1],"ascii",n2)==0 ){
}else if( strncmp(zMode,"ascii",n2)==0 ){
p->mode = MODE_Ascii;
sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Unit);
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Record);
}else if( c2=='m' && strncmp(azArg[1],"markdown",n2)==0 ){
}else if( strncmp(zMode,"markdown",n2)==0 ){
p->mode = MODE_Markdown;
}else if( c2=='t' && strncmp(azArg[1],"table",n2)==0 ){
if( !bWrapChng ) p->iWrap = 0;
if( !bQuoteChng ) p->bQuote = 0;
}else if( strncmp(zMode,"table",n2)==0 ){
p->mode = MODE_Table;
}else if( c2=='b' && strncmp(azArg[1],"box",n2)==0 ){
if( !bWrapChng ) p->iWrap = 0;
if( !bQuoteChng ) p->bQuote = 0;
}else if( strncmp(zMode,"box",n2)==0 ){
p->mode = MODE_Box;
}else if( c2=='c' && strncmp(azArg[1],"count",n2)==0 ){
if( !bWrapChng ) p->iWrap = 0;
if( !bQuoteChng ) p->bQuote = 0;
}else if( strcmp(zMode,"qbox")==0 ){
p->mode = MODE_Box;
if( !bWrapChng ) p->iWrap = 60;
if( !bQuoteChng ) p->bQuote = 1;
}else if( strncmp(zMode,"count",n2)==0 ){
p->mode = MODE_Count;
}else if( c2=='o' && strncmp(azArg[1],"off",n2)==0 ){
}else if( strncmp(zMode,"off",n2)==0 ){
p->mode = MODE_Off;
}else if( c2=='j' && strncmp(azArg[1],"json",n2)==0 ){
}else if( strncmp(zMode,"json",n2)==0 ){
p->mode = MODE_Json;
}else if( nArg==1 ){
raw_printf(p->out, "current output mode: %s\n", modeDescr[p->mode]);
}else{
raw_printf(stderr, "Error: mode should be one of: "
"ascii box column csv html insert json line list markdown "
"quote table tabs tcl\n");
"qbox quote table tabs tcl\n");
rc = 1;
}
p->cMode = p->mode;
@ -10213,7 +10319,14 @@ static int do_meta_command(char *zLine, ShellState *p){
utf8_printf(p->out, "%12.12s: %s\n","explain",
p->mode==MODE_Explain ? "on" : p->autoExplain ? "auto" : "off");
utf8_printf(p->out,"%12.12s: %s\n","headers", azBool[p->showHeader!=0]);
utf8_printf(p->out, "%12.12s: %s\n","mode", modeDescr[p->mode]);
if( p->mode==MODE_Column
|| (p->mode>=MODE_Markdown && p->mode<=MODE_Box)
){
utf8_printf(p->out, "%12.12s: %s --wrap %d --%squote\n", "mode",
modeDescr[p->mode], p->iWrap, p->bQuote ? "" : "no");
}else{
utf8_printf(p->out, "%12.12s: %s\n","mode", modeDescr[p->mode]);
}
utf8_printf(p->out, "%12.12s: ", "nullvalue");
output_c_string(p->out, p->nullValue);
raw_printf(p->out, "\n");

View File

@ -205,10 +205,10 @@ do_test shell1-2.2.4 {
} {0 {}}
do_test shell1-2.2.5 {
catchcmd "test.db" ".mode \"insert FOO"
} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown quote table tabs tcl}}
} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}}
do_test shell1-2.2.6 {
catchcmd "test.db" ".mode \'insert FOO"
} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown quote table tabs tcl}}
} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}}
# check multiple tokens, and quoted tokens
do_test shell1-2.3.1 {
@ -236,7 +236,7 @@ do_test shell1-2.3.7 {
# check quoted args are unquoted
do_test shell1-2.4.1 {
catchcmd "test.db" ".mode FOO"
} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown quote table tabs tcl}}
} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}}
do_test shell1-2.4.2 {
catchcmd "test.db" ".mode csv"
} {0 {}}
@ -437,7 +437,7 @@ do_test shell1-3.13.1 {
} {0 {current output mode: list}}
do_test shell1-3.13.2 {
catchcmd "test.db" ".mode FOO"
} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown quote table tabs tcl}}
} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}}
do_test shell1-3.13.3 {
catchcmd "test.db" ".mode csv"
} {0 {}}
@ -467,17 +467,6 @@ do_test shell1-3.13.11 {
catchcmd "test.db" ".mode tcl BAD"
} {0 {}}
# don't allow partial mode type matches
do_test shell1-3.13.12 {
catchcmd "test.db" ".mode l"
} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown quote table tabs tcl}}
do_test shell1-3.13.13 {
catchcmd "test.db" ".mode li"
} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown quote table tabs tcl}}
do_test shell1-3.13.14 {
catchcmd "test.db" ".mode lin"
} {0 {}}
# .nullvalue STRING Print STRING in place of NULL values
do_test shell1-3.14.1 {
catchcmd "test.db" ".nullvalue"