Progress toward adding new output modes to the CLI: json, table, and

markdown.

FossilOrigin-Name: 14f55fafec11491e87e6526c72cf85c689d74ba18418a1ae9646586ec206767a
This commit is contained in:
drh 2020-05-28 23:49:50 +00:00
parent c683573fd4
commit 30c54a01db
4 changed files with 187 additions and 38 deletions

View File

@ -1,5 +1,5 @@
C Enhance\sthe\s".quote"\smode\sin\sthe\sshell\sso\sthat\sit\shonors\s.separator.
D 2020-05-28T20:37:17.793
C Progress\stoward\sadding\snew\soutput\smodes\sto\sthe\sCLI:\s\sjson,\stable,\sand\nmarkdown.
D 2020-05-28T23:49:50.023
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -534,7 +534,7 @@ F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384
F src/resolve.c c2008519a0654f1e7490e9281ed0397d0f14bb840d81f0b96946248afcbdb25d
F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
F src/select.c 39a00a8bc89596dfb37c16afcbb1d33de5085b9963564b58aafe1566d08c0881
F src/shell.c.in 2bca5f1474b43e7c0c1bcd0537b854c2e8a0fac0de2bde473b8c1e919554dcc6
F src/shell.c.in d135e500f2c84808f86e8113fd22852af4c89f69305f122d3c529cd698ccb396
F src/sqlite.h.in 74342b41e9d68ff9e56b192009046f8dd0aa2bd76ce1a588f330de614ba61de7
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h 2d1af80082edffd71c6f96f70ad1ce6a4fb46615ad10291fc77fe0dea9ff0197
@ -1338,7 +1338,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 5bd10014ec494744f5e966a1521334e9d612119a0afcfa5251684a4e1f2ffc66
F test/shell1.test 1c4713ccec468f9300100d5e1419b414b8dcccc742978ad8942e8bd31d2adc9c
F test/shell2.test e242a9912f44f4c23c3d1d802a83e934e84c853b
F test/shell3.test ac8c2b744014c3e9a0e26bfd829ab65f00923dc1a91ffd044863e9423cc91494
F test/shell4.test 1c6aef11daaa2d6830acaba3ac9cbec93fbc1c3d5530743a637f39b3987d08ce
@ -1866,7 +1866,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 98d4262018a81a9a36dd8beb4b02ff0e75cdcbb8a121d143157ffb37b228d60d
R 6e7a8027469df73da3225b0c75827874
P b5e33ed537e7d7dcabc9f6dc91d6838e0d1657f323440e09e2e24ffa2ba6141a
R 65dafea1aa0e6dfee5aa69ebe4f5d82f
U drh
Z 824af62dceaa57c3d7f60896a599839b
Z 8f810f781bfa2ede3701aac97fce45df

View File

@ -1 +1 @@
b5e33ed537e7d7dcabc9f6dc91d6838e0d1657f323440e09e2e24ffa2ba6141a
14f55fafec11491e87e6526c72cf85c689d74ba18418a1ae9646586ec206767a

View File

@ -1035,18 +1035,6 @@ struct OpenSession {
};
#endif
/*
** Shell output mode information from before ".explain on",
** saved so that it can be restored by ".explain off"
*/
typedef struct SavedModeInfo SavedModeInfo;
struct SavedModeInfo {
int valid; /* Is there legit data in here? */
int mode; /* Mode prior to ".explain on" */
int showHeader; /* The ".header" setting prior to ".explain on" */
int colWidth[100]; /* Column widths prior to ".explain on" */
};
typedef struct ExpertInfo ExpertInfo;
struct ExpertInfo {
sqlite3expert *pExpert;
@ -1202,6 +1190,9 @@ struct ShellState {
#define MODE_Ascii 10 /* Use ASCII unit and record separators (0x1F/0x1E) */
#define MODE_Pretty 11 /* Pretty-print schemas */
#define MODE_EQP 12 /* Converts EXPLAIN QUERY PLAN output into a graph */
#define MODE_Json 13 /* Output JSON */
#define MODE_Markdown 14 /* Markdown formatting */
#define MODE_Table 15 /* MySQL-style table formatting */
static const char *modeDescr[] = {
"line",
@ -1216,7 +1207,10 @@ static const char *modeDescr[] = {
"explain",
"ascii",
"prettyprint",
"eqp"
"eqp",
"json",
"markdown",
"table"
};
/*
@ -1893,6 +1887,43 @@ static int progress_handler(void *pClientData) {
}
#endif /* SQLITE_OMIT_PROGRESS_CALLBACK */
/*
** Print N dashes
*/
static void print_dashes(FILE *out, int N){
const char zDash[] = "--------------------------------------------------";
const int nDash = sizeof(zDash) - 1;
while( N>nDash ){
fputs(zDash, out);
N -= nDash;
}
raw_printf(out, "%.*s", N, zDash);
}
/*
** Print a markdown or table-style row separator
*/
static void print_row_separator(
ShellState *p,
int nArg,
const char *zSep
){
int i;
for(i=0; i<nArg; i++){
int w;
if( i<ArraySize(p->actualWidth) ){
w = p->actualWidth[i];
if( w<0 ) w = -w;
}else{
w = 10;
}
fputs(zSep, p->out);
print_dashes(p->out, w+2);
}
fputs(zSep, p->out);
fputs("\n", p->out);
}
/*
** This is the callback routine that the shell
** invokes for each row of a query result.
@ -1923,23 +1954,38 @@ static int shell_callback(
}
break;
}
case MODE_Table:
case MODE_Markdown:
case MODE_Explain:
case MODE_Column: {
static const int aExplainWidths[] = {4, 13, 4, 4, 4, 13, 2, 13};
const int *colWidth;
int showHdr;
char *rowSep;
char *colSep;
char *rowStart;
int nWidth;
if( p->cMode==MODE_Column ){
colWidth = p->colWidth;
nWidth = ArraySize(p->colWidth);
showHdr = p->showHeader;
rowSep = p->rowSeparator;
}else{
colSep = " ";
rowStart = "";
}else if( p->cMode==MODE_Explain ){
colWidth = aExplainWidths;
nWidth = ArraySize(aExplainWidths);
showHdr = 1;
rowSep = SEP_Row;
colSep = " ";
rowStart = "";
}else{
colWidth = p->colWidth;
nWidth = ArraySize(p->colWidth);
showHdr = p->showHeader;
rowSep = " |\n";
colSep = " | ";
rowStart = "| ";
}
if( p->cnt++==0 ){
for(i=0; i<nArg; i++){
@ -1958,12 +2004,12 @@ static int shell_callback(
if( i<ArraySize(p->actualWidth) ){
p->actualWidth[i] = w;
}
if( showHdr ){
utf8_width_print(p->out, w, azCol[i]);
utf8_printf(p->out, "%s", i==nArg-1 ? rowSep : " ");
}
}
if( showHdr ){
if( p->cMode==MODE_Table ){
print_row_separator(p, nArg, "+");
}
fputs(rowStart, p->out);
for(i=0; i<nArg; i++){
int w;
if( i<ArraySize(p->actualWidth) ){
@ -1972,14 +2018,34 @@ static int shell_callback(
}else{
w = 10;
}
utf8_printf(p->out,"%-*.*s%s",w,w,
"----------------------------------------------------------"
"----------------------------------------------------------",
i==nArg-1 ? rowSep : " ");
utf8_width_print(p->out, w, azCol[i]);
fputs(i==nArg-1 ? rowSep : colSep, p->out);
}
for(i=0; i<nArg; i++){
int w;
if( i<ArraySize(p->actualWidth) ){
w = p->actualWidth[i];
if( w<0 ) w = -w;
}else{
w = 10;
}
if( p->cMode==MODE_Table || p->cMode==MODE_Markdown ){
char *zX = p->cMode==MODE_Markdown ? "|" : "+";
fputs(zX, p->out);
print_dashes(p->out, w+2);
if( i==nArg-1 ){
fputs(zX, p->out);
fputs("\n", p->out);
}
}else{
print_dashes(p->out, w);
fputs(i==nArg-1 ? rowSep : colSep, p->out);
}
}
}
}
if( azArg==0 ) break;
fputs(rowStart, p->out);
for(i=0; i<nArg; i++){
int w;
if( i<ArraySize(p->actualWidth) ){
@ -1997,7 +2063,7 @@ static int shell_callback(
p->iIndent++;
}
utf8_width_print(p->out, w, azArg[i] ? azArg[i] : p->nullValue);
utf8_printf(p->out, "%s", i==nArg-1 ? rowSep : " ");
utf8_printf(p->out, "%s", i==nArg-1 ? rowSep : colSep);
}
break;
}
@ -2201,6 +2267,50 @@ static int shell_callback(
raw_printf(p->out,");\n");
break;
}
case MODE_Json: {
if( azArg==0 ) break;
if( p->cnt==0 ){
fputs("[{", p->out);
}else{
fputs(",\n{", p->out);
}
p->cnt++;
for(i=0; i<nArg; i++){
output_c_string(p->out, azCol[i]);
putc(':', p->out);
if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){
fputs("null",p->out);
}else if( aiType && aiType[i]==SQLITE_FLOAT ){
char z[50];
double r = sqlite3_column_double(p->pStmt, i);
sqlite3_uint64 ur;
memcpy(&ur,&r,sizeof(r));
if( ur==0x7ff0000000000000LL ){
raw_printf(p->out, "1e999");
}else if( ur==0xfff0000000000000LL ){
raw_printf(p->out, "-1e999");
}else{
sqlite3_snprintf(50,z,"%!.20g", r);
raw_printf(p->out, "%s", z);
}
}else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){
const void *pBlob = sqlite3_column_blob(p->pStmt, i);
int nBlob = sqlite3_column_bytes(p->pStmt, i);
putc('"', p->out);
output_hex_blob(p->out, pBlob, nBlob);
putc('"', p->out);
}else if( aiType && aiType[i]==SQLITE_TEXT ){
output_c_string(p->out, azArg[i]);
}else{
utf8_printf(p->out,"%s", azArg[i]);
}
if( i<nArg-1 ){
putc(',', p->out);
}
}
putc('}', p->out);
break;
}
case MODE_Quote: {
if( azArg==0 ) break;
if( p->cnt==0 && p->showHeader ){
@ -2890,6 +3000,24 @@ static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){
sqlite3_finalize(pQ);
}
#if 0
/*
** Run a prepared statement and output the result in one of the
** table-oriented formats, either MODE_Markdown or MODE_Table.
**
** This is different from ordinary exec_prepared_stmt() in that
** it has to run the entire query and gather the results into memory
** first, in order to determine column widths, before providing
** any output.
*/
static void exec_prepared_stmt_tablemode(
ShellState *pArg, /* Pointer to ShellState */
sqlite3_stmt *pStmt /* Statment to run */
){
}
#endif
/*
** Run a prepared statement
*/
@ -2946,6 +3074,11 @@ static void exec_prepared_stmt(
}
} while( SQLITE_ROW == rc );
sqlite3_free(pData);
if( pArg->cMode==MODE_Table ){
print_row_separator(pArg, nCol, "+");
}else if( pArg->cMode==MODE_Json ){
fputs("]\n", pArg->out);
}
}
}
}
@ -8224,11 +8357,18 @@ static int do_meta_command(char *zLine, ShellState *p){
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 ){
p->mode = MODE_Markdown;
}else if( c2=='t' && strncmp(azArg[1],"table",n2)==0 ){
p->mode = MODE_Table;
}else if( c2=='j' && strncmp(azArg[1],"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 column csv html insert line list quote tabs tcl\n");
"ascii column csv html insert json line list markdown "
"quote table tabs tcl\n");
rc = 1;
}
p->cMode = p->mode;
@ -10238,9 +10378,11 @@ static const char zOptions[] =
" -help show this message\n"
" -html set output mode to HTML\n"
" -interactive force interactive I/O\n"
" -json set output mode to 'json'\n"
" -line set output mode to 'line'\n"
" -list set output mode to 'list'\n"
" -lookaside SIZE N use N entries of SZ bytes for lookaside memory\n"
" -markdown set output mode to 'markdown'\n"
#if defined(SQLITE_ENABLE_DESERIALIZE)
" -maxsize N maximum size for a --deserialize database\n"
#endif
@ -10260,6 +10402,7 @@ static const char zOptions[] =
" -sorterref SIZE sorter references threshold size\n"
#endif
" -stats print memory stats before each finalize\n"
" -table set output mode to 'table'\n"
" -version show SQLite version\n"
" -vfs NAME use NAME as the default VFS\n"
#ifdef SQLITE_ENABLE_VFSTRACE
@ -10661,6 +10804,12 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
data.mode = MODE_Line;
}else if( strcmp(z,"-column")==0 ){
data.mode = MODE_Column;
}else if( strcmp(z,"-json")==0 ){
data.mode = MODE_Json;
}else if( strcmp(z,"-markdown")==0 ){
data.mode = MODE_Markdown;
}else if( strcmp(z,"-table")==0 ){
data.mode = MODE_Table;
}else if( strcmp(z,"-csv")==0 ){
data.mode = MODE_Csv;
memcpy(data.colSeparator,",",2);

View File

@ -199,10 +199,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 column csv html insert line list quote tabs tcl}}
} {1 {Error: mode should be one of: ascii column csv html insert json line list markdown quote table tabs tcl}}
do_test shell1-2.2.6 {
catchcmd "test.db" ".mode \'insert FOO"
} {1 {Error: mode should be one of: ascii column csv html insert line list quote tabs tcl}}
} {1 {Error: mode should be one of: ascii column csv html insert json line list markdown quote table tabs tcl}}
# check multiple tokens, and quoted tokens
do_test shell1-2.3.1 {
@ -230,7 +230,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 column csv html insert line list quote tabs tcl}}
} {1 {Error: mode should be one of: ascii column csv html insert json line list markdown quote table tabs tcl}}
do_test shell1-2.4.2 {
catchcmd "test.db" ".mode csv"
} {0 {}}
@ -430,7 +430,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 column csv html insert line list quote tabs tcl}}
} {1 {Error: mode should be one of: ascii column csv html insert json line list markdown quote table tabs tcl}}
do_test shell1-3.13.3 {
catchcmd "test.db" ".mode csv"
} {0 {}}
@ -463,10 +463,10 @@ do_test shell1-3.13.11 {
# 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 column csv html insert line list quote tabs tcl}}
} {1 {Error: mode should be one of: ascii 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 column csv html insert line list quote tabs tcl}}
} {1 {Error: mode should be one of: ascii column csv html insert json line list markdown quote table tabs tcl}}
do_test shell1-3.13.14 {
catchcmd "test.db" ".mode lin"
} {0 {}}