| | @@ -5,10 +5,11 @@ |
| 5 | 5 | ** |
| 6 | 6 | ** ext/expert/sqlite3expert.c |
| 7 | 7 | ** ext/expert/sqlite3expert.h |
| 8 | 8 | ** ext/intck/sqlite3intck.c |
| 9 | 9 | ** ext/intck/sqlite3intck.h |
| 10 | +** ext/misc/analyze.c |
| 10 | 11 | ** ext/misc/appendvfs.c |
| 11 | 12 | ** ext/misc/base64.c |
| 12 | 13 | ** ext/misc/base85.c |
| 13 | 14 | ** ext/misc/completion.c |
| 14 | 15 | ** ext/misc/decimal.c |
| | @@ -1305,11 +1306,10 @@ |
| 1305 | 1306 | } |
| 1306 | 1307 | } |
| 1307 | 1308 | if( nRow>=0 ){ |
| 1308 | 1309 | if( nSp ) sqlite3_str_appendchar(pStats, nSp, ' '); |
| 1309 | 1310 | qrfApproxInt64(pStats, nRow); |
| 1310 | | - nSp = 2; |
| 1311 | 1311 | if( p->spec.eStyle==QRF_STYLE_StatsEst ){ |
| 1312 | 1312 | sqlite3_str_appendf(pStats, " "); |
| 1313 | 1313 | qrfApproxInt64(pStats, (i64)rEstCum); |
| 1314 | 1314 | } |
| 1315 | 1315 | } |
| | @@ -1595,44 +1595,43 @@ |
| 1595 | 1595 | if( pnNL ) *pnNL = nNL; |
| 1596 | 1596 | return n; |
| 1597 | 1597 | } |
| 1598 | 1598 | |
| 1599 | 1599 | /* |
| 1600 | | -** Escape the input string if it is needed and in accordance with |
| 1601 | | -** eEsc, which is either QRF_ESC_Ascii or QRF_ESC_Symbol. |
| 1600 | +** Escape the text starting at byte iStart of pStr, if needed, using the |
| 1601 | +** escape encoding of eEsc, which is either QRF_ESC_Ascii or QRF_ESC_Symbol. |
| 1602 | +** The pStr string is modified appropriately. |
| 1602 | 1603 | ** |
| 1603 | 1604 | ** Escaping is needed if the string contains any control characters |
| 1604 | 1605 | ** other than \t, \n, and \r\n |
| 1605 | 1606 | ** |
| 1606 | | -** If no escaping is needed (the common case) then set *ppOut to NULL |
| 1607 | | -** and return 0. If escaping is needed, write the escaped string into |
| 1608 | | -** memory obtained from sqlite3_malloc64() and make *ppOut point to that |
| 1609 | | -** memory and return 0. If an error occurs, return non-zero. |
| 1610 | | -** |
| 1611 | | -** The caller is responsible for freeing *ppFree if it is non-NULL in order |
| 1612 | | -** to reclaim memory. |
| 1607 | +** If no escaping is needed (the common case) then pStr is unchanged. |
| 1608 | +** If escaping is required, then pStr is expanded and modified to hold |
| 1609 | +** an escaped representation of the text. |
| 1613 | 1610 | */ |
| 1614 | 1611 | static void qrfEscape( |
| 1615 | 1612 | int eEsc, /* QRF_ESC_Ascii or QRF_ESC_Symbol */ |
| 1616 | 1613 | sqlite3_str *pStr, /* String to be escaped */ |
| 1617 | 1614 | int iStart /* Begin escapding on this byte of pStr */ |
| 1618 | 1615 | ){ |
| 1619 | 1616 | sqlite3_int64 i, j; /* Loop counters */ |
| 1620 | | - sqlite3_int64 sz; /* Size of the string prior to escaping */ |
| 1621 | 1617 | sqlite3_int64 nCtrl = 0;/* Number of control characters to escape */ |
| 1622 | 1618 | unsigned char *zIn; /* Text to be escaped */ |
| 1619 | + unsigned int nIn; /* Bytes of text to be escaped */ |
| 1623 | 1620 | unsigned char c; /* A single character of the text */ |
| 1624 | 1621 | unsigned char *zOut; /* Where to write the results */ |
| 1625 | 1622 | |
| 1626 | 1623 | /* Find the text to be escaped */ |
| 1627 | 1624 | zIn = (unsigned char*)sqlite3_str_value(pStr); |
| 1625 | + nIn = sqlite3_str_length(pStr); |
| 1628 | 1626 | if( zIn==0 ) return; |
| 1629 | 1627 | zIn += iStart; |
| 1628 | + nIn -= iStart; |
| 1630 | 1629 | |
| 1631 | 1630 | /* Count the control characters */ |
| 1632 | | - for(i=0; (c = zIn[i])!=0; i++){ |
| 1633 | | - if( c<=0x1f |
| 1631 | + for(i=0; i<nIn; i++){ |
| 1632 | + if( (c = zIn[i])<=0x1f |
| 1634 | 1633 | && c!='\t' |
| 1635 | 1634 | && c!='\n' |
| 1636 | 1635 | && (c!='\r' || zIn[i+1]!='\n') |
| 1637 | 1636 | ){ |
| 1638 | 1637 | nCtrl++; |
| | @@ -1640,22 +1639,21 @@ |
| 1640 | 1639 | } |
| 1641 | 1640 | if( nCtrl==0 ) return; /* Early out if no control characters */ |
| 1642 | 1641 | |
| 1643 | 1642 | /* Make space to hold the escapes. Copy the original text to the end |
| 1644 | 1643 | ** of the available space. */ |
| 1645 | | - sz = sqlite3_str_length(pStr) - iStart; |
| 1646 | 1644 | if( eEsc==QRF_ESC_Symbol ) nCtrl *= 2; |
| 1647 | 1645 | sqlite3_str_appendchar(pStr, nCtrl, ' '); |
| 1648 | 1646 | zOut = (unsigned char*)sqlite3_str_value(pStr); |
| 1649 | 1647 | if( zOut==0 ) return; |
| 1650 | 1648 | zOut += iStart; |
| 1651 | 1649 | zIn = zOut + nCtrl; |
| 1652 | | - memmove(zIn,zOut,sz); |
| 1650 | + memmove(zIn,zOut,nIn); |
| 1653 | 1651 | |
| 1654 | 1652 | /* Convert the control characters */ |
| 1655 | | - for(i=j=0; (c = zIn[i])!=0; i++){ |
| 1656 | | - if( c>0x1f |
| 1653 | + for(i=j=0; i<nIn; i++){ |
| 1654 | + if( (c = zIn[i])>0x1f |
| 1657 | 1655 | || c=='\t' |
| 1658 | 1656 | || c=='\n' |
| 1659 | 1657 | || (c=='\r' && zIn[i+1]=='\n') |
| 1660 | 1658 | ){ |
| 1661 | 1659 | continue; |
| | @@ -1663,10 +1661,11 @@ |
| 1663 | 1661 | if( i>0 ){ |
| 1664 | 1662 | memmove(&zOut[j], zIn, i); |
| 1665 | 1663 | j += i; |
| 1666 | 1664 | } |
| 1667 | 1665 | zIn += i+1; |
| 1666 | + nIn -= i+1; |
| 1668 | 1667 | i = -1; |
| 1669 | 1668 | if( eEsc==QRF_ESC_Symbol ){ |
| 1670 | 1669 | zOut[j++] = 0xe2; |
| 1671 | 1670 | zOut[j++] = 0x90; |
| 1672 | 1671 | zOut[j++] = 0x80+c; |
| | @@ -2065,12 +2064,26 @@ |
| 2065 | 2064 | int nBlob = sqlite3_column_bytes(p->pStmt,iCol); |
| 2066 | 2065 | sqlite3_str_appendf(pOut, "(%d-byte blob)", nBlob); |
| 2067 | 2066 | break; |
| 2068 | 2067 | } |
| 2069 | 2068 | default: { |
| 2070 | | - const char *zTxt = (const char*)sqlite3_column_text(p->pStmt,iCol); |
| 2071 | | - qrfEncodeText(p, pOut, zTxt); |
| 2069 | + const void *pBlob = sqlite3_column_blob(p->pStmt,iCol); |
| 2070 | + int nBlob = sqlite3_column_bytes(p->pStmt,iCol); |
| 2071 | + int rc; |
| 2072 | + qrfWrite(p); |
| 2073 | + if( nBlob==0 ){ |
| 2074 | + /* no-op */ |
| 2075 | + }else if( p->spec.eEsc==QRF_ESC_Off ){ |
| 2076 | + rc = p->spec.xWrite(p->spec.pWriteArg,pBlob,nBlob); |
| 2077 | + if( rc ){ |
| 2078 | + qrfError(p, rc, "Failed to write %d bytes of BLOB output", nBlob); |
| 2079 | + } |
| 2080 | + }else{ |
| 2081 | + sqlite3_str_append(pOut, pBlob, nBlob); |
| 2082 | + qrfEscape(p->spec.eEsc, pOut, 0); |
| 2083 | + qrfWrite(p); |
| 2084 | + } |
| 2072 | 2085 | } |
| 2073 | 2086 | } |
| 2074 | 2087 | break; |
| 2075 | 2088 | } |
| 2076 | 2089 | case SQLITE_NULL: { |
| | @@ -6005,11 +6018,11 @@ |
| 6005 | 6018 | } |
| 6006 | 6019 | if( p->isNull ){ |
| 6007 | 6020 | sqlite3_result_null(pCtx); |
| 6008 | 6021 | return; |
| 6009 | 6022 | } |
| 6010 | | - z = sqlite3_malloc64( (sqlite3_int64)p->nDigit+4 ); |
| 6023 | + z = sqlite3_malloc64( (sqlite3_int64)p->nDigit+8 ); |
| 6011 | 6024 | if( z==0 ){ |
| 6012 | 6025 | sqlite3_result_error_nomem(pCtx); |
| 6013 | 6026 | return; |
| 6014 | 6027 | } |
| 6015 | 6028 | i = 0; |
| | @@ -9789,12 +9802,10 @@ |
| 9789 | 9802 | /* # include "windirent.h" */ |
| 9790 | 9803 | # include <direct.h> |
| 9791 | 9804 | # define STRUCT_STAT struct _stat |
| 9792 | 9805 | # define chmod(path,mode) fileio_chmod(path,mode) |
| 9793 | 9806 | # define mkdir(path,mode) fileio_mkdir(path) |
| 9794 | | - extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*); |
| 9795 | | - extern char *sqlite3_win32_unicode_to_utf8(LPCWSTR); |
| 9796 | 9807 | #endif |
| 9797 | 9808 | #include <time.h> |
| 9798 | 9809 | #include <errno.h> |
| 9799 | 9810 | |
| 9800 | 9811 | /* When used as part of the CLI, the sqlite3_stdio.h module will have |
| | @@ -9817,17 +9828,73 @@ |
| 9817 | 9828 | #define FSDIR_COLUMN_DATA 3 /* File content */ |
| 9818 | 9829 | #define FSDIR_COLUMN_LEVEL 4 /* Level. Topmost is 1 */ |
| 9819 | 9830 | #define FSDIR_COLUMN_PATH 5 /* Path to top of search */ |
| 9820 | 9831 | #define FSDIR_COLUMN_DIR 6 /* Path is relative to this directory */ |
| 9821 | 9832 | |
| 9833 | +#ifdef _WIN32 |
| 9834 | +/* |
| 9835 | +** Convert a UTF-8 string to Microsoft Unicode. |
| 9836 | +** |
| 9837 | +** Space to hold the returned string is obtained from sqlite3_malloc(). |
| 9838 | +*/ |
| 9839 | +static wchar_t *winUtf8To16(const char *zText){ |
| 9840 | + int nChar; |
| 9841 | + wchar_t *zWideText; |
| 9842 | + |
| 9843 | + nChar = MultiByteToWideChar(CP_UTF8, 0, zText, -1, NULL, 0); |
| 9844 | + if( nChar==0 ){ |
| 9845 | + return 0; |
| 9846 | + } |
| 9847 | + zWideText = sqlite3_malloc64(nChar*sizeof(WCHAR) ); |
| 9848 | + if( zWideText==0 ){ |
| 9849 | + return 0; |
| 9850 | + } |
| 9851 | + nChar = MultiByteToWideChar(CP_UTF8, 0, zText, -1, zWideText, |
| 9852 | + nChar); |
| 9853 | + if( nChar==0 ){ |
| 9854 | + sqlite3_free(zWideText); |
| 9855 | + zWideText = 0; |
| 9856 | + } |
| 9857 | + return zWideText; |
| 9858 | +} |
| 9859 | +#endif /* _WIN32 */ |
| 9860 | + |
| 9861 | +#ifdef _WIN32 |
| 9862 | +/* |
| 9863 | +** Convert a Microsoft Unicode string to UTF-8. |
| 9864 | +** |
| 9865 | +** Space to hold the returned string is obtained from sqlite3_malloc(). |
| 9866 | +*/ |
| 9867 | +static char *winUtf16To8(wchar_t *zWideText){ |
| 9868 | + int nByte; |
| 9869 | + char *zText; |
| 9870 | + |
| 9871 | + nByte = WideCharToMultiByte(CP_UTF8, 0, zWideText, -1, 0, 0, 0, 0); |
| 9872 | + if( nByte == 0 ){ |
| 9873 | + return 0; |
| 9874 | + } |
| 9875 | + zText = sqlite3_malloc64( nByte ); |
| 9876 | + if( zText==0 ){ |
| 9877 | + return 0; |
| 9878 | + } |
| 9879 | + nByte = WideCharToMultiByte(CP_UTF8, 0, zWideText, -1, zText, nByte, |
| 9880 | + 0, 0); |
| 9881 | + if( nByte == 0 ){ |
| 9882 | + sqlite3_free(zText); |
| 9883 | + zText = 0; |
| 9884 | + } |
| 9885 | + return zText; |
| 9886 | +} |
| 9887 | +#endif /* _WIN32 */ |
| 9888 | + |
| 9822 | 9889 | /* |
| 9823 | 9890 | ** UTF8 chmod() function for Windows |
| 9824 | 9891 | */ |
| 9825 | 9892 | #if defined(_WIN32) || defined(WIN32) |
| 9826 | 9893 | static int fileio_chmod(const char *zPath, int pmode){ |
| 9827 | 9894 | int rc; |
| 9828 | | - wchar_t *b1 = sqlite3_win32_utf8_to_unicode(zPath); |
| 9895 | + wchar_t *b1 = winUtf8To16(zPath); |
| 9829 | 9896 | if( b1==0 ) return -1; |
| 9830 | 9897 | rc = _wchmod(b1, pmode); |
| 9831 | 9898 | sqlite3_free(b1); |
| 9832 | 9899 | return rc; |
| 9833 | 9900 | } |
| | @@ -9837,11 +9904,11 @@ |
| 9837 | 9904 | ** UTF8 mkdir() function for Windows |
| 9838 | 9905 | */ |
| 9839 | 9906 | #if defined(_WIN32) || defined(WIN32) |
| 9840 | 9907 | static int fileio_mkdir(const char *zPath){ |
| 9841 | 9908 | int rc; |
| 9842 | | - wchar_t *b1 = sqlite3_win32_utf8_to_unicode(zPath); |
| 9909 | + wchar_t *b1 = winUtf8To16(zPath); |
| 9843 | 9910 | if( b1==0 ) return -1; |
| 9844 | 9911 | rc = _wmkdir(b1); |
| 9845 | 9912 | sqlite3_free(b1); |
| 9846 | 9913 | return rc; |
| 9847 | 9914 | } |
| | @@ -9964,11 +10031,11 @@ |
| 9964 | 10031 | const char *zPath, |
| 9965 | 10032 | STRUCT_STAT *pStatBuf |
| 9966 | 10033 | ){ |
| 9967 | 10034 | #if defined(_WIN32) |
| 9968 | 10035 | int rc; |
| 9969 | | - wchar_t *b1 = sqlite3_win32_utf8_to_unicode(zPath); |
| 10036 | + wchar_t *b1 = winUtf8To16(zPath); |
| 9970 | 10037 | if( b1==0 ) return 1; |
| 9971 | 10038 | rc = _wstat(b1, pStatBuf); |
| 9972 | 10039 | if( rc==0 ){ |
| 9973 | 10040 | HANDLE hFindFile; |
| 9974 | 10041 | WIN32_FIND_DATAW fd; |
| | @@ -10117,18 +10184,17 @@ |
| 10117 | 10184 | FILETIME lastWrite; |
| 10118 | 10185 | SYSTEMTIME currentTime; |
| 10119 | 10186 | LONGLONG intervals; |
| 10120 | 10187 | HANDLE hFile; |
| 10121 | 10188 | LPWSTR zUnicodeName; |
| 10122 | | - extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*); |
| 10123 | 10189 | |
| 10124 | 10190 | GetSystemTime(¤tTime); |
| 10125 | 10191 | SystemTimeToFileTime(¤tTime, &lastAccess); |
| 10126 | 10192 | intervals = (mtime*10000000) + 116444736000000000; |
| 10127 | 10193 | lastWrite.dwLowDateTime = (DWORD)intervals; |
| 10128 | 10194 | lastWrite.dwHighDateTime = intervals >> 32; |
| 10129 | | - zUnicodeName = sqlite3_win32_utf8_to_unicode(zFile); |
| 10195 | + zUnicodeName = winUtf8To16(zFile); |
| 10130 | 10196 | if( zUnicodeName==0 ){ |
| 10131 | 10197 | return 1; |
| 10132 | 10198 | } |
| 10133 | 10199 | hFile = CreateFileW( |
| 10134 | 10200 | zUnicodeName, FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, |
| | @@ -10782,16 +10848,16 @@ |
| 10782 | 10848 | char *zOut = 0; /* Result */ |
| 10783 | 10849 | wchar_t *z = 0; /* Temporary buffer */ |
| 10784 | 10850 | |
| 10785 | 10851 | if( zPath==0 ) return 0; |
| 10786 | 10852 | |
| 10787 | | - zPath16 = sqlite3_win32_utf8_to_unicode(zPath); |
| 10853 | + zPath16 = winUtf8To16(zPath); |
| 10788 | 10854 | if( zPath16==0 ) return 0; |
| 10789 | 10855 | z = _wfullpath(NULL, zPath16, 0); |
| 10790 | 10856 | sqlite3_free(zPath16); |
| 10791 | 10857 | if( z ){ |
| 10792 | | - zOut = sqlite3_win32_unicode_to_utf8(z); |
| 10858 | + zOut = winUtf16To8(z); |
| 10793 | 10859 | free(z); |
| 10794 | 10860 | } |
| 10795 | 10861 | return zOut; |
| 10796 | 10862 | |
| 10797 | 10863 | #endif /* End WINDOWS, Begin common code */ |
| | @@ -19398,10 +19464,848 @@ |
| 19398 | 19464 | sqlite3_vfs_unregister(pVfs); |
| 19399 | 19465 | sqlite3_free(pVfs); |
| 19400 | 19466 | } |
| 19401 | 19467 | |
| 19402 | 19468 | /************************* End ext/misc/vfstrace.c ********************/ |
| 19469 | +/************************* Begin ext/misc/analyze.c ******************/ |
| 19470 | +/* |
| 19471 | +** 2026-04-13 |
| 19472 | +** |
| 19473 | +** The author disclaims copyright to this source code. In place of |
| 19474 | +** a legal notice, here is a blessing: |
| 19475 | +** |
| 19476 | +** May you do good and not evil. |
| 19477 | +** May you find forgiveness for yourself and forgive others. |
| 19478 | +** May you share freely, never taking more than you give. |
| 19479 | +** |
| 19480 | +****************************************************************************** |
| 19481 | +** |
| 19482 | +** Partial reimplement of the sqlite3_analyzer utility program as |
| 19483 | +** loadable SQL function. |
| 19484 | +*/ |
| 19485 | +/* #include "sqlite3ext.h" */ |
| 19486 | +SQLITE_EXTENSION_INIT1 |
| 19487 | +#include <assert.h> |
| 19488 | +#include <string.h> |
| 19489 | +#include <ctype.h> |
| 19490 | +#include <math.h> |
| 19491 | + |
| 19492 | +/* |
| 19493 | +** State information for the analysis |
| 19494 | +*/ |
| 19495 | +typedef struct Analysis Analysis; |
| 19496 | +struct Analysis { |
| 19497 | + sqlite3 *db; /* Database connection */ |
| 19498 | + sqlite3_context *context; /* SQL function context */ |
| 19499 | + sqlite3_str *pOut; /* Write output here */ |
| 19500 | + char *zSU; /* Name of the temp.space_used table */ |
| 19501 | + const char *zSchema; /* Schema to be analyzed */ |
| 19502 | +}; |
| 19503 | + |
| 19504 | +/* |
| 19505 | +** Free all resources that the Analysis object references and |
| 19506 | +** reset the Analysis object. |
| 19507 | +** |
| 19508 | +** Call this routine multiple times on the same Analysis object |
| 19509 | +** is a harmless no-op, as long as the memory for the object itself |
| 19510 | +** has not been freed. |
| 19511 | +*/ |
| 19512 | +static void analysisReset(Analysis *p){ |
| 19513 | + if( p->zSU ){ |
| 19514 | + char *zSql = sqlite3_mprintf("DROP TABLE temp.%s;", p->zSU); |
| 19515 | + if( zSql ){ |
| 19516 | + sqlite3_exec(p->db, zSql, 0, 0, 0); |
| 19517 | + sqlite3_free(zSql); |
| 19518 | + } |
| 19519 | + } |
| 19520 | + sqlite3_str_free(p->pOut); |
| 19521 | + sqlite3_free(p->zSU); |
| 19522 | + memset(p, 0, sizeof(*p)); |
| 19523 | +} |
| 19524 | + |
| 19525 | +/* |
| 19526 | +** Report an error using formatted text. If zFormat==NULL then report |
| 19527 | +** an OOM error. |
| 19528 | +*/ |
| 19529 | +static void analysisError(Analysis *p, const char *zFormat, ...){ |
| 19530 | + char *zErr; |
| 19531 | + if( zFormat ){ |
| 19532 | + va_list ap; |
| 19533 | + va_start(ap, zFormat); |
| 19534 | + zErr = sqlite3_vmprintf(zFormat, ap); |
| 19535 | + va_end(ap); |
| 19536 | + }else{ |
| 19537 | + zErr = 0; |
| 19538 | + } |
| 19539 | + if( zErr==0 ){ |
| 19540 | + sqlite3_result_error_nomem(p->context); |
| 19541 | + }else{ |
| 19542 | + sqlite3_result_error(p->context, zErr, -1); |
| 19543 | + sqlite3_free(zErr); |
| 19544 | + } |
| 19545 | + analysisReset(p); |
| 19546 | +} |
| 19547 | + |
| 19548 | +/* |
| 19549 | +** Prepare and return an SQL statement. |
| 19550 | +*/ |
| 19551 | +static sqlite3_stmt *analysisVPrep(Analysis *p, const char *zFmt, va_list ap){ |
| 19552 | + char *zSql; |
| 19553 | + int rc; |
| 19554 | + sqlite3_stmt *pStmt = 0; |
| 19555 | + zSql = sqlite3_vmprintf(zFmt, ap); |
| 19556 | + if( zSql==0 ){ analysisError(p,0); return 0; } |
| 19557 | + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); |
| 19558 | + if( rc ){ |
| 19559 | + analysisError(p, "SQL parse error: %s\nOriginal SQL: %s", |
| 19560 | + sqlite3_errmsg(p->db), zSql); |
| 19561 | + sqlite3_finalize(pStmt); |
| 19562 | + analysisReset(p); |
| 19563 | + pStmt = 0; |
| 19564 | + } |
| 19565 | + sqlite3_free(zSql); |
| 19566 | + return pStmt; |
| 19567 | +} |
| 19568 | +static sqlite3_stmt *analysisPrepare(Analysis *p, const char *zFormat, ...){ |
| 19569 | + va_list ap; |
| 19570 | + sqlite3_stmt *pStmt = 0; |
| 19571 | + va_start(ap, zFormat); |
| 19572 | + pStmt = analysisVPrep(p,zFormat,ap); |
| 19573 | + va_end(ap); |
| 19574 | + return pStmt; |
| 19575 | +} |
| 19576 | + |
| 19577 | +/* |
| 19578 | +** If rc is something other than SQLITE_DONE or SQLITE_OK, then report |
| 19579 | +** an error and return true. |
| 19580 | +** |
| 19581 | +** If rc is SQLITE_DONE or SQLITE_OK, then return false. |
| 19582 | +** |
| 19583 | +** The prepared statement is closed in either case. |
| 19584 | +*/ |
| 19585 | +static int analysisStmtFinish(Analysis *p, int rc, sqlite3_stmt *pStmt){ |
| 19586 | + if( rc==SQLITE_DONE ){ |
| 19587 | + rc = SQLITE_OK; |
| 19588 | + } |
| 19589 | + if( rc!=SQLITE_OK || (rc = sqlite3_reset(pStmt))!=SQLITE_OK ){ |
| 19590 | + analysisError(p, "SQL run-time error: %s\nOriginal SQL: %s", |
| 19591 | + sqlite3_errmsg(p->db), sqlite3_sql(pStmt)); |
| 19592 | + analysisReset(p); |
| 19593 | + } |
| 19594 | + sqlite3_finalize(pStmt); |
| 19595 | + return rc; |
| 19596 | +} |
| 19597 | + |
| 19598 | +/* |
| 19599 | +** Run SQL. Return the number of errors. |
| 19600 | +*/ |
| 19601 | +static int analysisSql(Analysis *p, const char *zFormat, ...){ |
| 19602 | + va_list ap; |
| 19603 | + int rc; |
| 19604 | + sqlite3_stmt *pStmt = 0; |
| 19605 | + va_start(ap, zFormat); |
| 19606 | + pStmt = analysisVPrep(p,zFormat,ap); |
| 19607 | + va_end(ap); |
| 19608 | + if( pStmt==0 ) return 1; |
| 19609 | + while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){} |
| 19610 | + if( rc==SQLITE_DONE ){ |
| 19611 | + rc = SQLITE_OK; |
| 19612 | + }else{ |
| 19613 | + analysisError(p, "SQL run-time error: %s\nOriginal SQL: %s", |
| 19614 | + sqlite3_errmsg(p->db), sqlite3_sql(pStmt)); |
| 19615 | + analysisReset(p); |
| 19616 | + } |
| 19617 | + sqlite3_finalize(pStmt); |
| 19618 | + return rc; |
| 19619 | +} |
| 19620 | + |
| 19621 | +/* |
| 19622 | +** Run an SQL query that returns an integer. Write that integer |
| 19623 | +** into *piRes. Return the number of errors. |
| 19624 | +*/ |
| 19625 | +static int analysisSqlInt( |
| 19626 | + Analysis *p, |
| 19627 | + sqlite3_int64 *piRes, |
| 19628 | + const char *zFormat, ... |
| 19629 | +){ |
| 19630 | + va_list ap; |
| 19631 | + int rc; |
| 19632 | + sqlite3_stmt *pStmt = 0; |
| 19633 | + va_start(ap, zFormat); |
| 19634 | + pStmt = analysisVPrep(p,zFormat,ap); |
| 19635 | + va_end(ap); |
| 19636 | + if( pStmt==0 ) return 1; |
| 19637 | + rc = sqlite3_step(pStmt); |
| 19638 | + if( rc==SQLITE_ROW ){ |
| 19639 | + *piRes = sqlite3_column_int64(pStmt, 0); |
| 19640 | + rc = SQLITE_OK; |
| 19641 | + }else if( rc==SQLITE_DONE ){ |
| 19642 | + rc = SQLITE_OK; |
| 19643 | + }else{ |
| 19644 | + if( p->db ){ |
| 19645 | + /* p->db is NULL if there was some prior error */ |
| 19646 | + analysisError(p, "SQL run-time error: %s\nOriginal SQL: %s", |
| 19647 | + sqlite3_errmsg(p->db), sqlite3_sql(pStmt)); |
| 19648 | + } |
| 19649 | + analysisReset(p); |
| 19650 | + } |
| 19651 | + sqlite3_finalize(pStmt); |
| 19652 | + return rc; |
| 19653 | +} |
| 19654 | + |
| 19655 | +/* |
| 19656 | +** Add to the output a title line that contains the text determined |
| 19657 | +** by the format string. If the output is initially empty, begin |
| 19658 | +** the title line with "/" so that it forms the beginning of a C-style |
| 19659 | +** comment. Otherwise begin with a new-line. Always finish with a |
| 19660 | +** newline. |
| 19661 | +*/ |
| 19662 | +static void analysisTitle(Analysis *p, const char *zFormat, ...){ |
| 19663 | + char *zFirst; |
| 19664 | + char *zTitle; |
| 19665 | + size_t nTitle; |
| 19666 | + va_list ap; |
| 19667 | + va_start(ap, zFormat); |
| 19668 | + zTitle = sqlite3_vmprintf(zFormat, ap); |
| 19669 | + va_end(ap); |
| 19670 | + if( zTitle==0 ){ |
| 19671 | + analysisError(p, 0); |
| 19672 | + return; |
| 19673 | + } |
| 19674 | + zFirst = sqlite3_str_length(p->pOut)==0 ? "/" : "\n*"; |
| 19675 | + nTitle = strlen(zTitle); |
| 19676 | + if( nTitle>=75 ){ |
| 19677 | + sqlite3_str_appendf(p->pOut, "%s** %z\n\n", zFirst, zTitle); |
| 19678 | + }else{ |
| 19679 | + int nExtra = 74 - (int)nTitle; |
| 19680 | + sqlite3_str_appendf(p->pOut, "%s** %z %.*c\n\n", zFirst, zTitle, |
| 19681 | + nExtra, '*'); |
| 19682 | + } |
| 19683 | +} |
| 19684 | + |
| 19685 | +/* |
| 19686 | +** Add an output line that begins with the zDesc text extended out to |
| 19687 | +** 50 columns with "." characters, and followed by whatever text is |
| 19688 | +** described by zFormat. |
| 19689 | +*/ |
| 19690 | +static void analysisLine( |
| 19691 | + Analysis *p, /* Analysis context */ |
| 19692 | + const char *zDesc, /* Description */ |
| 19693 | + const char *zFormat, /* Argument to the description */ |
| 19694 | + ... |
| 19695 | +){ |
| 19696 | + char *zTxt; |
| 19697 | + size_t nDesc; |
| 19698 | + va_list ap; |
| 19699 | + va_start(ap, zFormat); |
| 19700 | + zTxt = sqlite3_vmprintf(zFormat, ap); |
| 19701 | + va_end(ap); |
| 19702 | + if( zTxt==0 ){ |
| 19703 | + analysisError(p, 0); |
| 19704 | + return; |
| 19705 | + } |
| 19706 | + nDesc = strlen(zDesc); |
| 19707 | + if( nDesc>=50 ){ |
| 19708 | + sqlite3_str_appendf(p->pOut, "%s %z", zDesc, zTxt); |
| 19709 | + }else{ |
| 19710 | + int nExtra = 50 - (int)nDesc; |
| 19711 | + sqlite3_str_appendf(p->pOut, "%s%.*c %z", zDesc, nExtra, '.', zTxt); |
| 19712 | + } |
| 19713 | +} |
| 19714 | + |
| 19715 | +/* |
| 19716 | +** Write a percentage into the output. The number written should show |
| 19717 | +** two or three significant digits, with the decimal point being the fourth |
| 19718 | +** character. |
| 19719 | +*/ |
| 19720 | +static void analysisPercent(Analysis *p, double r){ |
| 19721 | + char zNum[100]; |
| 19722 | + char *zDP; |
| 19723 | + int nLeadingDigit; |
| 19724 | + int sz; |
| 19725 | + sqlite3_snprintf(sizeof(zNum)-5, zNum, r>=10.0 ? "%.3g" :"%.2g", r); |
| 19726 | + sz = (int)strlen(zNum); |
| 19727 | + zDP = strchr(zNum, '.'); |
| 19728 | + if( zDP==0 ){ |
| 19729 | + memcpy(zNum+sz,".0",3); |
| 19730 | + nLeadingDigit = sz; |
| 19731 | + sz += 2; |
| 19732 | + }else{ |
| 19733 | + nLeadingDigit = (int)(zDP - zNum); |
| 19734 | + } |
| 19735 | + if( nLeadingDigit<3 ){ |
| 19736 | + sqlite3_str_appendchar(p->pOut, 3-nLeadingDigit, ' '); |
| 19737 | + } |
| 19738 | + sqlite3_str_append(p->pOut, zNum, sz); |
| 19739 | + sqlite3_str_append(p->pOut, "%\n", 2); |
| 19740 | +} |
| 19741 | + |
| 19742 | +/* |
| 19743 | +** Create a subreport on a subset of tables and/or indexes. |
| 19744 | +** |
| 19745 | +** The title if the subreport is given by zTitle. zWhere is |
| 19746 | +** a boolean expression that can go in the WHERE clause to select |
| 19747 | +** the relevant rows of the s.zSU table. |
| 19748 | +*/ |
| 19749 | +static int analysisSubreport( |
| 19750 | + Analysis *p, /* Analysis context */ |
| 19751 | + char *zTitle, /* Title for this subreport */ |
| 19752 | + char *zWhere, /* WHERE clause for this subreport */ |
| 19753 | + sqlite3_int64 pgsz, /* Database page size */ |
| 19754 | + sqlite3_int64 nPage /* Number of pages in entire database */ |
| 19755 | +){ |
| 19756 | + sqlite3_stmt *pStmt; /* Statement to query p->zSU */ |
| 19757 | + sqlite3_int64 nentry; /* Number of btree entires */ |
| 19758 | + sqlite3_int64 payload; /* Payload in bytes */ |
| 19759 | + sqlite3_int64 ovfl_payload; /* overflow payload in bytes */ |
| 19760 | + sqlite3_int64 mx_payload; /* largest individual payload */ |
| 19761 | + sqlite3_int64 ovfl_cnt; /* Number entries using overflow */ |
| 19762 | + sqlite3_int64 leaf_pages; /* Leaf pages */ |
| 19763 | + sqlite3_int64 int_pages; /* internal pages */ |
| 19764 | + sqlite3_int64 ovfl_pages; /* overflow pages */ |
| 19765 | + sqlite3_int64 leaf_unused; /* unused bytes on leaf pages */ |
| 19766 | + sqlite3_int64 int_unused; /* unused bytes on internal pages */ |
| 19767 | + sqlite3_int64 ovfl_unused; /* unused bytes on overflow pages */ |
| 19768 | + sqlite3_int64 int_cell; /* B-tree entries on internal pages */ |
| 19769 | + sqlite3_int64 depth; /* btree depth */ |
| 19770 | + sqlite3_int64 cnt; /* Number of s.zSU entries that match */ |
| 19771 | + sqlite3_int64 storage; /* Total bytes */ |
| 19772 | + sqlite3_int64 total_pages; /* Total page count */ |
| 19773 | + sqlite3_int64 total_unused; /* Total unused bytes */ |
| 19774 | + sqlite3_int64 total_meta; /* Total metadata */ |
| 19775 | + int rc; |
| 19776 | + |
| 19777 | + if( zTitle==0 || zWhere==0 ){ |
| 19778 | + analysisError(p, 0); |
| 19779 | + return SQLITE_NOMEM; |
| 19780 | + } |
| 19781 | + pStmt = analysisPrepare(p, |
| 19782 | + "SELECT\n" |
| 19783 | + " sum(if(is_without_rowid OR is_index,nentry,leaf_entries)),\n" /* 0 */ |
| 19784 | + " sum(payload),\n" /* 1 */ |
| 19785 | + " sum(ovfl_payload),\n" /* 2 */ |
| 19786 | + " max(mx_payload),\n" /* 3 */ |
| 19787 | + " sum(ovfl_cnt),\n" /* 4 */ |
| 19788 | + " sum(leaf_pages),\n" /* 5 */ |
| 19789 | + " sum(int_pages),\n" /* 6 */ |
| 19790 | + " sum(ovfl_pages),\n" /* 7 */ |
| 19791 | + " sum(leaf_unused),\n" /* 8 */ |
| 19792 | + " sum(int_unused),\n" /* 9 */ |
| 19793 | + " sum(ovfl_unused),\n" /* 10 */ |
| 19794 | + " max(depth),\n" /* 11 */ |
| 19795 | + " count(*),\n" /* 12 */ |
| 19796 | + " sum(int_entries)\n" /* 13 */ |
| 19797 | + " FROM temp.%s WHERE %s", |
| 19798 | + p->zSU, zWhere); |
| 19799 | + if( pStmt==0 ) return 1; |
| 19800 | + rc = sqlite3_step(pStmt); |
| 19801 | + if( rc==SQLITE_ROW ){ |
| 19802 | + analysisTitle(p, zTitle); |
| 19803 | + |
| 19804 | + nentry = sqlite3_column_int64(pStmt, 0); |
| 19805 | + payload = sqlite3_column_int64(pStmt, 1); |
| 19806 | + ovfl_payload = sqlite3_column_int64(pStmt, 2); |
| 19807 | + mx_payload = sqlite3_column_int64(pStmt, 3); |
| 19808 | + ovfl_cnt = sqlite3_column_int64(pStmt, 4); |
| 19809 | + leaf_pages = sqlite3_column_int64(pStmt, 5); |
| 19810 | + int_pages = sqlite3_column_int64(pStmt, 6); |
| 19811 | + ovfl_pages = sqlite3_column_int64(pStmt, 7); |
| 19812 | + leaf_unused = sqlite3_column_int64(pStmt, 8); |
| 19813 | + int_unused = sqlite3_column_int64(pStmt, 9); |
| 19814 | + ovfl_unused = sqlite3_column_int64(pStmt, 10); |
| 19815 | + depth = sqlite3_column_int64(pStmt, 11); |
| 19816 | + cnt = sqlite3_column_int64(pStmt, 12); |
| 19817 | + int_cell = sqlite3_column_int64(pStmt, 13); |
| 19818 | + rc = SQLITE_DONE; |
| 19819 | + |
| 19820 | + total_pages = leaf_pages + int_pages + ovfl_pages; |
| 19821 | + analysisLine(p, "Percentage of total database", "%.3g%%\n", |
| 19822 | + (total_pages*100.0)/(double)nPage); |
| 19823 | + analysisLine(p, "Number of entries", "%lld\n", nentry); |
| 19824 | + storage = total_pages*pgsz; |
| 19825 | + analysisLine(p, "Bytes of storage consumed", "%lld\n", storage); |
| 19826 | + analysisLine(p, "Bytes of payload", "%-11lld ", payload); |
| 19827 | + analysisPercent(p, payload*100.0/(double)storage); |
| 19828 | + if( ovfl_cnt>0 ){ |
| 19829 | + analysisLine(p, "Bytes of payload in overflow","%-11lld ",ovfl_payload); |
| 19830 | + analysisPercent(p, ovfl_payload*100.0/(double)payload); |
| 19831 | + } |
| 19832 | + total_unused = leaf_unused + int_unused + ovfl_unused; |
| 19833 | + total_meta = storage - payload - total_unused; |
| 19834 | + analysisLine(p, "Bytes of metadata","%-11lld ", total_meta); |
| 19835 | + analysisPercent(p, total_meta*100.0/(double)storage); |
| 19836 | + if( cnt==1 ){ |
| 19837 | + analysisLine(p, "B-tree depth", "%lld\n", depth); |
| 19838 | + if( int_cell>1 ){ |
| 19839 | + analysisLine(p, "Average fanout", "%.1f\n", |
| 19840 | + (double)(int_cell+int_pages)/(double)int_pages); |
| 19841 | + } |
| 19842 | + } |
| 19843 | + if( nentry>0 ){ |
| 19844 | + analysisLine(p, "Average payload per entry", "%.1f\n", |
| 19845 | + (double)payload/(double)nentry); |
| 19846 | + analysisLine(p, "Average unused bytes per entry", "%.1f\n", |
| 19847 | + (double)total_unused/(double)nentry); |
| 19848 | + analysisLine(p, "Average metadata per entry", "%.1f\n", |
| 19849 | + (double)total_meta/(double)nentry); |
| 19850 | + } |
| 19851 | + analysisLine(p, "Maximum single-entry payload", "%lld\n", mx_payload); |
| 19852 | + if( nentry>0 ){ |
| 19853 | + analysisLine(p, "Entries that use overflow", "%-11lld ", ovfl_cnt); |
| 19854 | + analysisPercent(p, ovfl_cnt*100.0/(double)nentry); |
| 19855 | + } |
| 19856 | + if( int_pages>0 ){ |
| 19857 | + analysisLine(p, "Index pages used", "%lld\n", int_pages); |
| 19858 | + } |
| 19859 | + analysisLine(p, "Primary pages used", "%lld\n", leaf_pages); |
| 19860 | + if( ovfl_cnt ){ |
| 19861 | + analysisLine(p, "Overflow pages used", "%lld\n", ovfl_pages); |
| 19862 | + } |
| 19863 | + analysisLine(p, "Total pages used", "%lld\n", total_pages); |
| 19864 | + if( int_pages>0 ){ |
| 19865 | + analysisLine(p, "Unused bytes on index pages", "%lld\n", int_unused); |
| 19866 | + } |
| 19867 | + analysisLine(p, "Unused bytes on primary pages", "%lld\n", leaf_unused); |
| 19868 | + if( ovfl_cnt ){ |
| 19869 | + analysisLine(p, "Unused bytes on overflow pages", "%lld\n", ovfl_unused); |
| 19870 | + } |
| 19871 | + analysisLine(p, "Unused bytes on all pages", "%-11lld ", total_unused); |
| 19872 | + analysisPercent(p, total_unused*100.0/(double)storage); |
| 19873 | + } |
| 19874 | + return analysisStmtFinish(p, rc, pStmt); |
| 19875 | +} |
| 19876 | + |
| 19877 | +/* |
| 19878 | +** SQL Function: analyze(SCHEMA) |
| 19879 | +** |
| 19880 | +** Analyze the database schema named in the argument. Return text |
| 19881 | +** containing the analysis. |
| 19882 | +*/ |
| 19883 | +static void analyzeFunc( |
| 19884 | + sqlite3_context *context, |
| 19885 | + int argc, |
| 19886 | + sqlite3_value **argv |
| 19887 | +){ |
| 19888 | + int rc; |
| 19889 | + sqlite3_stmt *pStmt; |
| 19890 | + int n; |
| 19891 | + sqlite3_int64 i64; |
| 19892 | + sqlite3_int64 pgsz; |
| 19893 | + sqlite3_int64 nPage; |
| 19894 | + sqlite3_int64 nPageInUse; |
| 19895 | + sqlite3_int64 nFreeList; |
| 19896 | + sqlite3_int64 nIndex; |
| 19897 | + sqlite3_int64 nWORowid; |
| 19898 | + Analysis s; |
| 19899 | + sqlite3_uint64 r[2]; |
| 19900 | + |
| 19901 | + memset(&s, 0, sizeof(s)); |
| 19902 | + s.db = sqlite3_context_db_handle(context); |
| 19903 | + s.context = context; |
| 19904 | + s.pOut = sqlite3_str_new(0); |
| 19905 | + if( sqlite3_str_errcode(s.pOut) ){ |
| 19906 | + analysisError(&s, 0); |
| 19907 | + return; |
| 19908 | + } |
| 19909 | + s.zSchema = (const char*)sqlite3_value_text(argv[0]); |
| 19910 | + if( s.zSchema==0 ){ |
| 19911 | + s.zSchema = "main"; |
| 19912 | + }else if( sqlite3_strlike("temp",s.zSchema,0)==0 ){ |
| 19913 | + analysisReset(&s); |
| 19914 | + sqlite3_result_text(context, "cannot analyze \"temp\"",-1,SQLITE_STATIC); |
| 19915 | + return; |
| 19916 | + } |
| 19917 | + i64 = 0; |
| 19918 | + rc = analysisSqlInt(&s,&i64,"SELECT 1 FROM pragma_database_list" |
| 19919 | + " WHERE name=%Q COLLATE nocase",s.zSchema); |
| 19920 | + if( rc || i64==0 ){ |
| 19921 | + analysisReset(&s); |
| 19922 | + sqlite3_result_text(context,"no such database",-1,SQLITE_STATIC); |
| 19923 | + return; |
| 19924 | + } |
| 19925 | + sqlite3_randomness(sizeof(r), &r); |
| 19926 | + s.zSU = sqlite3_mprintf("analysis%016llx%016llx", r[0], r[1]); |
| 19927 | + if( s.zSU==0 ){ analysisError(&s, 0); return; } |
| 19928 | + |
| 19929 | + /* The s.zSU table contains the data used for the analysis. |
| 19930 | + ** The table name contains 128-bits of randomness to avoid |
| 19931 | + ** collisions with preexisting tables in temp. |
| 19932 | + */ |
| 19933 | + rc = analysisSql(&s, |
| 19934 | + "CREATE TABLE temp.%s(\n" |
| 19935 | + " name text, -- A table or index\n" |
| 19936 | + " tblname text, -- Table that owns name\n" |
| 19937 | + " is_index boolean, -- TRUE if it is an index\n" |
| 19938 | + " is_without_rowid boolean, -- TRUE if WITHOUT ROWID table\n" |
| 19939 | + " nentry int, -- Number of entries in the BTree\n" |
| 19940 | + " leaf_entries int, -- Number of leaf entries\n" |
| 19941 | + " depth int, -- Depth of the b-tree\n" |
| 19942 | + " payload int, -- Total data stored in this table/index\n" |
| 19943 | + " ovfl_payload int, -- Total data stored on overflow pages\n" |
| 19944 | + " ovfl_cnt int, -- Number of entries that use overflow\n" |
| 19945 | + " mx_payload int, -- Maximum payload size\n" |
| 19946 | + " int_pages int, -- Interior pages used\n" |
| 19947 | + " leaf_pages int, -- Leaf pages used\n" |
| 19948 | + " ovfl_pages int, -- Overflow pages used\n" |
| 19949 | + " int_unused int, -- Unused bytes on interior pages\n" |
| 19950 | + " leaf_unused int, -- Unused bytes on primary pages\n" |
| 19951 | + " ovfl_unused int, -- Unused bytes on overflow pages\n" |
| 19952 | + " int_entries int -- Btree cells on internal pages\n" |
| 19953 | + ");", |
| 19954 | + s.zSU |
| 19955 | + ); |
| 19956 | + if( rc ) return; |
| 19957 | + |
| 19958 | + /* Populate the s.zSU table |
| 19959 | + */ |
| 19960 | + rc = analysisSql(&s, |
| 19961 | + "WITH\n" |
| 19962 | + " allidx(idxname) AS (\n" |
| 19963 | + " SELECT name FROM \"%w\".sqlite_schema WHERE type='index'\n" |
| 19964 | + " ),\n" |
| 19965 | + " allobj(allname,tblname,isidx,isworowid) AS (\n" |
| 19966 | + " SELECT 'sqlite_schema',\n" |
| 19967 | + " 'sqlite_schema',\n" |
| 19968 | + " 0,\n" |
| 19969 | + " 0\n" |
| 19970 | + " UNION ALL\n" |
| 19971 | + " SELECT name,\n" |
| 19972 | + " tbl_name,\n" |
| 19973 | + " type='index',\n" |
| 19974 | + " EXISTS(SELECT 1\n" |
| 19975 | + " FROM pragma_index_list(sqlite_schema.name,%Q)\n" |
| 19976 | + " WHERE pragma_index_list.origin='pk'\n" |
| 19977 | + " AND pragma_index_list.name NOT IN allidx)\n" |
| 19978 | + " FROM \"%w\".sqlite_schema\n" |
| 19979 | + " )\n" |
| 19980 | + "INSERT INTO temp.%s\n" |
| 19981 | + " SELECT\n" |
| 19982 | + " allname,\n" |
| 19983 | + " tblname,\n" |
| 19984 | + " isidx,\n" |
| 19985 | + " isworowid,\n" |
| 19986 | + " sum(ncell),\n" |
| 19987 | + " sum((pagetype='leaf')*ncell),\n" |
| 19988 | + " max((length(if(path GLOB '*+*','',path))+3)/4),\n" |
| 19989 | + " sum(payload),\n" |
| 19990 | + " sum((pagetype='overflow')*payload),\n" |
| 19991 | + " sum(path GLOB '*+000000'),\n" |
| 19992 | + " max(mx_payload),\n" |
| 19993 | + " sum(pagetype='internal'),\n" |
| 19994 | + " sum(pagetype='leaf'),\n" |
| 19995 | + " sum(pagetype='overflow'),\n" |
| 19996 | + " sum((pagetype='internal')*unused),\n" |
| 19997 | + " sum((pagetype='leaf')*unused),\n" |
| 19998 | + " sum((pagetype='overflow')*unused),\n" |
| 19999 | + " sum(if(pagetype='internal',ncell))\n" |
| 20000 | + " FROM allobj CROSS JOIN dbstat(%Q) \n" |
| 20001 | + " WHERE dbstat.name=allobj.allname\n" |
| 20002 | + " GROUP BY allname;\n", |
| 20003 | + s.zSchema, /* %w.sqlite_schema -- in allidx */ |
| 20004 | + s.zSchema, /* pragma_index_list(...,%Q) */ |
| 20005 | + s.zSchema, /* %w.sqlite_schema */ |
| 20006 | + s.zSU, /* INTO temp.%s */ |
| 20007 | + s.zSchema /* JOIN dbstat(%Q) */ |
| 20008 | + ); |
| 20009 | + if( rc ) return; |
| 20010 | + |
| 20011 | + nPage = 0; |
| 20012 | + rc = analysisSqlInt(&s, &nPage, "PRAGMA \"%w\".page_count", s.zSchema); |
| 20013 | + if( rc ) return; |
| 20014 | + if( nPage<=0 ){ |
| 20015 | + /* Very brief reply for an empty database */ |
| 20016 | + analysisReset(&s); |
| 20017 | + sqlite3_result_text(context, "empty database", -1, SQLITE_STATIC); |
| 20018 | + return; |
| 20019 | + } |
| 20020 | + |
| 20021 | + /* Begin generating the report */ |
| 20022 | + analysisTitle(&s, "Database storage utilization report"); |
| 20023 | + pgsz = 0; |
| 20024 | + rc = analysisSqlInt(&s, &pgsz, "PRAGMA \"%w\".page_size", s.zSchema); |
| 20025 | + if( rc ) return; |
| 20026 | + analysisLine(&s, "Page size in bytes","%lld\n",pgsz); |
| 20027 | + analysisLine(&s, "Pages in the database", "%lld\n", nPage); |
| 20028 | + |
| 20029 | + nPageInUse = 0; |
| 20030 | + rc = analysisSqlInt(&s, &nPageInUse, |
| 20031 | + "SELECT sum(leaf_pages+int_pages+ovfl_pages) FROM temp.%s", s.zSU); |
| 20032 | + if( rc ) return; |
| 20033 | + analysisLine(&s, "Pages that store data", "%-11lld ", nPageInUse); |
| 20034 | + analysisPercent(&s, (nPageInUse*100.0)/(double)nPage); |
| 20035 | + |
| 20036 | + nFreeList = 0; |
| 20037 | + rc = analysisSqlInt(&s, &nFreeList, "PRAGMA \"%w\".freelist_count",s.zSchema); |
| 20038 | + if( rc ) return; |
| 20039 | + analysisLine(&s, "Pages on the freelist", "%-11lld ", nFreeList); |
| 20040 | + analysisPercent(&s, (nFreeList*100.0)/(double)nPage); |
| 20041 | + |
| 20042 | + i64 = 0; |
| 20043 | + rc = analysisSqlInt(&s, &i64, "PRAGMA \"%w\".auto_vacuum", s.zSchema); |
| 20044 | + if( rc ) return; |
| 20045 | + if( i64==0 || nPage<=1 ){ |
| 20046 | + i64 = 0; |
| 20047 | + }else{ |
| 20048 | + double rPtrsPerPage = pgsz/5; |
| 20049 | + double rAvPage = (nPage-1.0)/(rPtrsPerPage+1.0); |
| 20050 | + i64 = (sqlite3_int64)ceil(rAvPage); |
| 20051 | + } |
| 20052 | + analysisLine(&s, "Pages of auto-vacuum overhead", "%-11lld ", i64); |
| 20053 | + analysisPercent(&s, (i64*100.0)/(double)nPage); |
| 20054 | + |
| 20055 | + i64 = 0; |
| 20056 | + rc = analysisSqlInt(&s, &i64, |
| 20057 | + "SELECT count(*)+1 FROM \"%w\".sqlite_schema WHERE type='table'", |
| 20058 | + s.zSchema); |
| 20059 | + if( rc ) return; |
| 20060 | + analysisLine(&s, "Number of tables", "%lld\n", i64); |
| 20061 | + nWORowid = 0; |
| 20062 | + rc = analysisSqlInt(&s, &nWORowid, |
| 20063 | + "SELECT count(*) FROM \"%w\".pragma_table_list WHERE wr", |
| 20064 | + s.zSchema); |
| 20065 | + if( rc ) return; |
| 20066 | + if( nWORowid>0 ){ |
| 20067 | + analysisLine(&s, "Number of WITHOUT ROWID tables", "%lld\n", nWORowid); |
| 20068 | + analysisLine(&s, "Number of rowid tables", "%lld\n", i64 - nWORowid); |
| 20069 | + } |
| 20070 | + nIndex = 0; |
| 20071 | + rc = analysisSqlInt(&s, &nIndex, |
| 20072 | + "SELECT count(*) FROM \"%w\".sqlite_schema WHERE type='index'", |
| 20073 | + s.zSchema); |
| 20074 | + if( rc ) return; |
| 20075 | + analysisLine(&s, "Number of indexes", "%lld\n", nIndex); |
| 20076 | + i64 = 0; |
| 20077 | + rc = analysisSqlInt(&s, &i64, |
| 20078 | + "SELECT count(*) FROM \"%w\".sqlite_schema" |
| 20079 | + " WHERE name GLOB 'sqlite_autoindex_*' AND type='index'", |
| 20080 | + s.zSchema); |
| 20081 | + if( rc ) return; |
| 20082 | + analysisLine(&s, "Number of defined indexes", "%lld\n", nIndex - i64); |
| 20083 | + analysisLine(&s, "Number of implied indexes", "%lld\n", i64); |
| 20084 | + analysisLine(&s, "Size of the database in bytes", "%lld\n", pgsz*nPage); |
| 20085 | + i64 = 0; |
| 20086 | + rc = analysisSqlInt(&s, &i64, |
| 20087 | + "SELECT sum(payload) FROM temp.%s" |
| 20088 | + " WHERE NOT is_index AND name NOT LIKE 'sqlite_schema'", |
| 20089 | + s.zSU); |
| 20090 | + if( rc ) return; |
| 20091 | + analysisLine(&s, "Bytes of payload", "%-11lld ", i64); |
| 20092 | + analysisPercent(&s, i64*100.0/(double)(pgsz*nPage)); |
| 20093 | + |
| 20094 | + analysisTitle(&s, "Page counts for all tables with their indexes"); |
| 20095 | + pStmt = analysisPrepare(&s, |
| 20096 | + "SELECT upper(tblname),\n" |
| 20097 | + " sum(int_pages+leaf_pages+ovfl_pages)\n" |
| 20098 | + " FROM temp.%s\n" |
| 20099 | + " GROUP BY 1\n" |
| 20100 | + " ORDER BY 2 DESC, 1;", |
| 20101 | + s.zSU); |
| 20102 | + if( pStmt==0 ) return; |
| 20103 | + while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){ |
| 20104 | + sqlite3_int64 nn = sqlite3_column_int64(pStmt,1); |
| 20105 | + analysisLine(&s, (const char*)sqlite3_column_text(pStmt,0), "%-11lld ", nn); |
| 20106 | + analysisPercent(&s, (nn*100.0)/(double)nPage); |
| 20107 | + } |
| 20108 | + if( analysisStmtFinish(&s, rc, pStmt) ) return; |
| 20109 | + |
| 20110 | + analysisTitle(&s, "Page counts for all tables and indexes separately"); |
| 20111 | + pStmt = analysisPrepare(&s, |
| 20112 | + "SELECT upper(name),\n" |
| 20113 | + " sum(int_pages+leaf_pages+ovfl_pages)\n" |
| 20114 | + " FROM temp.%s\n" |
| 20115 | + " GROUP BY 1\n" |
| 20116 | + " ORDER BY 2 DESC, 1;", |
| 20117 | + s.zSU); |
| 20118 | + if( pStmt==0 ) return; |
| 20119 | + while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){ |
| 20120 | + sqlite3_int64 nn = sqlite3_column_int64(pStmt,1); |
| 20121 | + analysisLine(&s, (const char*)sqlite3_column_text(pStmt,0), "%-11lld ", nn); |
| 20122 | + analysisPercent(&s, (nn*100.0)/(double)nPage); |
| 20123 | + } |
| 20124 | + if( analysisStmtFinish(&s, rc, pStmt) ) return; |
| 20125 | + |
| 20126 | + rc = analysisSubreport(&s, "All tables and indexes", "1", pgsz, nPage); |
| 20127 | + if( rc ) return; |
| 20128 | + rc = analysisSubreport(&s, "All tables", "NOT is_index", pgsz, nPage); |
| 20129 | + if( rc ) return; |
| 20130 | + if( nWORowid>0 ){ |
| 20131 | + rc = analysisSubreport(&s, "All WITHOUT ROWID tables", "is_without_rowid", |
| 20132 | + pgsz, nPage); |
| 20133 | + if( rc ) return; |
| 20134 | + rc = analysisSubreport(&s, "All rowid tables", |
| 20135 | + "NOT is_without_rowid AND NOT is_index", |
| 20136 | + pgsz, nPage); |
| 20137 | + if( rc ) return; |
| 20138 | + } |
| 20139 | + rc = analysisSubreport(&s, "All indexes", "is_index", pgsz, nPage); |
| 20140 | + if( rc ) return; |
| 20141 | + |
| 20142 | + pStmt = analysisPrepare(&s, |
| 20143 | + "SELECT upper(tblname), tblname, sum(is_index) FROM temp.%s" |
| 20144 | + " GROUP BY 1 ORDER BY 1", |
| 20145 | + s.zSU); |
| 20146 | + if( pStmt==0 ) return; |
| 20147 | + while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){ |
| 20148 | + const char *zUpper = (const char*)sqlite3_column_text(pStmt, 0); |
| 20149 | + const char *zName = (const char*)sqlite3_column_text(pStmt, 1); |
| 20150 | + int nSubIndex = sqlite3_column_int(pStmt, 2); |
| 20151 | + if( nSubIndex==0 ){ |
| 20152 | + char *zTitle = sqlite3_mprintf("Table %s", zUpper); |
| 20153 | + char *zWhere = sqlite3_mprintf("name=%Q", zName); |
| 20154 | + rc = analysisSubreport(&s, zTitle, zWhere, pgsz, nPage); |
| 20155 | + sqlite3_free(zTitle); |
| 20156 | + sqlite3_free(zWhere); |
| 20157 | + if( rc ) break; |
| 20158 | + }else{ |
| 20159 | + sqlite3_stmt *pS2; |
| 20160 | + char *zTitle = sqlite3_mprintf("Table %s and all its indexes", zUpper); |
| 20161 | + char *zWhere = sqlite3_mprintf("tblname=%Q", zName); |
| 20162 | + rc = analysisSubreport(&s, zTitle, zWhere, pgsz, nPage); |
| 20163 | + sqlite3_free(zTitle); |
| 20164 | + sqlite3_free(zWhere); |
| 20165 | + if( rc ) break; |
| 20166 | + zTitle = sqlite3_mprintf("Table %s w/o any indexes", zUpper); |
| 20167 | + zWhere = sqlite3_mprintf("name=%Q", zName); |
| 20168 | + rc = analysisSubreport(&s, zTitle, zWhere, pgsz, nPage); |
| 20169 | + sqlite3_free(zTitle); |
| 20170 | + sqlite3_free(zWhere); |
| 20171 | + if( rc ) break; |
| 20172 | + if( nSubIndex>1 ){ |
| 20173 | + zTitle = sqlite3_mprintf("All indexes of table %s", zUpper); |
| 20174 | + zWhere = sqlite3_mprintf("tblname=%Q AND is_index", zName); |
| 20175 | + rc = analysisSubreport(&s, zTitle, zWhere, pgsz, nPage); |
| 20176 | + sqlite3_free(zTitle); |
| 20177 | + sqlite3_free(zWhere); |
| 20178 | + if( rc ) break; |
| 20179 | + } |
| 20180 | + pS2 = analysisPrepare(&s, |
| 20181 | + "SELECT name, upper(name) FROM temp.%s" |
| 20182 | + " WHERE is_index AND tblname=%Q", |
| 20183 | + s.zSU, zName); |
| 20184 | + if( pS2==0 ){ |
| 20185 | + rc = SQLITE_NOMEM; |
| 20186 | + break; |
| 20187 | + } |
| 20188 | + while( (rc = sqlite3_step(pS2))==SQLITE_ROW ){ |
| 20189 | + const char *zU = (const char*)sqlite3_column_text(pS2, 1); |
| 20190 | + const char *zN = (const char*)sqlite3_column_text(pS2, 0); |
| 20191 | + zTitle = sqlite3_mprintf("Index %s", zU); |
| 20192 | + zWhere = sqlite3_mprintf("name=%Q", zN); |
| 20193 | + rc = analysisSubreport(&s, zTitle, zWhere, pgsz, nPage); |
| 20194 | + sqlite3_free(zTitle); |
| 20195 | + sqlite3_free(zWhere); |
| 20196 | + if( rc ) break; |
| 20197 | + } |
| 20198 | + rc = analysisStmtFinish(&s, rc, pS2); |
| 20199 | + if( rc ) break; |
| 20200 | + } |
| 20201 | + } |
| 20202 | + if( analysisStmtFinish(&s, rc, pStmt) ) return; |
| 20203 | + |
| 20204 | + /* Append SQL statements that will recreate the raw data used for |
| 20205 | + ** the analysis. |
| 20206 | + */ |
| 20207 | + analysisTitle(&s, "Raw data used to generate this report"); |
| 20208 | + sqlite3_str_appendf(s.pOut, |
| 20209 | + "The following SQL will create a table named \"space_used\" which\n" |
| 20210 | + "contains most of the information used to generate the report above.\n" |
| 20211 | + "*/\n" |
| 20212 | + ); |
| 20213 | + sqlite3_str_appendf(s.pOut, |
| 20214 | + "BEGIN;\n" |
| 20215 | + "CREATE TABLE space_used(\n" |
| 20216 | + " name text, -- A table or index\n" /* 0 */ |
| 20217 | + " tblname text, -- Table that owns name\n" /* 1 */ |
| 20218 | + " is_index boolean, -- TRUE if it is an index\n" /* 2 */ |
| 20219 | + " is_without_rowid boolean, -- TRUE if WITHOUT ROWID table\n" /* 3 */ |
| 20220 | + " nentry int, -- Number of entries in the BTree\n" /* 4 */ |
| 20221 | + " leaf_entries int, -- Number of leaf entries\n" /* 5 */ |
| 20222 | + " depth int, -- Depth of the b-tree\n" /* 6 */ |
| 20223 | + " payload int, -- Total data in this table/index\n" /* 7 */ |
| 20224 | + " ovfl_payload int, -- Total data on overflow pages\n" /* 8 */ |
| 20225 | + " ovfl_cnt int, -- Entries that use overflow\n" /* 9 */ |
| 20226 | + " mx_payload int, -- Maximum payload size\n" /* 10 */ |
| 20227 | + " int_pages int, -- Interior pages used\n" /* 11 */ |
| 20228 | + " leaf_pages int, -- Leaf pages used\n" /* 12 */ |
| 20229 | + " ovfl_pages int, -- Overflow pages used\n" /* 13 */ |
| 20230 | + " int_unused int, -- Unused bytes on interior pages\n" /* 14 */ |
| 20231 | + " leaf_unused int, -- Unused bytes on primary pages\n" /* 15 */ |
| 20232 | + " ovfl_unused int, -- Unused bytes on overflow pages\n" /* 16 */ |
| 20233 | + " int_entries int -- B-tree entries on internal pages\n"/* 17 */ |
| 20234 | + ");\n" |
| 20235 | + "INSERT INTO space_used VALUES\n" |
| 20236 | + ); |
| 20237 | + pStmt = analysisPrepare(&s, |
| 20238 | + "SELECT quote(name), quote(tblname),\n" /* 0..1 */ |
| 20239 | + " is_index, is_without_rowid, nentry, leaf_entries,\n" /* 2..5 */ |
| 20240 | + " depth, payload, ovfl_payload, ovfl_cnt, mx_payload,\n" /* 6..10 */ |
| 20241 | + " int_pages, leaf_pages, ovfl_pages, int_unused,\n" /* 11..14 */ |
| 20242 | + " leaf_unused, ovfl_unused, int_entries\n" /* 15..17 */ |
| 20243 | + " FROM temp.%s;", |
| 20244 | + s.zSU); |
| 20245 | + if( pStmt==0 ) return; |
| 20246 | + n = 0; |
| 20247 | + while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){ |
| 20248 | + if( n++ ) sqlite3_str_appendf(s.pOut,",\n"); |
| 20249 | + sqlite3_str_appendf(s.pOut, |
| 20250 | + " (%s,%s,%lld,%lld,%lld,%lld,%lld,%lld,%lld," |
| 20251 | + "%lld,%lld,%lld,%lld,%lld,%lld,%lld,%lld,%lld)", |
| 20252 | + sqlite3_column_text(pStmt, 0), |
| 20253 | + sqlite3_column_text(pStmt, 1), |
| 20254 | + sqlite3_column_int64(pStmt, 2), |
| 20255 | + sqlite3_column_int64(pStmt, 3), |
| 20256 | + sqlite3_column_int64(pStmt, 4), |
| 20257 | + sqlite3_column_int64(pStmt, 5), |
| 20258 | + sqlite3_column_int64(pStmt, 6), |
| 20259 | + sqlite3_column_int64(pStmt, 7), |
| 20260 | + sqlite3_column_int64(pStmt, 8), |
| 20261 | + sqlite3_column_int64(pStmt, 9), |
| 20262 | + sqlite3_column_int64(pStmt, 10), |
| 20263 | + sqlite3_column_int64(pStmt, 11), |
| 20264 | + sqlite3_column_int64(pStmt, 12), |
| 20265 | + sqlite3_column_int64(pStmt, 13), |
| 20266 | + sqlite3_column_int64(pStmt, 14), |
| 20267 | + sqlite3_column_int64(pStmt, 15), |
| 20268 | + sqlite3_column_int64(pStmt, 16), |
| 20269 | + sqlite3_column_int64(pStmt, 17)); |
| 20270 | + } |
| 20271 | + if( rc!=SQLITE_DONE ){ |
| 20272 | + analysisError(&s, "SQL run-time error: %s\nSQL: %s", |
| 20273 | + sqlite3_errmsg(s.db), sqlite3_sql(pStmt)); |
| 20274 | + sqlite3_finalize(pStmt); |
| 20275 | + return; |
| 20276 | + } |
| 20277 | + sqlite3_str_appendf(s.pOut,";\nCOMMIT;"); |
| 20278 | + sqlite3_finalize(pStmt); |
| 20279 | + |
| 20280 | + if( sqlite3_str_length(s.pOut) ){ |
| 20281 | + sqlite3_result_text(context, sqlite3_str_finish(s.pOut), -1, |
| 20282 | + sqlite3_free); |
| 20283 | + s.pOut = 0; |
| 20284 | + } |
| 20285 | + analysisReset(&s); |
| 20286 | +} |
| 20287 | + |
| 20288 | + |
| 20289 | +#ifdef _WIN32 |
| 20290 | + |
| 20291 | +#endif |
| 20292 | +int sqlite3_analyze_init( |
| 20293 | + sqlite3 *db, |
| 20294 | + char **pzErrMsg, |
| 20295 | + const sqlite3_api_routines *pApi |
| 20296 | +){ |
| 20297 | + int rc = SQLITE_OK; |
| 20298 | + SQLITE_EXTENSION_INIT2(pApi); |
| 20299 | + (void)pzErrMsg; /* Unused parameter */ |
| 20300 | + rc = sqlite3_create_function(db, "analyze", 1, |
| 20301 | + SQLITE_UTF8|SQLITE_INNOCUOUS, |
| 20302 | + 0, analyzeFunc, 0, 0); |
| 20303 | + return rc; |
| 20304 | +} |
| 20305 | + |
| 20306 | +/************************* End ext/misc/analyze.c ********************/ |
| 19403 | 20307 | |
| 19404 | 20308 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) |
| 19405 | 20309 | #define SQLITE_SHELL_HAVE_RECOVER 1 |
| 19406 | 20310 | #else |
| 19407 | 20311 | #define SQLITE_SHELL_HAVE_RECOVER 0 |
| | @@ -23966,10 +24870,11 @@ |
| 23966 | 24870 | ** Output routines that are able to redirect to memory rather than |
| 23967 | 24871 | ** doing actually I/O. |
| 23968 | 24872 | ** Works like. |
| 23969 | 24873 | ** -------------- |
| 23970 | 24874 | ** cli_printf(FILE*, const char*, ...); fprintf() |
| 24875 | +** cli_write(FILE*, const char*, int); write() |
| 23971 | 24876 | ** cli_puts(const char*, FILE*); fputs() |
| 23972 | 24877 | ** cli_vprintf(FILE*, const char*, va_list); vfprintf() |
| 23973 | 24878 | ** |
| 23974 | 24879 | ** These are just thin wrappers with the following added semantics: |
| 23975 | 24880 | ** If the file-scope variable cli_output_capture is not NULL, and |
| | @@ -23991,10 +24896,18 @@ |
| 23991 | 24896 | }else{ |
| 23992 | 24897 | rc = sqlite3_vfprintf(out, zFormat, ap); |
| 23993 | 24898 | } |
| 23994 | 24899 | va_end(ap); |
| 23995 | 24900 | return rc; |
| 24901 | +} |
| 24902 | +static int cli_write(FILE *out, const char *zData, int nData){ |
| 24903 | + if( cli_output_capture && (out==stdout || out==stderr) ){ |
| 24904 | + sqlite3_str_append(cli_output_capture, zData, nData); |
| 24905 | + }else{ |
| 24906 | + nData = (int)fwrite(zData, 1, nData, out); |
| 24907 | + } |
| 24908 | + return nData; |
| 23996 | 24909 | } |
| 23997 | 24910 | static int cli_puts(const char *zText, FILE *out){ |
| 23998 | 24911 | if( cli_output_capture && (out==stdout || out==stderr) ){ |
| 23999 | 24912 | sqlite3_str_appendall(cli_output_capture, zText); |
| 24000 | 24913 | return 1; |
| | @@ -24189,15 +25102,47 @@ |
| 24189 | 25102 | |
| 24190 | 25103 | /* |
| 24191 | 25104 | ** The default prompts. |
| 24192 | 25105 | */ |
| 24193 | 25106 | #ifndef SQLITE_PS1 |
| 24194 | | -# define SQLITE_PS1 "SQLite /f> " |
| 25107 | +# define SQLITE_PS1 "/A /f> " |
| 24195 | 25108 | #endif |
| 24196 | 25109 | #ifndef SQLITE_PS2 |
| 24197 | 25110 | # define SQLITE_PS2 "/B.../H> " |
| 24198 | 25111 | #endif |
| 25112 | + |
| 25113 | +/* |
| 25114 | +** Redefinable name of a function that is used to find the text for |
| 25115 | +** some prompt expansions: /A /V /v |
| 25116 | +*/ |
| 25117 | +#ifndef SQLITE_PS_APPDEF |
| 25118 | +# define SQLITE_PS_APPDEF shellPromptAppDef |
| 25119 | +#else |
| 25120 | +extern const char *SQLITE_PS_APPDEF(int); |
| 25121 | +#endif |
| 25122 | + |
| 25123 | +/* |
| 25124 | +** Return a string appropriate for various prompt expansion characters. |
| 25125 | +** Return an empty string at least. Always return a valid string pointer. |
| 25126 | +*/ |
| 25127 | +static const char *shellPromptAppDef(int c){ |
| 25128 | + if( c=='A' ) return "SQLite"; |
| 25129 | + if( c=='V' ) return sqlite3_libversion(); |
| 25130 | + if( c=='v' ){ |
| 25131 | + static char zRel[16]; |
| 25132 | + const char *zF = sqlite3_libversion(); |
| 25133 | + const char *zD = strrchr(zF,'.'); |
| 25134 | + if( zD && (size_t)(zD-zF)<sizeof(zRel)-1 ){ |
| 25135 | + memcpy(zRel,zF,(size_t)(zD-zF)); |
| 25136 | + zRel[(size_t)(zD-zF)] = 0; |
| 25137 | + return zRel; |
| 25138 | + }else{ |
| 25139 | + return zF; |
| 25140 | + } |
| 25141 | + } |
| 25142 | + return ""; |
| 25143 | +} |
| 24199 | 25144 | |
| 24200 | 25145 | /* |
| 24201 | 25146 | ** Return the raw (unexpanded) prompt string. This will be the |
| 24202 | 25147 | ** first of the following that exist: |
| 24203 | 25148 | ** |
| | @@ -24236,11 +25181,14 @@ |
| 24236 | 25181 | zFN = p->pAuxDb->zDbFilename; |
| 24237 | 25182 | }else if( p->db && (pFN = sqlite3_db_filename(p->db,0))!=0 ){ |
| 24238 | 25183 | zFN = sqlite3_filename_database(pFN); |
| 24239 | 25184 | } |
| 24240 | 25185 | if( zFN==0 || zFN[0]==0 ){ |
| 24241 | | - zFN = "in-memory"; |
| 25186 | + zFN = p->pAuxDb->zDbFilename; |
| 25187 | + if( zFN==0 || zFN[0]==0 || cli_strcmp(zFN,":memory:")==0 ){ |
| 25188 | + zFN = "in-memory"; |
| 25189 | + } |
| 24242 | 25190 | } |
| 24243 | 25191 | return zFN; |
| 24244 | 25192 | } |
| 24245 | 25193 | |
| 24246 | 25194 | /* |
| | @@ -24262,16 +25210,17 @@ |
| 24262 | 25210 | const char *zPrompt /* Prompt string to be expanded */ |
| 24263 | 25211 | ){ |
| 24264 | 25212 | sqlite3_str *pOut = sqlite3_str_new(0); |
| 24265 | 25213 | int i; |
| 24266 | 25214 | char c; |
| 24267 | | - int onoff = 1; |
| 25215 | + unsigned int mOff = 0; /* Bitmask of FALSE for if/then/else */ |
| 24268 | 25216 | int idxSpace = -1; |
| 25217 | + int iDate = -1; |
| 24269 | 25218 | for(i=0; zPrompt[i]; i++){ |
| 24270 | 25219 | if( zPrompt[i]!='/' ) continue; |
| 24271 | 25220 | if( i>0 ){ |
| 24272 | | - if( onoff ) sqlite3_str_append(pOut, zPrompt, i); |
| 25221 | + if( !mOff ) sqlite3_str_append(pOut, zPrompt, i); |
| 24273 | 25222 | zPrompt += i; |
| 24274 | 25223 | i = 0; |
| 24275 | 25224 | } |
| 24276 | 25225 | /* At this point zPrompt[0] is a / character and all prior |
| 24277 | 25226 | ** characters have already been loaded into pOut. Process the |
| | @@ -24290,18 +25239,27 @@ |
| 24290 | 25239 | /* /nnn becomes a single byte given by octal nnn */ |
| 24291 | 25240 | int v = c - '0'; |
| 24292 | 25241 | while( i<=2 && zPrompt[i+1]>='0' && zPrompt[i+1]<='7' ){ |
| 24293 | 25242 | v = v*8 + zPrompt[++i] - '0'; |
| 24294 | 25243 | } |
| 24295 | | - if( onoff ) sqlite3_str_appendchar(pOut, 1, v); |
| 25244 | + if( !mOff ) sqlite3_str_appendchar(pOut, 1, v); |
| 24296 | 25245 | zPrompt += i+1; |
| 24297 | 25246 | i = -1; |
| 24298 | 25247 | continue; |
| 24299 | 25248 | } |
| 24300 | 25249 | if( c=='e' ){ |
| 24301 | 25250 | /* /e is shorthand for /033 which is U+001B "Escape" */ |
| 24302 | | - if( onoff ) sqlite3_str_append(pOut, "\033", 1); |
| 25251 | + if( !mOff ) sqlite3_str_append(pOut, "\033", 1); |
| 25252 | + zPrompt += 2; |
| 25253 | + i = -1; |
| 25254 | + continue; |
| 25255 | + } |
| 25256 | + if( c=='A' || c=='V' || c=='v' ){ |
| 25257 | + /* /A expands to the application name */ |
| 25258 | + /* /V expands to the version number with patch level */ |
| 25259 | + /* /v expands to the version number without the patch level */ |
| 25260 | + if( !mOff ) sqlite3_str_appendall(pOut, SQLITE_PS_APPDEF(c)); |
| 24303 | 25261 | zPrompt += 2; |
| 24304 | 25262 | i = -1; |
| 24305 | 25263 | continue; |
| 24306 | 25264 | } |
| 24307 | 25265 | |
| | @@ -24317,36 +25275,43 @@ |
| 24317 | 25275 | ** or red if within a transaction: |
| 24318 | 25276 | ** |
| 24319 | 25277 | ** .prompt '/e[1;/x31/:34/;m~f>/e[0m ' |
| 24320 | 25278 | */ |
| 24321 | 25279 | if( c==':' ){ |
| 24322 | | - /* toggle display on/off */ |
| 24323 | | - onoff = !onoff; |
| 25280 | + /* ELSE: toggle display on/off */ |
| 25281 | + mOff ^= 1; |
| 24324 | 25282 | zPrompt += 2; |
| 24325 | 25283 | i = -1; |
| 24326 | 25284 | continue; |
| 24327 | 25285 | } |
| 24328 | 25286 | if( c==';' ){ |
| 24329 | | - /* Turn display on */ |
| 24330 | | - onoff = 1; |
| 25287 | + /* ENDIF: Turn display on */ |
| 25288 | + mOff >>= 1; |
| 24331 | 25289 | zPrompt += 2; |
| 24332 | 25290 | i = -1; |
| 24333 | 25291 | continue; |
| 24334 | 25292 | } |
| 24335 | 25293 | if( c=='x' ){ |
| 24336 | 25294 | /* /x turns display off not in a transaction, on if in txn */ |
| 24337 | | - onoff = p->db && !sqlite3_get_autocommit(p->db); |
| 25295 | + mOff = (mOff<<1) | (p->db==0 || sqlite3_get_autocommit(p->db)!=0); |
| 25296 | + zPrompt += 2; |
| 25297 | + i = -1; |
| 25298 | + continue; |
| 25299 | + } |
| 25300 | + if( c=='r' ){ |
| 25301 | + /* /r turns display off if database is read/write, on if read-only */ |
| 25302 | + mOff = (mOff<<1) | (p->db==0 || sqlite3_db_readonly(p->db,0)==0); |
| 24338 | 25303 | zPrompt += 2; |
| 24339 | 25304 | i = -1; |
| 24340 | 25305 | continue; |
| 24341 | 25306 | } |
| 24342 | 25307 | |
| 24343 | 25308 | if( c=='f' || c=='F' || c=='~' ){ |
| 24344 | 25309 | /* /f becomes the tail of the database filename */ |
| 24345 | 25310 | /* /F becomes the full pathname */ |
| 24346 | 25311 | /* /~ becomes the full pathname relative to $HOME */ |
| 24347 | | - if( onoff ){ |
| 25312 | + if( !mOff ){ |
| 24348 | 25313 | const char *zFN = prompt_filename(p); |
| 24349 | 25314 | if( c=='f' ){ |
| 24350 | 25315 | #ifdef _WIN32 |
| 24351 | 25316 | const char *zTail = strrchr(zFN,'\\'); |
| 24352 | 25317 | #else |
| | @@ -24371,11 +25336,11 @@ |
| 24371 | 25336 | continue; |
| 24372 | 25337 | } |
| 24373 | 25338 | |
| 24374 | 25339 | if( c=='H' ){ |
| 24375 | 25340 | /* /H becomes text needed to terminate current input */ |
| 24376 | | - if( onoff ){ |
| 25341 | + if( !mOff ){ |
| 24377 | 25342 | sqlite3_int64 R = zPrior ? sqlite3_incomplete(zPrior) : 0; |
| 24378 | 25343 | int cc = (R>>16)&0xff; |
| 24379 | 25344 | int nParen = R>>32; |
| 24380 | 25345 | int eSemi = (R>>8)&0xff; |
| 24381 | 25346 | if( cc==0 ){ |
| | @@ -24405,22 +25370,48 @@ |
| 24405 | 25370 | |
| 24406 | 25371 | if( c=='B' ){ |
| 24407 | 25372 | /* /B is a no-op for the main prompt. For the continuation prompt, |
| 24408 | 25373 | ** /B expands to zero or more spaces to make the continuation prompt |
| 24409 | 25374 | ** at least as wide as the main prompt. */ |
| 24410 | | - if( onoff ) idxSpace = sqlite3_str_length(pOut); |
| 25375 | + if( !mOff ) idxSpace = sqlite3_str_length(pOut); |
| 25376 | + zPrompt += 2; |
| 25377 | + i = -1; |
| 25378 | + continue; |
| 25379 | + } |
| 25380 | + |
| 25381 | + if( c=='D' ){ |
| 25382 | + /* /D.../D replaces all of the text between the two /D escapes with |
| 25383 | + ** the result from strftime(). */ |
| 25384 | + if( iDate<0 ){ |
| 25385 | + iDate = sqlite3_str_length(pOut); |
| 25386 | + }else{ |
| 25387 | + if( !mOff ){ |
| 25388 | + time_t now; |
| 25389 | + char zBuf[200]; |
| 25390 | + zBuf[0] = 0; |
| 25391 | + time(&now); |
| 25392 | + strftime(zBuf, sizeof(zBuf)-1, sqlite3_str_value(pOut)+iDate, |
| 25393 | + localtime(&now)); |
| 25394 | + zBuf[199] = 0; |
| 25395 | + sqlite3_str_truncate(pOut, iDate); |
| 25396 | + sqlite3_str_appendall(pOut, zBuf); |
| 25397 | + } |
| 25398 | + iDate = -1; |
| 25399 | + } |
| 24411 | 25400 | zPrompt += 2; |
| 24412 | 25401 | i = -1; |
| 24413 | 25402 | continue; |
| 24414 | 25403 | } |
| 24415 | 25404 | |
| 24416 | | - /* No match to a known escape. Generate an error. */ |
| 24417 | | - if( onoff ) sqlite3_str_appendf(pOut,"UNKNOWN(\"/%c\")",c); |
| 25405 | + /* No match to a known escape. Generate an error. The mOff flag |
| 25406 | + ** is ignored for this output, so that errors appear even if they |
| 25407 | + ** are in an unused branch. */ |
| 25408 | + sqlite3_str_appendf(pOut,"UNKNOWN(\"/%c\")",c); |
| 24418 | 25409 | zPrompt += 2; |
| 24419 | 25410 | i = -1; |
| 24420 | 25411 | } |
| 24421 | | - if( i>0 && onoff ){ |
| 25412 | + if( i>0 && !mOff ){ |
| 24422 | 25413 | sqlite3_str_append(pOut, zPrompt, i); |
| 24423 | 25414 | } |
| 24424 | 25415 | |
| 24425 | 25416 | /* Expand the /B, if there is one and if this is a continuation prompt */ |
| 24426 | 25417 | if( idxSpace>=0 && zPrior!=0 && zPrior[0]!=0 ){ |
| | @@ -24440,11 +25431,15 @@ |
| 24440 | 25431 | memmove(z+idxSpace+nNew, z+idxSpace, len-nNew-idxSpace); |
| 24441 | 25432 | memset(z+idxSpace, ' ', nNew); |
| 24442 | 25433 | } |
| 24443 | 25434 | } |
| 24444 | 25435 | } |
| 24445 | | - |
| 25436 | + |
| 25437 | + if( 0==sqlite3_str_length(pOut) ){ |
| 25438 | + /* Avoid a bogus OOM */ |
| 25439 | + sqlite3_str_appendchar(pOut, 1, '\0'); |
| 25440 | + } |
| 24446 | 25441 | return sqlite3_str_finish(pOut); |
| 24447 | 25442 | } |
| 24448 | 25443 | |
| 24449 | 25444 | /* |
| 24450 | 25445 | ** Retrieve a single line of input text. |
| | @@ -25331,11 +26326,11 @@ |
| 25331 | 26326 | ** (2) Run program EDITOR on that temporary file. |
| 25332 | 26327 | ** (3) Read the temporary file back and return its content as the result. |
| 25333 | 26328 | ** (4) Delete the temporary file |
| 25334 | 26329 | ** |
| 25335 | 26330 | ** If the EDITOR argument is omitted, use the value in the VISUAL |
| 25336 | | -** environment variable. If still there is no EDITOR, through an error. |
| 26331 | +** environment variable. If still there is no EDITOR, throw an error. |
| 25337 | 26332 | ** |
| 25338 | 26333 | ** Also throw an error if the EDITOR program returns a non-zero exit code. |
| 25339 | 26334 | */ |
| 25340 | 26335 | #ifndef SQLITE_NOHAVE_SYSTEM |
| 25341 | 26336 | static void editFunc( |
| | @@ -26688,11 +27683,11 @@ |
| 26688 | 27683 | /* |
| 26689 | 27684 | ** QRF write callback |
| 26690 | 27685 | */ |
| 26691 | 27686 | static int shellWriteQR(void *pX, const char *z, sqlite3_int64 n){ |
| 26692 | 27687 | ShellState *pArg = (ShellState*)pX; |
| 26693 | | - cli_printf(pArg->out, "%.*s", (int)n, z); |
| 27688 | + cli_write(pArg->out, z, (int)n); |
| 26694 | 27689 | return SQLITE_OK; |
| 26695 | 27690 | } |
| 26696 | 27691 | |
| 26697 | 27692 | /* |
| 26698 | 27693 | ** Execute a statement or set of statements. Print |
| | @@ -27230,10 +28225,11 @@ |
| 27230 | 28225 | ".databases List names and files of attached databases", |
| 27231 | 28226 | ".dbconfig ?op? ?val? List or change sqlite3_db_config() options", |
| 27232 | 28227 | #if SQLITE_SHELL_HAVE_RECOVER |
| 27233 | 28228 | ".dbinfo ?DB? Show status information about the database", |
| 27234 | 28229 | #endif |
| 28230 | + ".dbstat ?SCHEMA? Report database space and size stats", |
| 27235 | 28231 | ".dbtotxt Hex dump of the database file", |
| 27236 | 28232 | ".dump ?OBJECTS? Render database content as SQL", |
| 27237 | 28233 | " Options:", |
| 27238 | 28234 | " --data-only Output only INSERT statements", |
| 27239 | 28235 | " --newlines Allow unescaped newline characters in output", |
| | @@ -27262,11 +28258,11 @@ |
| 27262 | 28258 | ".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto", |
| 27263 | 28259 | ".filectrl CMD ... Run various sqlite3_file_control() operations", |
| 27264 | 28260 | " --schema SCHEMA Use SCHEMA instead of \"main\"", |
| 27265 | 28261 | " --help Show CMD details", |
| 27266 | 28262 | ".fullschema ?--indent? Show schema and the content of sqlite_stat tables", |
| 27267 | | - ",headers on|off Turn display of headers on or off", |
| 28263 | + ".headers on|off Turn display of headers on or off", |
| 27268 | 28264 | ".help ?-all? ?PATTERN? Show help text for PATTERN", |
| 27269 | 28265 | #ifndef SQLITE_SHELL_FIDDLE |
| 27270 | 28266 | ".import FILE TABLE Import data from FILE into TABLE", |
| 27271 | 28267 | #endif |
| 27272 | 28268 | #ifndef SQLITE_OMIT_TEST_CONTROL |
| | @@ -27274,11 +28270,11 @@ |
| 27274 | 28270 | #endif |
| 27275 | 28271 | ".indexes ?PATTERN? Show names of indexes matching PATTERN", |
| 27276 | 28272 | " -a|--all Also show system-generated indexes", |
| 27277 | 28273 | " --expr Show only expression indexes", |
| 27278 | 28274 | " --sys Show only system-generated indexes", |
| 27279 | | - ".intck ?STEPS_PER_UNLOCK? Run an incremental integrity check on the db", |
| 28275 | + ".intck ?STEPS? Run an incremental integrity check on the db", |
| 27280 | 28276 | #ifdef SQLITE_ENABLE_IOTRACE |
| 27281 | 28277 | ",iotrace FILE Enable I/O diagnostic logging to FILE", |
| 27282 | 28278 | #endif |
| 27283 | 28279 | ".limit ?LIMIT? ?VAL? Display or change the value of an SQLITE_LIMIT", |
| 27284 | 28280 | ".lint OPTIONS Report potential schema issues.", |
| | @@ -27339,14 +28335,15 @@ |
| 27339 | 28335 | " --once Do no more than one progress interrupt", |
| 27340 | 28336 | " --quiet|-q No output except at interrupts", |
| 27341 | 28337 | " --reset Reset the count for each input and interrupt", |
| 27342 | 28338 | " --timeout S Halt after running for S seconds", |
| 27343 | 28339 | #endif |
| 27344 | | - ".prompt MAIN CONTINUE Replace the standard prompts", |
| 27345 | | - " --reset Revert to default prompts", |
| 27346 | | - " --show Show the current prompt strings", |
| 27347 | | - " -- No more options. Subsequent args are prompts", |
| 28340 | + ".prompt MAIN CONTINUE Replace the standard prompts", |
| 28341 | + " --hard-reset Unset SQLITE_PS1/2 and then --reset", |
| 28342 | + " --reset Revert to default prompts", |
| 28343 | + " --show Show the current prompt strings", |
| 28344 | + " -- No more options. Subsequent args are prompts", |
| 27348 | 28345 | #ifndef SQLITE_SHELL_FIDDLE |
| 27349 | 28346 | ".quit Stop interpreting input stream, exit if primary.", |
| 27350 | 28347 | ".read FILE Read input from FILE or command output", |
| 27351 | 28348 | " If FILE begins with \"|\", it is a command that generates the input.", |
| 27352 | 28349 | #endif |
| | @@ -27622,11 +28619,11 @@ |
| 27622 | 28619 | { ".testcase", |
| 27623 | 28620 | "USAGE: .testcase [OPTIONS] NAME\n" |
| 27624 | 28621 | "\n" |
| 27625 | 28622 | "Start a new test case identified by NAME. All output\n" |
| 27626 | 28623 | "through the next \".check\" command is captured for comparison. See the\n" |
| 27627 | | -"\".check\" commandn for additional informatioon.\n" |
| 28624 | +"\".check\" command for additional informatioon.\n" |
| 27628 | 28625 | "\n" |
| 27629 | 28626 | "Options:\n" |
| 27630 | 28627 | " --error-prefix TEXT Change error message prefix text to TEXT\n" |
| 27631 | 28628 | }, |
| 27632 | 28629 | }; |
| | @@ -28237,10 +29234,11 @@ |
| 28237 | 29234 | sqlite3_base64_init(p->db, 0, 0); |
| 28238 | 29235 | sqlite3_base85_init(p->db, 0, 0); |
| 28239 | 29236 | sqlite3_regexp_init(p->db, 0, 0); |
| 28240 | 29237 | sqlite3_ieee_init(p->db, 0, 0); |
| 28241 | 29238 | sqlite3_series_init(p->db, 0, 0); |
| 29239 | + sqlite3_analyze_init(p->db, 0, 0); |
| 28242 | 29240 | #ifndef SQLITE_SHELL_FIDDLE |
| 28243 | 29241 | sqlite3_fileio_init(p->db, 0, 0); |
| 28244 | 29242 | sqlite3_completion_init(p->db, 0, 0); |
| 28245 | 29243 | #endif |
| 28246 | 29244 | #ifdef SQLITE_HAVE_ZLIB |
| | @@ -29116,11 +30114,11 @@ |
| 29116 | 30114 | zCmd = sqlite3_mprintf("%s %s", zXdgOpenCmd, p->zTempFile); |
| 29117 | 30115 | if( system(zCmd) ){ |
| 29118 | 30116 | cli_printf(stderr,"Failed: [%s]\n", zCmd); |
| 29119 | 30117 | }else{ |
| 29120 | 30118 | /* Give the start/open/xdg-open command some time to get |
| 29121 | | - ** going before we continue, and potential delete the |
| 30119 | + ** going before we continue and potentially delete the |
| 29122 | 30120 | ** p->zTempFile data file out from under it */ |
| 29123 | 30121 | sqlite3_sleep(2000); |
| 29124 | 30122 | } |
| 29125 | 30123 | sqlite3_free(zCmd); |
| 29126 | 30124 | modePop(p); |
| | @@ -32266,12 +33264,11 @@ |
| 32266 | 33264 | if( z[1]=='-' ) z++; |
| 32267 | 33265 | if( cli_strcmp(z,"-bom")==0 ){ |
| 32268 | 33266 | zBom = zBomUtf8; |
| 32269 | 33267 | }else if( cli_strcmp(z,"-plain")==0 ){ |
| 32270 | 33268 | bPlain = 1; |
| 32271 | | - }else if( c=='o' && z[0]=='1' && z[1]!=0 && z[2]==0 |
| 32272 | | - && (z[1]=='x' || z[1]=='e' || z[1]=='w') ){ |
| 33269 | + }else if( c=='o' && sqlite3_strglob("-[ewx]",z)==0 ){ |
| 32273 | 33270 | if( bKeep || eMode ){ |
| 32274 | 33271 | dotCmdError(p, i, "incompatible with prior options",0); |
| 32275 | 33272 | goto dotCmdOutput_error; |
| 32276 | 33273 | } |
| 32277 | 33274 | eMode = z[1]; |
| | @@ -32556,11 +33553,11 @@ |
| 32556 | 33553 | ** DOT-COMMAND: .testcase |
| 32557 | 33554 | ** USAGE: .testcase [OPTIONS] NAME |
| 32558 | 33555 | ** |
| 32559 | 33556 | ** Start a new test case identified by NAME. All output |
| 32560 | 33557 | ** through the next ".check" command is captured for comparison. See the |
| 32561 | | -** ".check" commandn for additional informatioon. |
| 33558 | +** ".check" command for additional informatioon. |
| 32562 | 33559 | ** |
| 32563 | 33560 | ** Options: |
| 32564 | 33561 | ** --error-prefix TEXT Change error message prefix text to TEXT |
| 32565 | 33562 | */ |
| 32566 | 33563 | static int dotCmdTestcase(ShellState *p){ |
| | @@ -33030,16 +34027,47 @@ |
| 33030 | 34027 | |
| 33031 | 34028 | #if SQLITE_SHELL_HAVE_RECOVER |
| 33032 | 34029 | if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbinfo", n)==0 ){ |
| 33033 | 34030 | rc = shell_dbinfo_command(p, nArg, azArg); |
| 33034 | 34031 | }else |
| 33035 | | - |
| 33036 | | - if( c=='r' && cli_strncmp(azArg[0], "recover", n)==0 ){ |
| 33037 | | - open_db(p, 0); |
| 33038 | | - rc = recoverDatabaseCmd(p, nArg, azArg); |
| 33039 | | - }else |
| 33040 | 34032 | #endif /* SQLITE_SHELL_HAVE_RECOVER */ |
| 34033 | + |
| 34034 | + if( c=='d' && n==6 && cli_strncmp(azArg[0], "dbstat", n)==0 ){ |
| 34035 | + const char *zSchema = 0; |
| 34036 | + int ii; |
| 34037 | + char *zSql; |
| 34038 | + open_db(p, 0); |
| 34039 | + for(ii=1; ii<nArg; ii++){ |
| 34040 | + const char *z = azArg[ii]; |
| 34041 | + if( z[0]=='-' ){ |
| 34042 | + dotCmdError(p, ii, "unknown option", 0); |
| 34043 | + rc = 1; |
| 34044 | + goto meta_command_exit; |
| 34045 | + } |
| 34046 | + if( zSchema ){ |
| 34047 | + dotCmdError(p, ii, "unknown argument", 0); |
| 34048 | + rc = 1; |
| 34049 | + goto meta_command_exit; |
| 34050 | + } |
| 34051 | + zSchema = z; |
| 34052 | + } |
| 34053 | + zSql = sqlite3_mprintf("SELECT analyze(%Q)", zSchema); |
| 34054 | + shell_check_oom(zSql); |
| 34055 | + modePush(p); |
| 34056 | + modeChange(p, MODE_BATCH); |
| 34057 | + p->mode.spec.nLineLimit = 0; |
| 34058 | + p->mode.spec.nCharLimit = 0; |
| 34059 | + p->mode.spec.nTitleLimit = 0; |
| 34060 | + shell_exec(p, zSql, 0); |
| 34061 | + modePop(p); |
| 34062 | + sqlite3_free(zSql); |
| 34063 | + }else |
| 34064 | + |
| 34065 | + if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbtotxt", n)==0 ){ |
| 34066 | + open_db(p, 0); |
| 34067 | + rc = shell_dbtotxt_command(p, nArg, azArg); |
| 34068 | + }else |
| 33041 | 34069 | |
| 33042 | 34070 | if( c=='d' && cli_strncmp(azArg[0], "dump", n)==0 ){ |
| 33043 | 34071 | char *zLike = 0; |
| 33044 | 34072 | char *zSql; |
| 33045 | 34073 | int i; |
| | @@ -33167,15 +34195,10 @@ |
| 33167 | 34195 | eputz("Usage: .echo on|off\n"); |
| 33168 | 34196 | rc = 1; |
| 33169 | 34197 | } |
| 33170 | 34198 | }else |
| 33171 | 34199 | |
| 33172 | | - if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbtotxt", n)==0 ){ |
| 33173 | | - open_db(p, 0); |
| 33174 | | - rc = shell_dbtotxt_command(p, nArg, azArg); |
| 33175 | | - }else |
| 33176 | | - |
| 33177 | 34200 | if( c=='e' && cli_strncmp(azArg[0], "eqp", n)==0 ){ |
| 33178 | 34201 | if( nArg==2 ){ |
| 33179 | 34202 | if( p->mode.autoEQPtrace ){ |
| 33180 | 34203 | if( p->db ) sqlite3_exec(p->db, "PRAGMA vdbe_trace=OFF;", 0, 0, 0); |
| 33181 | 34204 | p->mode.autoEQPtrace = 0; |
| | @@ -34139,10 +35162,20 @@ |
| 34139 | 35162 | int noOpt = 0; |
| 34140 | 35163 | for(i=1; i<nArg; i++){ |
| 34141 | 35164 | const char *z = azArg[i]; |
| 34142 | 35165 | if( z[0]=='-' && !noOpt ){ |
| 34143 | 35166 | if( z[1]=='-' ) z++; |
| 35167 | + if( strcmp(z,"-hard-reset")==0 ){ |
| 35168 | +#ifdef _WIN32 |
| 35169 | + _putenv("SQLITE_PS1="); |
| 35170 | + _putenv("SQLITE_PS2="); |
| 35171 | +#else |
| 35172 | + unsetenv("SQLITE_PS1"); |
| 35173 | + unsetenv("SQLITE_PS2"); |
| 35174 | +#endif |
| 35175 | + z += 5; |
| 35176 | + } |
| 34144 | 35177 | if( strcmp(z,"-reset")==0 ){ |
| 34145 | 35178 | free(p->azPrompt[0]); |
| 34146 | 35179 | free(p->azPrompt[1]); |
| 34147 | 35180 | memset(p->azPrompt, 0, sizeof(p->azPrompt)); |
| 34148 | 35181 | }else |
| | @@ -34160,13 +35193,16 @@ |
| 34160 | 35193 | } |
| 34161 | 35194 | }else if( cnt>1 ){ |
| 34162 | 35195 | dotCmdError(p, i, "extra argument", 0); |
| 34163 | 35196 | rc = 1; |
| 34164 | 35197 | goto meta_command_exit; |
| 35198 | + }else if( !p->dot.abQuot[i] && sqlite3_strglob("*[^a-z]*",z)!=0 ){ |
| 35199 | + dotCmdError(p, i, "use quotes around the prompt string", 0); |
| 34165 | 35200 | }else{ |
| 34166 | 35201 | free(p->azPrompt[cnt]); |
| 34167 | 35202 | p->azPrompt[cnt] = strdup(z); |
| 35203 | + cnt++; |
| 34168 | 35204 | } |
| 34169 | 35205 | } |
| 34170 | 35206 | }else |
| 34171 | 35207 | |
| 34172 | 35208 | #ifndef SQLITE_SHELL_FIDDLE |
| | @@ -34211,10 +35247,17 @@ |
| 34211 | 35247 | p->in = inSaved; |
| 34212 | 35248 | p->lineno = savedLineno; |
| 34213 | 35249 | }else |
| 34214 | 35250 | #endif /* !defined(SQLITE_SHELL_FIDDLE) */ |
| 34215 | 35251 | |
| 35252 | +#if SQLITE_SHELL_HAVE_RECOVER |
| 35253 | + if( c=='r' && cli_strncmp(azArg[0], "recover", n)==0 ){ |
| 35254 | + open_db(p, 0); |
| 35255 | + rc = recoverDatabaseCmd(p, nArg, azArg); |
| 35256 | + }else |
| 35257 | +#endif /* SQLITE_SHELL_HAVE_RECOVER */ |
| 35258 | + |
| 34216 | 35259 | #ifndef SQLITE_SHELL_FIDDLE |
| 34217 | 35260 | if( c=='r' && n>=3 && cli_strncmp(azArg[0], "restore", n)==0 ){ |
| 34218 | 35261 | const char *zSrcFile; |
| 34219 | 35262 | const char *zDb; |
| 34220 | 35263 | sqlite3 *pSrc; |
| | @@ -37245,10 +38288,11 @@ |
| 37245 | 38288 | linenoiseSetCompletionCallback(linenoise_completion); |
| 37246 | 38289 | #elif HAVE_LINENOISE==2 |
| 37247 | 38290 | linenoiseSetCompletionCallback(linenoise_completion, NULL); |
| 37248 | 38291 | #endif |
| 37249 | 38292 | data.in = 0; |
| 38293 | + open_db(&data, 0); |
| 37250 | 38294 | rc = process_input(&data, "<stdin>"); |
| 37251 | 38295 | if( zHistory ){ |
| 37252 | 38296 | shell_stifle_history(2000); |
| 37253 | 38297 | shell_write_history(zHistory); |
| 37254 | 38298 | sqlite3_free(zHistory); |
| 37255 | 38299 | |