| | @@ -9859,10 +9859,11 @@ |
| 9859 | 9859 | #define MODE_Pretty 11 /* Pretty-print schemas */ |
| 9860 | 9860 | #define MODE_EQP 12 /* Converts EXPLAIN QUERY PLAN output into a graph */ |
| 9861 | 9861 | #define MODE_Json 13 /* Output JSON */ |
| 9862 | 9862 | #define MODE_Markdown 14 /* Markdown formatting */ |
| 9863 | 9863 | #define MODE_Table 15 /* MySQL-style table formatting */ |
| 9864 | +#define MODE_Box 16 /* Unicode box-drawing characters */ |
| 9864 | 9865 | |
| 9865 | 9866 | static const char *modeDescr[] = { |
| 9866 | 9867 | "line", |
| 9867 | 9868 | "column", |
| 9868 | 9869 | "list", |
| | @@ -9876,11 +9877,12 @@ |
| 9876 | 9877 | "ascii", |
| 9877 | 9878 | "prettyprint", |
| 9878 | 9879 | "eqp", |
| 9879 | 9880 | "json", |
| 9880 | 9881 | "markdown", |
| 9881 | | - "table" |
| 9882 | + "table", |
| 9883 | + "box" |
| 9882 | 9884 | }; |
| 9883 | 9885 | |
| 9884 | 9886 | /* |
| 9885 | 9887 | ** These are the column/row/line separators used by the various |
| 9886 | 9888 | ** import/export modes. |
| | @@ -10601,23 +10603,27 @@ |
| 10601 | 10603 | } |
| 10602 | 10604 | raw_printf(out, "%.*s", N, zDash); |
| 10603 | 10605 | } |
| 10604 | 10606 | |
| 10605 | 10607 | /* |
| 10606 | | -** Print a markdown or table-style row separator |
| 10608 | +** Print a markdown or table-style row separator using ascii-art |
| 10607 | 10609 | */ |
| 10608 | 10610 | static void print_row_separator( |
| 10609 | 10611 | ShellState *p, |
| 10610 | 10612 | int nArg, |
| 10611 | 10613 | const char *zSep |
| 10612 | 10614 | ){ |
| 10613 | 10615 | int i; |
| 10614 | | - for(i=0; i<nArg; i++){ |
| 10616 | + if( nArg>0 ){ |
| 10617 | + fputs(zSep, p->out); |
| 10618 | + print_dashes(p->out, p->actualWidth[0]+2); |
| 10619 | + for(i=1; i<nArg; i++){ |
| 10620 | + fputs(zSep, p->out); |
| 10621 | + print_dashes(p->out, p->actualWidth[i]+2); |
| 10622 | + } |
| 10615 | 10623 | fputs(zSep, p->out); |
| 10616 | | - print_dashes(p->out, p->actualWidth[i]+2); |
| 10617 | 10624 | } |
| 10618 | | - fputs(zSep, p->out); |
| 10619 | 10625 | fputs("\n", p->out); |
| 10620 | 10626 | } |
| 10621 | 10627 | |
| 10622 | 10628 | /* |
| 10623 | 10629 | ** This is the callback routine that the shell |
| | @@ -11611,14 +11617,81 @@ |
| 11611 | 11617 | } |
| 11612 | 11618 | sqlite3_reset(pQ); |
| 11613 | 11619 | } |
| 11614 | 11620 | sqlite3_finalize(pQ); |
| 11615 | 11621 | } |
| 11622 | + |
| 11623 | +/* |
| 11624 | +** UTF8 box-drawing characters. Imagine box lines like this: |
| 11625 | +** |
| 11626 | +** 1 |
| 11627 | +** | |
| 11628 | +** 4 --+-- 2 |
| 11629 | +** | |
| 11630 | +** 3 |
| 11631 | +** |
| 11632 | +** Each box characters has between 2 and 4 of the lines leading from |
| 11633 | +** the center. The characters are here identified by the numbers of |
| 11634 | +** their corresponding lines. |
| 11635 | +*/ |
| 11636 | +#define BOX_24 "\342\224\200" /* U+2500 --- */ |
| 11637 | +#define BOX_13 "\342\224\202" /* U+2502 | */ |
| 11638 | +#define BOX_23 "\342\224\214" /* U+250c ,- */ |
| 11639 | +#define BOX_34 "\342\224\220" /* U+2510 -, */ |
| 11640 | +#define BOX_12 "\342\224\224" /* U+2514 '- */ |
| 11641 | +#define BOX_14 "\342\224\230" /* U+2518 -' */ |
| 11642 | +#define BOX_123 "\342\224\234" /* U+251c |- */ |
| 11643 | +#define BOX_134 "\342\224\244" /* U+2524 -| */ |
| 11644 | +#define BOX_234 "\342\224\254" /* U+252c -,- */ |
| 11645 | +#define BOX_124 "\342\224\264" /* U+2534 -'- */ |
| 11646 | +#define BOX_1234 "\342\224\274" /* U+253c -|- */ |
| 11647 | + |
| 11648 | +/* Draw horizontal line N characters long using unicode box |
| 11649 | +** characters |
| 11650 | +*/ |
| 11651 | +static void print_box_line(FILE *out, int N){ |
| 11652 | + const char zDash[] = |
| 11653 | + BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 |
| 11654 | + BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24; |
| 11655 | + const int nDash = sizeof(zDash) - 1; |
| 11656 | + N *= 3; |
| 11657 | + while( N>nDash ){ |
| 11658 | + utf8_printf(out, zDash); |
| 11659 | + N -= nDash; |
| 11660 | + } |
| 11661 | + utf8_printf(out, "%.*s", N, zDash); |
| 11662 | +} |
| 11663 | + |
| 11664 | +/* |
| 11665 | +** Draw a horizontal separator for a MODE_Box table. |
| 11666 | +*/ |
| 11667 | +static void print_box_row_separator( |
| 11668 | + ShellState *p, |
| 11669 | + int nArg, |
| 11670 | + const char *zSep1, |
| 11671 | + const char *zSep2, |
| 11672 | + const char *zSep3 |
| 11673 | +){ |
| 11674 | + int i; |
| 11675 | + if( nArg>0 ){ |
| 11676 | + utf8_printf(p->out, "%s", zSep1); |
| 11677 | + print_box_line(p->out, p->actualWidth[0]+2); |
| 11678 | + for(i=1; i<nArg; i++){ |
| 11679 | + utf8_printf(p->out, "%s", zSep2); |
| 11680 | + print_box_line(p->out, p->actualWidth[i]+2); |
| 11681 | + } |
| 11682 | + utf8_printf(p->out, "%s", zSep3); |
| 11683 | + } |
| 11684 | + fputs("\n", p->out); |
| 11685 | +} |
| 11686 | + |
| 11687 | + |
| 11616 | 11688 | |
| 11617 | 11689 | /* |
| 11618 | 11690 | ** Run a prepared statement and output the result in one of the |
| 11619 | | -** table-oriented formats: MODE_Column, MODE_Markdown, or MODE_Table. |
| 11691 | +** table-oriented formats: MODE_Column, MODE_Markdown, MODE_Table, |
| 11692 | +** or MODE_Box. |
| 11620 | 11693 | ** |
| 11621 | 11694 | ** This is different from ordinary exec_prepared_stmt() in that |
| 11622 | 11695 | ** it has to run the entire query and gather the results into memory |
| 11623 | 11696 | ** first, in order to determine column widths, before providing |
| 11624 | 11697 | ** any output. |
| | @@ -11632,20 +11705,24 @@ |
| 11632 | 11705 | char **azData = 0; |
| 11633 | 11706 | char *zMsg = 0; |
| 11634 | 11707 | const char *z; |
| 11635 | 11708 | int rc; |
| 11636 | 11709 | int i, j, nTotal, w, n; |
| 11637 | | - const char *colSep; |
| 11638 | | - const char *rowSep; |
| 11710 | + const char *colSep = 0; |
| 11711 | + const char *rowSep = 0; |
| 11639 | 11712 | |
| 11640 | 11713 | rc = sqlite3_get_table(p->db, sqlite3_sql(pStmt), |
| 11641 | 11714 | &azData, &nRow, &nColumn, &zMsg); |
| 11642 | 11715 | if( rc ){ |
| 11643 | 11716 | utf8_printf(p->out, "ERROR: %s\n", zMsg); |
| 11644 | 11717 | sqlite3_free(zMsg); |
| 11645 | 11718 | sqlite3_free_table(azData); |
| 11646 | 11719 | return; |
| 11720 | + } |
| 11721 | + if( nRow==0 || nColumn==0 ){ |
| 11722 | + sqlite3_free_table(azData); |
| 11723 | + return; |
| 11647 | 11724 | } |
| 11648 | 11725 | if( nColumn>p->nWidth ){ |
| 11649 | 11726 | p->colWidth = realloc(p->colWidth, nColumn*2*sizeof(int)); |
| 11650 | 11727 | if( p->colWidth==0 ) shell_out_of_memory(); |
| 11651 | 11728 | for(i=p->nWidth; i<nColumn; i++) p->colWidth[i] = 0; |
| | @@ -11664,54 +11741,91 @@ |
| 11664 | 11741 | if( z==0 ) z = p->nullValue; |
| 11665 | 11742 | n = strlenChar(z); |
| 11666 | 11743 | j = i%nColumn; |
| 11667 | 11744 | if( n>p->actualWidth[j] ) p->actualWidth[j] = n; |
| 11668 | 11745 | } |
| 11669 | | - if( p->cMode==MODE_Column ){ |
| 11670 | | - colSep = " "; |
| 11671 | | - rowSep = "\n"; |
| 11672 | | - if( p->showHeader ){ |
| 11673 | | - for(i=0; i<nColumn; i++){ |
| 11674 | | - w = p->actualWidth[i]; |
| 11675 | | - if( p->colWidth[i]<0 ) w = -w; |
| 11676 | | - utf8_width_print(p->out, w, azData[i]); |
| 11677 | | - fputs(i==nColumn-1?"\n":" ", p->out); |
| 11678 | | - } |
| 11679 | | - for(i=0; i<nColumn; i++){ |
| 11680 | | - print_dashes(p->out, p->actualWidth[i]); |
| 11681 | | - fputs(i==nColumn-1?"\n":" ", p->out); |
| 11682 | | - } |
| 11683 | | - } |
| 11684 | | - }else{ |
| 11685 | | - colSep = " | "; |
| 11686 | | - rowSep = " |\n"; |
| 11687 | | - if( p->cMode==MODE_Table ) print_row_separator(p, nColumn, "+"); |
| 11688 | | - fputs("| ", p->out); |
| 11689 | | - for(i=0; i<nColumn; i++){ |
| 11690 | | - w = p->actualWidth[i]; |
| 11691 | | - n = strlenChar(azData[i]); |
| 11692 | | - utf8_printf(p->out, "%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, ""); |
| 11693 | | - fputs(i==nColumn-1?" |\n":" | ", p->out); |
| 11694 | | - } |
| 11695 | | - print_row_separator(p, nColumn, p->cMode==MODE_Table ? "+" : "|"); |
| 11746 | + switch( p->cMode ){ |
| 11747 | + case MODE_Column: { |
| 11748 | + colSep = " "; |
| 11749 | + rowSep = "\n"; |
| 11750 | + if( p->showHeader ){ |
| 11751 | + for(i=0; i<nColumn; i++){ |
| 11752 | + w = p->actualWidth[i]; |
| 11753 | + if( p->colWidth[i]<0 ) w = -w; |
| 11754 | + utf8_width_print(p->out, w, azData[i]); |
| 11755 | + fputs(i==nColumn-1?"\n":" ", p->out); |
| 11756 | + } |
| 11757 | + for(i=0; i<nColumn; i++){ |
| 11758 | + print_dashes(p->out, p->actualWidth[i]); |
| 11759 | + fputs(i==nColumn-1?"\n":" ", p->out); |
| 11760 | + } |
| 11761 | + } |
| 11762 | + break; |
| 11763 | + } |
| 11764 | + case MODE_Table: { |
| 11765 | + colSep = " | "; |
| 11766 | + rowSep = " |\n"; |
| 11767 | + print_row_separator(p, nColumn, "+"); |
| 11768 | + fputs("| ", p->out); |
| 11769 | + for(i=0; i<nColumn; i++){ |
| 11770 | + w = p->actualWidth[i]; |
| 11771 | + n = strlenChar(azData[i]); |
| 11772 | + utf8_printf(p->out, "%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, ""); |
| 11773 | + fputs(i==nColumn-1?" |\n":" | ", p->out); |
| 11774 | + } |
| 11775 | + print_row_separator(p, nColumn, "+"); |
| 11776 | + break; |
| 11777 | + } |
| 11778 | + case MODE_Markdown: { |
| 11779 | + colSep = " | "; |
| 11780 | + rowSep = " |\n"; |
| 11781 | + fputs("| ", p->out); |
| 11782 | + for(i=0; i<nColumn; i++){ |
| 11783 | + w = p->actualWidth[i]; |
| 11784 | + n = strlenChar(azData[i]); |
| 11785 | + utf8_printf(p->out, "%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, ""); |
| 11786 | + fputs(i==nColumn-1?" |\n":" | ", p->out); |
| 11787 | + } |
| 11788 | + print_row_separator(p, nColumn, "|"); |
| 11789 | + break; |
| 11790 | + } |
| 11791 | + case MODE_Box: { |
| 11792 | + colSep = " " BOX_13 " "; |
| 11793 | + rowSep = " " BOX_13 "\n"; |
| 11794 | + print_box_row_separator(p, nColumn, BOX_23, BOX_234, BOX_34); |
| 11795 | + utf8_printf(p->out, BOX_13 " "); |
| 11796 | + for(i=0; i<nColumn; i++){ |
| 11797 | + w = p->actualWidth[i]; |
| 11798 | + n = strlenChar(azData[i]); |
| 11799 | + utf8_printf(p->out, "%*s%s%*s%s", |
| 11800 | + (w-n)/2, "", azData[i], (w-n+1)/2, "", |
| 11801 | + i==nColumn-1?" "BOX_13"\n":" "BOX_13" "); |
| 11802 | + } |
| 11803 | + print_box_row_separator(p, nColumn, BOX_123, BOX_1234, BOX_134); |
| 11804 | + break; |
| 11805 | + } |
| 11696 | 11806 | } |
| 11697 | 11807 | for(i=nColumn, j=0; i<nTotal; i++, j++){ |
| 11698 | | - if( j==0 && p->cMode!=MODE_Column ) fputs("| ", p->out); |
| 11808 | + if( j==0 && p->cMode!=MODE_Column ){ |
| 11809 | + utf8_printf(p->out, "%s", p->cMode==MODE_Box?BOX_13" ":"| "); |
| 11810 | + } |
| 11699 | 11811 | z = azData[i]; |
| 11700 | 11812 | if( z==0 ) z = p->nullValue; |
| 11701 | 11813 | w = p->actualWidth[j]; |
| 11702 | 11814 | if( p->colWidth[j]<0 ) w = -w; |
| 11703 | 11815 | utf8_width_print(p->out, w, z); |
| 11704 | 11816 | if( j==nColumn-1 ){ |
| 11705 | | - fputs(rowSep, p->out); |
| 11817 | + utf8_printf(p->out, "%s", rowSep); |
| 11706 | 11818 | j = -1; |
| 11707 | 11819 | }else{ |
| 11708 | | - fputs(colSep, p->out); |
| 11820 | + utf8_printf(p->out, "%s", colSep); |
| 11709 | 11821 | } |
| 11710 | 11822 | } |
| 11711 | 11823 | if( p->cMode==MODE_Table ){ |
| 11712 | 11824 | print_row_separator(p, nColumn, "+"); |
| 11825 | + }else if( p->cMode==MODE_Box ){ |
| 11826 | + print_box_row_separator(p, nColumn, BOX_12, BOX_124, BOX_14); |
| 11713 | 11827 | } |
| 11714 | 11828 | sqlite3_free_table(azData); |
| 11715 | 11829 | } |
| 11716 | 11830 | |
| 11717 | 11831 | /* |
| | @@ -11723,10 +11837,11 @@ |
| 11723 | 11837 | ){ |
| 11724 | 11838 | int rc; |
| 11725 | 11839 | |
| 11726 | 11840 | if( pArg->cMode==MODE_Column |
| 11727 | 11841 | || pArg->cMode==MODE_Table |
| 11842 | + || pArg->cMode==MODE_Box |
| 11728 | 11843 | || pArg->cMode==MODE_Markdown |
| 11729 | 11844 | ){ |
| 11730 | 11845 | exec_prepared_stmt_columnar(pArg, pStmt); |
| 11731 | 11846 | return; |
| 11732 | 11847 | } |
| | @@ -11776,13 +11891,11 @@ |
| 11776 | 11891 | rc = sqlite3_step(pStmt); |
| 11777 | 11892 | } |
| 11778 | 11893 | } |
| 11779 | 11894 | } while( SQLITE_ROW == rc ); |
| 11780 | 11895 | sqlite3_free(pData); |
| 11781 | | - if( pArg->cMode==MODE_Table ){ |
| 11782 | | - print_row_separator(pArg, nCol, "+"); |
| 11783 | | - }else if( pArg->cMode==MODE_Json ){ |
| 11896 | + if( pArg->cMode==MODE_Json ){ |
| 11784 | 11897 | fputs("]\n", pArg->out); |
| 11785 | 11898 | } |
| 11786 | 11899 | } |
| 11787 | 11900 | } |
| 11788 | 11901 | } |
| | @@ -12462,10 +12575,11 @@ |
| 12462 | 12575 | #endif |
| 12463 | 12576 | ".log FILE|off Turn logging on or off. FILE can be stderr/stdout", |
| 12464 | 12577 | ".mode MODE ?TABLE? Set output mode", |
| 12465 | 12578 | " MODE is one of:", |
| 12466 | 12579 | " ascii Columns/rows delimited by 0x1F and 0x1E", |
| 12580 | + " box Tables using unicode box-drawing characters", |
| 12467 | 12581 | " csv Comma-separated values", |
| 12468 | 12582 | " column Output in columns. (See .width)", |
| 12469 | 12583 | " html HTML <table> code", |
| 12470 | 12584 | " insert SQL insert statements for TABLE", |
| 12471 | 12585 | " json Results in a JSON array", |
| | @@ -17075,17 +17189,19 @@ |
| 17075 | 17189 | sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Record); |
| 17076 | 17190 | }else if( c2=='m' && strncmp(azArg[1],"markdown",n2)==0 ){ |
| 17077 | 17191 | p->mode = MODE_Markdown; |
| 17078 | 17192 | }else if( c2=='t' && strncmp(azArg[1],"table",n2)==0 ){ |
| 17079 | 17193 | p->mode = MODE_Table; |
| 17194 | + }else if( c2=='b' && strncmp(azArg[1],"box",n2)==0 ){ |
| 17195 | + p->mode = MODE_Box; |
| 17080 | 17196 | }else if( c2=='j' && strncmp(azArg[1],"json",n2)==0 ){ |
| 17081 | 17197 | p->mode = MODE_Json; |
| 17082 | 17198 | }else if( nArg==1 ){ |
| 17083 | 17199 | raw_printf(p->out, "current output mode: %s\n", modeDescr[p->mode]); |
| 17084 | 17200 | }else{ |
| 17085 | 17201 | raw_printf(stderr, "Error: mode should be one of: " |
| 17086 | | - "ascii column csv html insert json line list markdown " |
| 17202 | + "ascii box column csv html insert json line list markdown " |
| 17087 | 17203 | "quote table tabs tcl\n"); |
| 17088 | 17204 | rc = 1; |
| 17089 | 17205 | } |
| 17090 | 17206 | p->cMode = p->mode; |
| 17091 | 17207 | }else |
| | @@ -19081,10 +19197,11 @@ |
| 19081 | 19197 | #endif |
| 19082 | 19198 | " -append append the database to the end of the file\n" |
| 19083 | 19199 | " -ascii set output mode to 'ascii'\n" |
| 19084 | 19200 | " -bail stop after hitting an error\n" |
| 19085 | 19201 | " -batch force batch I/O\n" |
| 19202 | + " -box set output mode to 'box'\n" |
| 19086 | 19203 | " -column set output mode to 'column'\n" |
| 19087 | 19204 | " -cmd COMMAND run \"COMMAND\" before reading stdin\n" |
| 19088 | 19205 | " -csv set output mode to 'csv'\n" |
| 19089 | 19206 | #if defined(SQLITE_ENABLE_DESERIALIZE) |
| 19090 | 19207 | " -deserialize open the database using sqlite3_deserialize()\n" |
| | @@ -19528,10 +19645,12 @@ |
| 19528 | 19645 | data.mode = MODE_Json; |
| 19529 | 19646 | }else if( strcmp(z,"-markdown")==0 ){ |
| 19530 | 19647 | data.mode = MODE_Markdown; |
| 19531 | 19648 | }else if( strcmp(z,"-table")==0 ){ |
| 19532 | 19649 | data.mode = MODE_Table; |
| 19650 | + }else if( strcmp(z,"-box")==0 ){ |
| 19651 | + data.mode = MODE_Box; |
| 19533 | 19652 | }else if( strcmp(z,"-csv")==0 ){ |
| 19534 | 19653 | data.mode = MODE_Csv; |
| 19535 | 19654 | memcpy(data.colSeparator,",",2); |
| 19536 | 19655 | #ifdef SQLITE_HAVE_ZLIB |
| 19537 | 19656 | }else if( strcmp(z,"-zip")==0 ){ |
| 19538 | 19657 | |