| | @@ -681,10 +681,13 @@ |
| 681 | 681 | ** Header file for the Result-Format or "resfmt" utility library for SQLite. |
| 682 | 682 | ** See the resfmt.md documentation for additional information. |
| 683 | 683 | */ |
| 684 | 684 | #ifndef SQLITE_QRF_H |
| 685 | 685 | #define SQLITE_QRF_H |
| 686 | +#ifdef __cplusplus |
| 687 | +extern "C" { |
| 688 | +#endif |
| 686 | 689 | #include <stdlib.h> |
| 687 | 690 | /* #include "sqlite3.h" */ |
| 688 | 691 | |
| 689 | 692 | /* |
| 690 | 693 | ** Specification used by clients to define the output format they want |
| | @@ -701,10 +704,11 @@ |
| 701 | 704 | unsigned char bWordWrap; /* Try to wrap on word boundaries */ |
| 702 | 705 | unsigned char bTextJsonb; /* Render JSONB blobs as JSON text */ |
| 703 | 706 | unsigned char bTextNull; /* Apply eText encoding to zNull[] */ |
| 704 | 707 | unsigned char eDfltAlign; /* Default alignment, no covered by aAlignment */ |
| 705 | 708 | unsigned char eTitleAlign; /* Alignment for column headers */ |
| 709 | + unsigned char bSplitColumn; /* Wrap single-column output into many columns */ |
| 706 | 710 | short int nWrap; /* Wrap columns wider than this */ |
| 707 | 711 | short int nScreenWidth; /* Maximum overall table width */ |
| 708 | 712 | short int nLineLimit; /* Maximum number of lines for any row */ |
| 709 | 713 | int nCharLimit; /* Maximum number of characters in a cell */ |
| 710 | 714 | int nWidth; /* Number of entries in aWidth[] */ |
| | @@ -846,12 +850,13 @@ |
| 846 | 850 | ** Unfortunately, there is nothing we can do about that. |
| 847 | 851 | */ |
| 848 | 852 | int sqlite3_qrf_wcwidth(int c); |
| 849 | 853 | |
| 850 | 854 | |
| 851 | | - |
| 852 | | - |
| 855 | +#ifdef __cplusplus |
| 856 | +} |
| 857 | +#endif |
| 853 | 858 | #endif /* !defined(SQLITE_QRF_H) */ |
| 854 | 859 | |
| 855 | 860 | /************************* End ext/qrf/qrf.h ********************/ |
| 856 | 861 | /************************* Begin ext/qrf/qrf.c ******************/ |
| 857 | 862 | /* |
| | @@ -1895,10 +1900,21 @@ |
| 1895 | 1900 | sqlite3_str_append(pOut, "...", 3); |
| 1896 | 1901 | } |
| 1897 | 1902 | } |
| 1898 | 1903 | #endif |
| 1899 | 1904 | } |
| 1905 | + |
| 1906 | +/* Trim spaces of the end if pOut |
| 1907 | +*/ |
| 1908 | +static void qrfRTrim(sqlite3_str *pOut){ |
| 1909 | +#if SQLITE_VERSION_NUMBER>=3052000 |
| 1910 | + int nByte = sqlite3_str_length(pOut); |
| 1911 | + const char *zOut = sqlite3_str_value(pOut); |
| 1912 | + while( nByte>0 && zOut[nByte-1]==' ' ){ nByte--; } |
| 1913 | + sqlite3_str_truncate(pOut, nByte); |
| 1914 | +#endif |
| 1915 | +} |
| 1900 | 1916 | |
| 1901 | 1917 | /* |
| 1902 | 1918 | ** Store string zUtf to pOut as w characters. If w is negative, |
| 1903 | 1919 | ** then right-justify the text. W is the width in display characters, not |
| 1904 | 1920 | ** in bytes. Double-width unicode characters count as two characters. |
| | @@ -2305,10 +2321,135 @@ |
| 2305 | 2321 | (pData->a[i].e & QRF_ALIGN_VMASK); |
| 2306 | 2322 | } |
| 2307 | 2323 | } |
| 2308 | 2324 | } |
| 2309 | 2325 | } |
| 2326 | + |
| 2327 | +/* |
| 2328 | +** If the single column in pData->a[] with pData->n entries can be |
| 2329 | +** laid out as nCol columns with a 2-space gap between each such |
| 2330 | +** that all columns fit within nSW, then return a pointer to an array |
| 2331 | +** of integers which is the width of each column from left to right. |
| 2332 | +** |
| 2333 | +** If the layout is not possible, return a NULL pointer. |
| 2334 | +** |
| 2335 | +** Space to hold the returned array is from sqlite_malloc64(). |
| 2336 | +*/ |
| 2337 | +static int *qrfValidLayout( |
| 2338 | + qrfColData *pData, /* Collected query results */ |
| 2339 | + Qrf *p, /* On which to report an OOM */ |
| 2340 | + int nCol, /* Attempt this many columns */ |
| 2341 | + int nSW /* Screen width */ |
| 2342 | +){ |
| 2343 | + int i; /* Loop counter */ |
| 2344 | + int nr; /* Number of rows */ |
| 2345 | + int w = 0; /* Width of the current column */ |
| 2346 | + int t; /* Total width of all columns */ |
| 2347 | + int *aw; /* Array of individual column widths */ |
| 2348 | + |
| 2349 | + aw = sqlite3_malloc64( sizeof(int)*nCol ); |
| 2350 | + if( aw==0 ){ |
| 2351 | + qrfOom(p); |
| 2352 | + return 0; |
| 2353 | + } |
| 2354 | + nr = (pData->n + nCol - 1)/nCol; |
| 2355 | + for(i=0; i<pData->n; i++){ |
| 2356 | + if( (i%nr)==0 ){ |
| 2357 | + if( i>0 ) aw[i/nr-1] = w; |
| 2358 | + w = pData->aiWth[i]; |
| 2359 | + }else if( pData->aiWth[i]>w ){ |
| 2360 | + w = pData->aiWth[i]; |
| 2361 | + } |
| 2362 | + } |
| 2363 | + aw[nCol-1] = w; |
| 2364 | + for(t=i=0; i<nCol; i++) t += aw[i]; |
| 2365 | + t += 2*(nCol-1); |
| 2366 | + if( t>nSW ){ |
| 2367 | + sqlite3_free(aw); |
| 2368 | + return 0; |
| 2369 | + } |
| 2370 | + return aw; |
| 2371 | +} |
| 2372 | + |
| 2373 | +/* |
| 2374 | +** The output is single-column and the bSplitColumn flag is set. |
| 2375 | +** Check to see if the single-column output can be split into multiple |
| 2376 | +** columns that appear side-by-side. Adjust pData appropriately. |
| 2377 | +*/ |
| 2378 | +static void qrfSplitColumn(qrfColData *pData, Qrf *p){ |
| 2379 | + int nCol = 1; |
| 2380 | + int *aw = 0; |
| 2381 | + char **az = 0; |
| 2382 | + int *aiWth = 0; |
| 2383 | + int nColNext = 2; |
| 2384 | + int w; |
| 2385 | + struct qrfPerCol *a = 0; |
| 2386 | + sqlite3_int64 nRow = 1; |
| 2387 | + sqlite3_int64 i; |
| 2388 | + while( 1/*exit-by-break*/ ){ |
| 2389 | + int *awNew = qrfValidLayout(pData, p, nColNext, p->spec.nScreenWidth); |
| 2390 | + if( awNew==0 ) break; |
| 2391 | + sqlite3_free(aw); |
| 2392 | + aw = awNew; |
| 2393 | + nCol = nColNext; |
| 2394 | + nRow = (pData->n + nCol - 1)/nCol; |
| 2395 | + if( nRow==1 ) break; |
| 2396 | + nColNext++; |
| 2397 | + while( (pData->n + nColNext - 1)/nColNext == nRow ) nColNext++; |
| 2398 | + } |
| 2399 | + if( nCol==1 ){ |
| 2400 | + sqlite3_free(aw); |
| 2401 | + return; /* Cannot do better than 1 column */ |
| 2402 | + } |
| 2403 | + az = sqlite3_malloc64( nRow*nCol*sizeof(char*) ); |
| 2404 | + if( az==0 ){ |
| 2405 | + qrfOom(p); |
| 2406 | + return; |
| 2407 | + } |
| 2408 | + aiWth = sqlite3_malloc64( nRow*nCol*sizeof(int) ); |
| 2409 | + if( aiWth==0 ){ |
| 2410 | + sqlite3_free(az); |
| 2411 | + qrfOom(p); |
| 2412 | + return; |
| 2413 | + } |
| 2414 | + a = sqlite3_malloc64( nCol*sizeof(struct qrfPerCol) ); |
| 2415 | + if( a==0 ){ |
| 2416 | + sqlite3_free(az); |
| 2417 | + sqlite3_free(aiWth); |
| 2418 | + qrfOom(p); |
| 2419 | + return; |
| 2420 | + } |
| 2421 | + for(i=0; i<pData->n; i++){ |
| 2422 | + sqlite3_int64 j = (i%nRow)*nCol + (i/nRow); |
| 2423 | + az[j] = pData->az[i]; |
| 2424 | + pData->az[i] = 0; |
| 2425 | + aiWth[j] = pData->aiWth[i]; |
| 2426 | + } |
| 2427 | + while( i<nRow*nCol ){ |
| 2428 | + sqlite3_int64 j = (i%nRow)*nCol + (i/nRow); |
| 2429 | + az[j] = sqlite3_mprintf(""); |
| 2430 | + if( az[j]==0 ) qrfOom(p); |
| 2431 | + aiWth[j] = 0; |
| 2432 | + i++; |
| 2433 | + } |
| 2434 | + for(i=0; i<nCol; i++){ |
| 2435 | + a[i].fx = a[i].mxW = a[i].w = aw[i]; |
| 2436 | + a[i].e = pData->a[0].e; |
| 2437 | + } |
| 2438 | + sqlite3_free(pData->az); |
| 2439 | + sqlite3_free(pData->aiWth); |
| 2440 | + sqlite3_free(pData->a); |
| 2441 | + sqlite3_free(aw); |
| 2442 | + pData->az = az; |
| 2443 | + pData->aiWth = aiWth; |
| 2444 | + pData->a = a; |
| 2445 | + pData->nCol = nCol; |
| 2446 | + pData->n = pData->nAlloc = nRow*nCol; |
| 2447 | + for(i=w=0; i<nCol; i++) w += a[i].w; |
| 2448 | + pData->nMargin = (p->spec.nScreenWidth - w)/(nCol - 1); |
| 2449 | + if( pData->nMargin>5 ) pData->nMargin = 5; |
| 2450 | +} |
| 2310 | 2451 | |
| 2311 | 2452 | /* |
| 2312 | 2453 | ** Adjust the layout for the screen width restriction |
| 2313 | 2454 | */ |
| 2314 | 2455 | static void qrfRestrictScreenWidth(qrfColData *pData, Qrf *p){ |
| | @@ -2395,10 +2536,11 @@ |
| 2395 | 2536 | int rc; /* Result code */ |
| 2396 | 2537 | int nColumn = p->nCol; /* Number of columns */ |
| 2397 | 2538 | int bWW; /* True to do word-wrap */ |
| 2398 | 2539 | sqlite3_str *pStr; /* Temporary rendering */ |
| 2399 | 2540 | qrfColData data; /* Columnar layout data */ |
| 2541 | + int bRTrim; /* Trim trailing space */ |
| 2400 | 2542 | |
| 2401 | 2543 | rc = sqlite3_step(p->pStmt); |
| 2402 | 2544 | if( rc!=SQLITE_ROW || nColumn==0 ){ |
| 2403 | 2545 | return; /* No output */ |
| 2404 | 2546 | } |
| | @@ -2504,12 +2646,26 @@ |
| 2504 | 2646 | } |
| 2505 | 2647 | } |
| 2506 | 2648 | data.a[i].w = w; |
| 2507 | 2649 | } |
| 2508 | 2650 | |
| 2509 | | - /* Adjust the column widths due to screen width restrictions */ |
| 2510 | | - qrfRestrictScreenWidth(&data, p); |
| 2651 | + if( nColumn==1 |
| 2652 | + && data.n>1 |
| 2653 | + && p->spec.bSplitColumn==QRF_Yes |
| 2654 | + && p->spec.eStyle==QRF_STYLE_Column |
| 2655 | + && p->spec.bTitles==QRF_No |
| 2656 | + && p->spec.nScreenWidth>data.a[0].w+3 |
| 2657 | + ){ |
| 2658 | + /* Attempt to convert single-column tables into multi-column by |
| 2659 | + ** verticle wrapping, if the screen is wide enough and if the |
| 2660 | + ** bSplitColumn flag is set. */ |
| 2661 | + qrfSplitColumn(&data, p); |
| 2662 | + nColumn = data.nCol; |
| 2663 | + }else{ |
| 2664 | + /* Adjust the column widths due to screen width restrictions */ |
| 2665 | + qrfRestrictScreenWidth(&data, p); |
| 2666 | + } |
| 2511 | 2667 | |
| 2512 | 2668 | /* Draw the line across the top of the table. Also initialize |
| 2513 | 2669 | ** the row boundary and column separator texts. */ |
| 2514 | 2670 | switch( p->spec.eStyle ){ |
| 2515 | 2671 | case QRF_STYLE_Box: |
| | @@ -2536,11 +2692,17 @@ |
| 2536 | 2692 | } |
| 2537 | 2693 | qrfRowSeparator(p->pOut, &data, '+'); |
| 2538 | 2694 | break; |
| 2539 | 2695 | case QRF_STYLE_Column: |
| 2540 | 2696 | rowStart = ""; |
| 2541 | | - colSep = data.nMargin ? " " : " "; |
| 2697 | + if( data.nMargin<2 ){ |
| 2698 | + colSep = " "; |
| 2699 | + }else if( data.nMargin<=5 ){ |
| 2700 | + colSep = " " + (5-data.nMargin); |
| 2701 | + }else{ |
| 2702 | + colSep = " "; |
| 2703 | + } |
| 2542 | 2704 | rowSep = "\n"; |
| 2543 | 2705 | break; |
| 2544 | 2706 | default: /*case QRF_STYLE_Markdown:*/ |
| 2545 | 2707 | if( data.nMargin ){ |
| 2546 | 2708 | rowStart = "| "; |
| | @@ -2556,10 +2718,11 @@ |
| 2556 | 2718 | szRowStart = (int)strlen(rowStart); |
| 2557 | 2719 | szRowSep = (int)strlen(rowSep); |
| 2558 | 2720 | szColSep = (int)strlen(colSep); |
| 2559 | 2721 | |
| 2560 | 2722 | bWW = (p->spec.bWordWrap==QRF_Yes && data.bMultiRow); |
| 2723 | + bRTrim = (p->spec.eStyle==QRF_STYLE_Column); |
| 2561 | 2724 | for(i=0; i<data.n; i+=nColumn){ |
| 2562 | 2725 | int bMore; |
| 2563 | 2726 | int nRow = 0; |
| 2564 | 2727 | |
| 2565 | 2728 | /* Draw a single row of the table. This might be the title line |
| | @@ -2577,14 +2740,17 @@ |
| 2577 | 2740 | int nWS; |
| 2578 | 2741 | qrfWrapLine(data.a[j].z, data.a[j].w, bWW, &nThis, &nWide, &iNext); |
| 2579 | 2742 | nWS = data.a[j].w - nWide; |
| 2580 | 2743 | qrfPrintAligned(p->pOut, data.a[j].z, nThis, nWS, data.a[j].e); |
| 2581 | 2744 | data.a[j].z += iNext; |
| 2582 | | - if( data.a[j].z[0]!=0 ) bMore = 1; |
| 2745 | + if( data.a[j].z[0]!=0 ){ |
| 2746 | + bMore = 1; |
| 2747 | + } |
| 2583 | 2748 | if( j<nColumn-1 ){ |
| 2584 | 2749 | sqlite3_str_append(p->pOut, colSep, szColSep); |
| 2585 | 2750 | }else{ |
| 2751 | + if( bRTrim ) qrfRTrim(p->pOut); |
| 2586 | 2752 | sqlite3_str_append(p->pOut, rowSep, szRowSep); |
| 2587 | 2753 | } |
| 2588 | 2754 | } |
| 2589 | 2755 | }while( bMore && ++nRow < p->mxHeight ); |
| 2590 | 2756 | if( bMore ){ |
| | @@ -2599,10 +2765,11 @@ |
| 2599 | 2765 | qrfPrintAligned(p->pOut, "...", nE, data.a[j].w-nE, data.a[j].e); |
| 2600 | 2766 | } |
| 2601 | 2767 | if( j<nColumn-1 ){ |
| 2602 | 2768 | sqlite3_str_append(p->pOut, colSep, szColSep); |
| 2603 | 2769 | }else{ |
| 2770 | + if( bRTrim ) qrfRTrim(p->pOut); |
| 2604 | 2771 | sqlite3_str_append(p->pOut, rowSep, szRowSep); |
| 2605 | 2772 | } |
| 2606 | 2773 | } |
| 2607 | 2774 | } |
| 2608 | 2775 | |
| | @@ -2639,14 +2806,16 @@ |
| 2639 | 2806 | for(j=0; j<nColumn; j++){ |
| 2640 | 2807 | sqlite3_str_appendchar(p->pOut, data.a[j].w, '-'); |
| 2641 | 2808 | if( j<nColumn-1 ){ |
| 2642 | 2809 | sqlite3_str_append(p->pOut, colSep, szColSep); |
| 2643 | 2810 | }else{ |
| 2811 | + qrfRTrim(p->pOut); |
| 2644 | 2812 | sqlite3_str_append(p->pOut, rowSep, szRowSep); |
| 2645 | 2813 | } |
| 2646 | 2814 | } |
| 2647 | 2815 | }else if( data.bMultiRow ){ |
| 2816 | + qrfRTrim(p->pOut); |
| 2648 | 2817 | sqlite3_str_append(p->pOut, "\n", 1); |
| 2649 | 2818 | } |
| 2650 | 2819 | break; |
| 2651 | 2820 | } |
| 2652 | 2821 | } |
| | @@ -23946,16 +24115,17 @@ |
| 23946 | 24115 | #define MODE_List 12 /* One record per line with a separator */ |
| 23947 | 24116 | #define MODE_Markdown 13 /* Markdown formatting */ |
| 23948 | 24117 | #define MODE_Off 14 /* No query output shown */ |
| 23949 | 24118 | #define MODE_QBox 15 /* BOX with SQL-quoted content */ |
| 23950 | 24119 | #define MODE_Quote 16 /* Quote values as for SQL */ |
| 23951 | | -#define MODE_Table 17 /* MySQL-style table formatting */ |
| 23952 | | -#define MODE_Tabs 18 /* Tab-separated values */ |
| 23953 | | -#define MODE_Tcl 19 /* Space-separated list of TCL strings */ |
| 23954 | | -#define MODE_Www 20 /* Full web-page output */ |
| 24120 | +#define MODE_Split 17 /* Split-column mode */ |
| 24121 | +#define MODE_Table 18 /* MySQL-style table formatting */ |
| 24122 | +#define MODE_Tabs 19 /* Tab-separated values */ |
| 24123 | +#define MODE_Tcl 20 /* Space-separated list of TCL strings */ |
| 24124 | +#define MODE_Www 21 /* Full web-page output */ |
| 23955 | 24125 | |
| 23956 | | -#define MODE_BUILTIN 20 /* Maximum built-in mode */ |
| 24126 | +#define MODE_BUILTIN 21 /* Maximum built-in mode */ |
| 23957 | 24127 | #define MODE_BATCH 50 /* Default mode for batch processing */ |
| 23958 | 24128 | #define MODE_TTY 51 /* Default mode for interactive processing */ |
| 23959 | 24129 | #define MODE_USER 75 /* First user-defined mode */ |
| 23960 | 24130 | #define MODE_N_USER 25 /* Maximum number of user-defined modes */ |
| 23961 | 24131 | |
| | @@ -24000,10 +24170,11 @@ |
| 24000 | 24170 | { "list", 2, 1, 9, 1, 1, 1, 1, 12, 0 }, |
| 24001 | 24171 | { "markdown", 0, 0, 9, 1, 1, 1, 2, 13, 2 }, |
| 24002 | 24172 | { "off", 0, 0, 0, 0, 0, 0, 0, 14, 0 }, |
| 24003 | 24173 | { "qbox", 0, 0, 9, 2, 1, 2, 2, 1, 2 }, |
| 24004 | 24174 | { "quote", 4, 1, 10, 2, 2, 2, 1, 12, 0 }, |
| 24175 | + { "split", 0, 0, 9, 1, 1, 1, 1, 2, 2 }, |
| 24005 | 24176 | { "table", 0, 0, 9, 1, 1, 1, 2, 19, 2 }, |
| 24006 | 24177 | { "tabs", 8, 1, 9, 3, 3, 1, 1, 12, 0 }, |
| 24007 | 24178 | { "tcl", 3, 1, 12, 5, 5, 4, 1, 12, 0 }, |
| 24008 | 24179 | { "www", 0, 0, 9, 4, 4, 1, 2, 7, 0 } |
| 24009 | 24180 | }; /* | / / | / / | | \ |
| | @@ -24128,10 +24299,16 @@ |
| 24128 | 24299 | if( pI->eNull ) modeSetStr(&pM->spec.zNull, aModeStr[pI->eNull]); |
| 24129 | 24300 | pM->spec.eText = pI->eText; |
| 24130 | 24301 | pM->spec.eBlob = pI->eBlob; |
| 24131 | 24302 | pM->spec.bTitles = pI->bHdr; |
| 24132 | 24303 | pM->spec.eTitle = pI->eHdr; |
| 24304 | + if( eMode==MODE_Split ){ |
| 24305 | + pM->spec.bSplitColumn = QRF_Yes; |
| 24306 | + pM->bAutoScreenWidth = 1; |
| 24307 | + }else{ |
| 24308 | + pM->spec.bSplitColumn = QRF_No; |
| 24309 | + } |
| 24133 | 24310 | }else if( eMode>=MODE_USER && eMode-MODE_USER<p->nSavedModes ){ |
| 24134 | 24311 | modeFree(&p->mode); |
| 24135 | 24312 | modeDup(&p->mode, &p->aSavedModes[eMode-MODE_USER].mode); |
| 24136 | 24313 | }else if( eMode==MODE_BATCH ){ |
| 24137 | 24314 | u8 mFlags = p->mode.mFlags; |
| | @@ -26441,10 +26618,12 @@ |
| 26441 | 26618 | " --linelimit N Set the maximum number of output lines to show for\n" |
| 26442 | 26619 | " any single SQL value to N. Longer values are\n" |
| 26443 | 26620 | " truncated. Zero means \"no limit\". Only works\n" |
| 26444 | 26621 | " in \"line\" mode and in columnar modes.\n" |
| 26445 | 26622 | " --list List available modes\n" |
| 26623 | +" --no-limits Shorthand to turn off --linelimit, --charlimit,\n" |
| 26624 | +" and --screenwidth.\n" |
| 26446 | 26625 | " --null STRING Render SQL NULL values as the given string\n" |
| 26447 | 26626 | " --once Setting changes to the right are reverted after\n" |
| 26448 | 26627 | " the next SQL command.\n" |
| 26449 | 26628 | " --quote ARG Enable/disable quoting of text. ARG can be\n" |
| 26450 | 26629 | " \"off\", \"on\", \"sql\", \"csv\", \"html\", \"tcl\",\n" |
| | @@ -30320,10 +30499,12 @@ |
| 30320 | 30499 | ** --linelimit N Set the maximum number of output lines to show for |
| 30321 | 30500 | ** any single SQL value to N. Longer values are |
| 30322 | 30501 | ** truncated. Zero means "no limit". Only works |
| 30323 | 30502 | ** in "line" mode and in columnar modes. |
| 30324 | 30503 | ** --list List available modes |
| 30504 | +** --no-limits Shorthand to turn off --linelimit, --charlimit, |
| 30505 | +** and --screenwidth. |
| 30325 | 30506 | ** --null STRING Render SQL NULL values as the given string |
| 30326 | 30507 | ** --once Setting changes to the right are reverted after |
| 30327 | 30508 | ** the next SQL command. |
| 30328 | 30509 | ** --quote ARG Enable/disable quoting of text. ARG can be |
| 30329 | 30510 | ** "off", "on", "sql", "csv", "html", "tcl", |
| | @@ -30465,10 +30646,25 @@ |
| 30465 | 30646 | } |
| 30466 | 30647 | for(ii=0; ii<p->nSavedModes; ii++){ |
| 30467 | 30648 | cli_printf(p->out, " %s", p->aSavedModes[ii].zTag); |
| 30468 | 30649 | } |
| 30469 | 30650 | cli_puts(" batch tty\n", p->out); |
| 30651 | + }else if( optionMatch(z,"once") ){ |
| 30652 | + p->nPopMode = 0; |
| 30653 | + modePush(p); |
| 30654 | + p->nPopMode = 1; |
| 30655 | + }else if( optionMatch(z,"noquote") ){ |
| 30656 | + /* (undocumented legacy) --noquote always turns quoting off */ |
| 30657 | + p->mode.spec.eText = QRF_TEXT_Plain; |
| 30658 | + p->mode.spec.eBlob = QRF_BLOB_Text; |
| 30659 | + chng = 1; |
| 30660 | + }else if( optionMatch(z,"no-limits") ){ |
| 30661 | + p->mode.spec.nLineLimit = 0; |
| 30662 | + p->mode.spec.nCharLimit = 0; |
| 30663 | + p->mode.spec.nScreenWidth = 0; |
| 30664 | + p->mode.bAutoScreenWidth = 0; |
| 30665 | + chng = 1; |
| 30470 | 30666 | }else if( optionMatch(z,"quote") ){ |
| 30471 | 30667 | if( i+1<nArg |
| 30472 | 30668 | && azArg[i+1][0]!='-' |
| 30473 | 30669 | && (iMode>0 || strcmp(azArg[i+1],"off")==0 || modeFind(p, azArg[i+1])<0) |
| 30474 | 30670 | ){ |
| | @@ -30517,19 +30713,10 @@ |
| 30517 | 30713 | p->mode.spec.eText = QRF_TEXT_Plain; |
| 30518 | 30714 | p->mode.spec.eBlob = QRF_BLOB_Text; |
| 30519 | 30715 | break; |
| 30520 | 30716 | } |
| 30521 | 30717 | chng = 1; |
| 30522 | | - }else if( optionMatch(z,"once") ){ |
| 30523 | | - p->nPopMode = 0; |
| 30524 | | - modePush(p); |
| 30525 | | - p->nPopMode = 1; |
| 30526 | | - }else if( optionMatch(z,"noquote") ){ |
| 30527 | | - /* (undocumented legacy) --noquote always turns quoting off */ |
| 30528 | | - p->mode.spec.eText = QRF_TEXT_Plain; |
| 30529 | | - p->mode.spec.eBlob = QRF_BLOB_Text; |
| 30530 | | - chng = 1; |
| 30531 | 30718 | }else if( optionMatch(z,"reset") ){ |
| 30532 | 30719 | int saved_eMode = p->mode.eMode; |
| 30533 | 30720 | modeFree(&p->mode); |
| 30534 | 30721 | modeChange(p, saved_eMode); |
| 30535 | 30722 | }else if( optionMatch(z,"screenwidth") ){ |
| | @@ -31077,13 +31264,14 @@ |
| 31077 | 31264 | |
| 31078 | 31265 | dotCmdOutput_error: |
| 31079 | 31266 | sqlite3_free(zFile); |
| 31080 | 31267 | return 1; |
| 31081 | 31268 | } |
| 31269 | + |
| 31082 | 31270 | /* |
| 31083 | | -** Parse input line zLine up into individual arguments. Retain the |
| 31084 | | -** parse in the p->dot substructure. |
| 31271 | +** Enlarge the space allocated in p->dot so that it can hold more |
| 31272 | +** than nArg parsed command-line arguments. |
| 31085 | 31273 | */ |
| 31086 | 31274 | static void parseDotRealloc(ShellState *p, int nArg){ |
| 31087 | 31275 | p->dot.nAlloc = nArg+22; |
| 31088 | 31276 | p->dot.azArg = realloc(p->dot.azArg,p->dot.nAlloc*sizeof(char*)); |
| 31089 | 31277 | shell_check_oom(p->dot.azArg); |
| | @@ -31090,19 +31278,33 @@ |
| 31090 | 31278 | p->dot.aiOfst = realloc(p->dot.aiOfst,p->dot.nAlloc*sizeof(int)); |
| 31091 | 31279 | shell_check_oom(p->dot.aiOfst); |
| 31092 | 31280 | p->dot.abQuot = realloc(p->dot.abQuot,p->dot.nAlloc); |
| 31093 | 31281 | shell_check_oom(p->dot.abQuot); |
| 31094 | 31282 | } |
| 31283 | + |
| 31284 | + |
| 31285 | +/* |
| 31286 | +** Parse input line zLine up into individual arguments. Retain the |
| 31287 | +** parse in the p->dot substructure. |
| 31288 | +*/ |
| 31095 | 31289 | static void parseDotCmdArgs(const char *zLine, ShellState *p){ |
| 31096 | 31290 | char *z; |
| 31097 | 31291 | int h = 1; |
| 31098 | 31292 | int nArg = 0; |
| 31293 | + size_t szLine; |
| 31099 | 31294 | |
| 31100 | 31295 | p->dot.zOrig = zLine; |
| 31101 | 31296 | free(p->dot.zCopy); |
| 31102 | 31297 | z = p->dot.zCopy = strdup(zLine); |
| 31103 | 31298 | shell_check_oom(z); |
| 31299 | + szLine = strlen(z); |
| 31300 | + while( szLine>0 && IsSpace(z[szLine-1]) ) szLine--; |
| 31301 | + if( szLine>0 && z[szLine-1]==';' && p->iCompat>=20251115 ){ |
| 31302 | + szLine--; |
| 31303 | + while( szLine>0 && IsSpace(z[szLine-1]) ) szLine--; |
| 31304 | + } |
| 31305 | + z[szLine] = 0; |
| 31104 | 31306 | parseDotRealloc(p, 2); |
| 31105 | 31307 | while( z[h] ){ |
| 31106 | 31308 | while( IsSpace(z[h]) ){ h++; } |
| 31107 | 31309 | if( z[h]==0 ) break; |
| 31108 | 31310 | if( nArg+2>p->dot.nAlloc ){ |
| | @@ -33406,16 +33608,15 @@ |
| 33406 | 33608 | |
| 33407 | 33609 | if( (c=='t' && n>1 && cli_strncmp(azArg[0], "tables", n)==0) |
| 33408 | 33610 | || (c=='i' && (cli_strncmp(azArg[0], "indices", n)==0 |
| 33409 | 33611 | || cli_strncmp(azArg[0], "indexes", n)==0) ) |
| 33410 | 33612 | ){ |
| 33411 | | - sqlite3_stmt *pStmt; |
| 33412 | | - char **azResult; |
| 33413 | | - int nRow, nAlloc; |
| 33414 | 33613 | int ii; |
| 33415 | | - ShellText s; |
| 33416 | | - initText(&s); |
| 33614 | + sqlite3_stmt *pStmt; |
| 33615 | + sqlite3_str *pSql; |
| 33616 | + const char *zPattern = nArg>1 ? azArg[1] : 0; |
| 33617 | + |
| 33417 | 33618 | open_db(p, 0); |
| 33418 | 33619 | rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); |
| 33419 | 33620 | if( rc ){ |
| 33420 | 33621 | sqlite3_finalize(pStmt); |
| 33421 | 33622 | return shellDatabaseError(p->db); |
| | @@ -33428,90 +33629,50 @@ |
| 33428 | 33629 | eputz("Usage: .indexes ?LIKE-PATTERN?\n"); |
| 33429 | 33630 | rc = 1; |
| 33430 | 33631 | sqlite3_finalize(pStmt); |
| 33431 | 33632 | goto meta_command_exit; |
| 33432 | 33633 | } |
| 33634 | + pSql = sqlite3_str_new(p->db); |
| 33433 | 33635 | for(ii=0; sqlite3_step(pStmt)==SQLITE_ROW; ii++){ |
| 33434 | 33636 | const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1); |
| 33435 | 33637 | if( zDbName==0 ) continue; |
| 33436 | | - if( s.zTxt && s.zTxt[0] ) appendText(&s, " UNION ALL ", 0); |
| 33638 | + if( sqlite3_str_length(pSql) ){ |
| 33639 | + sqlite3_str_appendall(pSql, " UNION ALL "); |
| 33640 | + } |
| 33437 | 33641 | if( sqlite3_stricmp(zDbName, "main")==0 ){ |
| 33438 | | - appendText(&s, "SELECT name FROM ", 0); |
| 33642 | + sqlite3_str_appendall(pSql, "SELECT name FROM "); |
| 33439 | 33643 | }else{ |
| 33440 | | - appendText(&s, "SELECT ", 0); |
| 33441 | | - appendText(&s, zDbName, '\''); |
| 33442 | | - appendText(&s, "||'.'||name FROM ", 0); |
| 33644 | + sqlite3_str_appendf(pSql, "SELECT %Q||'.'||name FROM ", zDbName); |
| 33443 | 33645 | } |
| 33444 | | - appendText(&s, zDbName, '"'); |
| 33445 | | - appendText(&s, ".sqlite_schema ", 0); |
| 33646 | + sqlite3_str_appendf(pSql, "\"%w\".sqlite_schema", zDbName); |
| 33446 | 33647 | if( c=='t' ){ |
| 33447 | | - appendText(&s," WHERE type IN ('table','view')" |
| 33448 | | - " AND name NOT LIKE 'sqlite__%' ESCAPE '_'" |
| 33449 | | - " AND name LIKE ?1", 0); |
| 33648 | + sqlite3_str_appendf(pSql, |
| 33649 | + " WHERE type IN ('table','view')" |
| 33650 | + " AND name NOT LIKE 'sqlite__%%' ESCAPE '_'" |
| 33651 | + ); |
| 33652 | + if( zPattern ){ |
| 33653 | + sqlite3_str_appendf(pSql," AND name LIKE %Q", zPattern); |
| 33654 | + } |
| 33450 | 33655 | }else{ |
| 33451 | | - appendText(&s," WHERE type='index'" |
| 33452 | | - " AND tbl_name LIKE ?1", 0); |
| 33656 | + sqlite3_str_appendf(pSql, " WHERE type='index'"); |
| 33657 | + if( zPattern ){ |
| 33658 | + sqlite3_str_appendf(pSql," AND tbl_name LIKE %Q", zPattern); |
| 33659 | + } |
| 33453 | 33660 | } |
| 33454 | 33661 | } |
| 33455 | 33662 | rc = sqlite3_finalize(pStmt); |
| 33456 | 33663 | if( rc==SQLITE_OK ){ |
| 33457 | | - appendText(&s, " ORDER BY 1", 0); |
| 33458 | | - rc = sqlite3_prepare_v2(p->db, s.zTxt, -1, &pStmt, 0); |
| 33459 | | - } |
| 33460 | | - freeText(&s); |
| 33461 | | - if( rc ) return shellDatabaseError(p->db); |
| 33462 | | - |
| 33463 | | - /* Run the SQL statement prepared by the above block. Store the results |
| 33464 | | - ** as an array of nul-terminated strings in azResult[]. */ |
| 33465 | | - nRow = nAlloc = 0; |
| 33466 | | - azResult = 0; |
| 33467 | | - if( nArg>1 ){ |
| 33468 | | - sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT); |
| 33469 | | - }else{ |
| 33470 | | - sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC); |
| 33471 | | - } |
| 33472 | | - while( sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 33473 | | - if( nRow>=nAlloc ){ |
| 33474 | | - char **azNew; |
| 33475 | | - sqlite3_int64 n2 = 2*(sqlite3_int64)nAlloc + 10; |
| 33476 | | - azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2); |
| 33477 | | - shell_check_oom(azNew); |
| 33478 | | - nAlloc = (int)n2; |
| 33479 | | - azResult = azNew; |
| 33480 | | - } |
| 33481 | | - azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); |
| 33482 | | - shell_check_oom(azResult[nRow]); |
| 33483 | | - nRow++; |
| 33484 | | - } |
| 33485 | | - if( sqlite3_finalize(pStmt)!=SQLITE_OK ){ |
| 33486 | | - rc = shellDatabaseError(p->db); |
| 33487 | | - } |
| 33488 | | - |
| 33489 | | - /* Pretty-print the contents of array azResult[] to the output */ |
| 33490 | | - if( rc==0 && nRow>0 ){ |
| 33491 | | - int len, maxlen = 0; |
| 33492 | | - int i, j; |
| 33493 | | - int nPrintCol, nPrintRow; |
| 33494 | | - for(i=0; i<nRow; i++){ |
| 33495 | | - len = strlen30(azResult[i]); |
| 33496 | | - if( len>maxlen ) maxlen = len; |
| 33497 | | - } |
| 33498 | | - nPrintCol = shellScreenWidth()/(maxlen+2); |
| 33499 | | - if( nPrintCol<1 ) nPrintCol = 1; |
| 33500 | | - nPrintRow = (nRow + nPrintCol - 1)/nPrintCol; |
| 33501 | | - for(i=0; i<nPrintRow; i++){ |
| 33502 | | - for(j=i; j<nRow; j+=nPrintRow){ |
| 33503 | | - char *zSp = j<nPrintRow ? "" : " "; |
| 33504 | | - cli_printf(p->out, |
| 33505 | | - "%s%-*s", zSp, maxlen, azResult[j] ? azResult[j]:""); |
| 33506 | | - } |
| 33507 | | - cli_puts("\n", p->out); |
| 33508 | | - } |
| 33509 | | - } |
| 33510 | | - |
| 33511 | | - for(ii=0; ii<nRow; ii++) sqlite3_free(azResult[ii]); |
| 33512 | | - sqlite3_free(azResult); |
| 33664 | + sqlite3_str_appendall(pSql, " ORDER BY 1"); |
| 33665 | + } |
| 33666 | + |
| 33667 | + /* Run the SQL statement in "split" mode. */ |
| 33668 | + modePush(p); |
| 33669 | + modeChange(p, MODE_Split); |
| 33670 | + shell_exec(p, sqlite3_str_value(pSql), 0); |
| 33671 | + sqlite3_str_free(pSql); |
| 33672 | + modePop(p); |
| 33673 | + if( rc ) return shellDatabaseError(p->db); |
| 33513 | 33674 | }else |
| 33514 | 33675 | |
| 33515 | 33676 | #ifndef SQLITE_SHELL_FIDDLE |
| 33516 | 33677 | /* Begin redirecting output to the file "testcase-out.txt" */ |
| 33517 | 33678 | if( c=='t' && cli_strcmp(azArg[0],"testcase")==0 ){ |
| | @@ -35635,10 +35796,20 @@ |
| 35635 | 35796 | } |
| 35636 | 35797 | sqlite3_free(zErrMsg); |
| 35637 | 35798 | if( rc==0 ) rc = 1; |
| 35638 | 35799 | goto shell_main_exit; |
| 35639 | 35800 | } |
| 35801 | + if( data.nPopMode ){ |
| 35802 | + modePop(&data); |
| 35803 | + data.nPopMode = 0; |
| 35804 | + } |
| 35805 | + } |
| 35806 | + if( data.nPopOutput ){ |
| 35807 | + output_reset(&data); |
| 35808 | + data.nPopOutput = 0; |
| 35809 | + }else{ |
| 35810 | + clearTempFile(&data); |
| 35640 | 35811 | } |
| 35641 | 35812 | } |
| 35642 | 35813 | }else{ |
| 35643 | 35814 | /* Run commands received from standard input |
| 35644 | 35815 | */ |
| 35645 | 35816 | |