From: larrybr Date: Thu, 5 May 2022 03:49:35 +0000 (+0000) Subject: Cause CLI to use ExportHandler interface for its query output, and implement built... X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=29cbcde95a331b9d98212f081b409a57878e03ab;p=thirdparty%2Fsqlite.git Cause CLI to use ExportHandler interface for its query output, and implement built-in subclasses of it, all in preparation for supporting implementations by shell extensions. (a WIP) FossilOrigin-Name: 9b37e0be1a416a49671bbfb1a7e62c66f07b3a9ef6b4ce6cf72a135b046675c5 --- diff --git a/manifest b/manifest index 19fad4e4ca..c242d0b584 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Remove\sFILE*\sfrom\sshell\sextension\sinterface. -D 2022-05-01T14:26:49.216 +C Cause\sCLI\sto\suse\sExportHandler\sinterface\sfor\sits\squery\soutput,\sand\simplement\sbuilt-in\ssubclasses\sof\sit,\sall\sin\spreparation\sfor\ssupporting\simplementations\sby\sshell\sextensions.\s(a\sWIP) +D 2022-05-05T03:49:35.900 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -558,8 +558,8 @@ F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c F src/resolve.c f72bb13359dd5a74d440df25f320dc2c1baff5cde4fc9f0d1bc3feba90b8932a F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/select.c cc1a7581403fc074eee85283ba8d81de50a831ae175cb65a5751be00f621c0d5 -F src/shell.c.in 2609f4dfa2d4340350cb1a51da79f2c359cc0535b5b6c4ee49ffbf05b7675790 -F src/shext_linkage.h b1af148b4f0ab92d73ad08a4f25365c60db497d18461a5b8a51b3c70977e0ac4 +F src/shell.c.in 08ca1d0f9e563003efa9b34f9040ae79e06c08a5c3485ecb8ee2e3657f7b7570 +F src/shext_linkage.h 27dcf7624df05b2a7a6d367834339a6db3636f3035157f641f7db2ec499f8f6d F src/sqlite.h.in 2a35f62185eb5e7ecc64a2f68442b538ce9be74f80f28a00abc24837edcf1c17 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h f49e28c25bd941e79794db5415fdf7b202deb3bc072ed6f1ed273d578703684e @@ -1894,7 +1894,7 @@ F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61 F tool/mkopcodeh.tcl 5dab48c49a25452257494e9601702ab63adaba6bd54a9b382615fa52661c8f8c F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa F tool/mkpragmatab.tcl bd07bd59d45d0f3448e123d6937e9811195f9908a51e09d774609883055bfd3d -F tool/mkshellc.tcl db13d7de92f4a4b8f0a03c4e5942d0c032d2388c6c6fb2cd227c823f0d6ad0b3 x +F tool/mkshellc.tcl db5df976cdb94518b99551df7af737edaf92f0bc276739b656154980c5054277 x F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f @@ -1960,8 +1960,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P d2b16c29fcbd73c2579aefcfb7042b22a2676e84e815c8ba4bf4b5570eca0d97 -R 01c7577fc5e77a5b3ed875d297e8d677 +P 27ff5ce5170ef5902f15ca8fe4133e41b139e0ef5214f8f5a58d12e852a2b782 +R 4beaadfe3bf5ec7c6332a3e29a67a3d9 U larrybr -Z 82444d8b81ca9a647979eae39ec42355 +Z 81b8545fbd4fe6f77f4f8bcaa8f1132f # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index c3199cc4bf..e82c59f8ba 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -27ff5ce5170ef5902f15ca8fe4133e41b139e0ef5214f8f5a58d12e852a2b782 \ No newline at end of file +9b37e0be1a416a49671bbfb1a7e62c66f07b3a9ef6b4ce6cf72a135b046675c5 \ No newline at end of file diff --git a/src/shell.c.in b/src/shell.c.in index 7c6139648a..bd0f695c52 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -250,8 +250,8 @@ static void setTextMode(FILE *file, int isOutput){ /* Use a shorter form of this ubiquitously used, (varargs) API: */ #define smprintf sqlite3_mprintf +/* Forward declaration of the number of built-in dot commands (as compiled) */ static unsigned numCommands; -static FILE *currentOutputFile(ShellExState *p); /* True if the timer is enabled */ static int enableTimer = 0; @@ -512,7 +512,7 @@ void utf8_printf(FILE *out, const char *zFormat, ...){ /* Indicate out-of-memory and exit. */ static void shell_out_of_memory(void){ raw_printf(STD_ERR,"Error: out of memory\n"); - sqlite3_mutex_free(pGlobalDbLock); + sqlite3_mutex_free(pGlobalDbLock); pGlobalDbLock = 0; SHELL_OOM_EXIT; } @@ -1242,6 +1242,9 @@ struct EQPGraph { # define SHEXT_VAREXP(psi) 0 #endif +/* Enable use of ExportHandler and ImportHandler interfaces for built-in I/O */ +#define SHELL_DATAIO_EXT 1 + #if SHELL_DYNAMIC_EXTENSION /* This is only used to support extensions that need this information. @@ -1432,7 +1435,13 @@ typedef struct ShellInState { ShellEventNotify eventHandler; } *pSubscriptions; /* The current shell event subscriptions */ u8 bDbDispatch; /* Cache fact of dbShell dispatch table */ - DotCommand *pUnknown; /* Last registered "unknown" dot command */ + DotCommand *pUnknown; /* Last registered "unknown" dot command */ +#endif + +#if SHELL_DATAIO_EXT + ExportHandler *pFreeformExporter; /* Default freeform mode exporter */ + ExportHandler *pColumnarExporter; /* Default columnar mode exporter */ + ExportHandler *pActiveExporter; /* Presently active exporter */ #endif ShellExState *pSXS; /* Pointer to companion, exposed shell state */ @@ -3985,6 +3994,30 @@ static char *quoted_column(sqlite3_stmt *pStmt, int i){ return 0; /* Not reached */ } +#if SHELL_DATAIO_EXT +/* +** Run a prepared statement with output as determined by ExportHandler. +*/ +static void exec_prepared_stmt( + ShellExState *psx, /* Pointer to shell state */ + sqlite3_stmt *pStmt /* Statment to run */ +){ + ShellInState *psi = ISS(psx); + ExportHandler *pExporter = psi->pActiveExporter; + char *zErr = 0; + int rc; + + rc = pExporter->pMethods->prependResultsOut(pExporter, psx, &zErr, pStmt); + if( rc==SQLITE_OK ){ + do{ + rc = pExporter->pMethods->rowResultsOut(pExporter, psx, &zErr, pStmt); + }while( rc==SQLITE_ROW ); + rc = pExporter->pMethods->appendResultsOut(pExporter, psx, &zErr, pStmt); + } +} + +#else /* !SHELL_DATAIO_EXT */ + /* ** Run a prepared statement and output the result in one of the ** table-oriented formats, for which MODE_IS_COLUMNAR(m) is true: @@ -4079,7 +4112,7 @@ static void exec_prepared_stmt_columnar( } if( wx<0 ) wx = -wx; if( useNextLine ){ - uz = azNextLine[i]; + uz = azNextiLne[i]; if( uz==0 ) uz = (u8*)zEmpty; }else if( psi->cmOpts.bQuote ){ sqlite3_free(azQuoted[i]); @@ -4176,7 +4209,7 @@ static void exec_prepared_stmt_columnar( utf8_printf(psi->out, "%s", psi->cMode==MODE_Box?BOX_13" ":"| "); } z = azData[i]; - if( z==0 ) z = psi->nullValue; + if( z==0 ) z = zEmpty; w = psx->pHaveWidths[j]; if( psx->pSpecWidths[j]<0 ) w = -w; utf8_width_print(psi->out, w, z); @@ -4209,7 +4242,7 @@ columnar_end: nData = (nRow+1)*nColumn; for(i=0; icMode = MODE_Explain; explain_data_prepare(psi, pExplain); + psi->cMode = MODE_Explain; +#if SHELL_DATAIO_EXT + { + ExportHandler *pexSave = psi->pActiveExporter; + psi->pActiveExporter = psi->pFreeformExporter; + exec_prepared_stmt(psx, pExplain); + psi->pActiveExporter = pexSave; + } +#else exec_prepared_stmt(psx, pExplain); +#endif explain_data_delete(psi); } sqlite3_finalize(pExplain); @@ -7225,7 +7268,7 @@ static DotCmdRC arDotCommand( memset(&cmd, 0, sizeof(cmd)); cmd.fromCmdLine = fromCmdLine; rv = arParseCommand(azArg, nArg, &cmd); - cmd.out = currentOutputFile(pState); + cmd.out = ISS(pState)->out; if( rv==DCR_Ok ){ int eDbType = SHELL_OPEN_UNSPEC; cmd.p = pState; @@ -7971,11 +8014,532 @@ FROM (\ } } -static FILE *currentOutputFile(ShellExState *p){ - return ISS(p)->out; +#if SHELL_DATAIO_EXT + +/* +** Standard ExportHandlers +** These implement the built-in renderers of query results. +** Two are provided, one for freeform results, the other for columnar results. +*/ + +static void EH_FF_destruct(ExportHandler *pMe); +static void EH_CM_destruct(ExportHandler *pMe); +static const char *EH_FF_name(ExportHandler *pMe); +static const char *EH_CM_name(ExportHandler *pMe); +static const char *EH_help(ExportHandler *pMe, const char *zWhat); +static int EH_openResultsOutStream(ExportHandler *pMe, + ShellExState *pSES, char **pzErr, + int numArgs, char *azArgs[], + const char * zName); +static int EH_FF_prependResultsOut(ExportHandler *pMe, + ShellExState *pSES, char **pzErr, + sqlite3_stmt *pStmt); +static int EH_CM_prependResultsOut(ExportHandler *pMe, + ShellExState *pSES, char **pzErr, + sqlite3_stmt *pStmt); +static int EH_FF_rowResultsOut(ExportHandler *pMe, + ShellExState *pSES, char **pzErr, + sqlite3_stmt *pStmt); +static int EH_CM_rowResultsOut(ExportHandler *pMe, + ShellExState *pSES, char **pzErr, + sqlite3_stmt *pStmt); +static int EH_FF_appendResultsOut(ExportHandler *pMe, + ShellExState *pSES, char **pzErr, + sqlite3_stmt *pStmt); +static int EH_CM_appendResultsOut(ExportHandler *pMe, + ShellExState *pSES, char **pzErr, + sqlite3_stmt *pStmt); +static void EH_closeResultsOutStream(ExportHandler *pMe, + ShellExState *pSES, + char **pzErr); + +static VTABLE_NAME(ExportHandler) exporter_Vtab_FF = { + EH_FF_destruct, + EH_FF_name, + EH_help, + EH_openResultsOutStream, + EH_FF_prependResultsOut, + EH_FF_rowResultsOut, + EH_FF_appendResultsOut, + EH_closeResultsOutStream +}; + +static VTABLE_NAME(ExportHandler) exporter_Vtab_CM = { + EH_CM_destruct, + EH_CM_name, + EH_help, + EH_openResultsOutStream, + EH_CM_prependResultsOut, + EH_CM_rowResultsOut, + EH_CM_appendResultsOut, + EH_closeResultsOutStream +}; + +typedef struct { + char **azCols; /* Names of result columns */ + char **azVals; /* Results */ + int *aiTypes; /* Result types */ +} ColumnsInfo; + +typedef struct { + VTABLE_NAME(ExportHandler) *pMethods; + ShellInState *psi; + int nCol; + sqlite3_uint64 nRow; + void *pData; + void *pRowInfo; + ColumnsInfo colInfo; +} BuiltInFFExporter; +#define BI_FF_EXPORTER_INIT(psi) { & exporter_Vtab_FF, psi, 0, 0, 0, 0 } + +typedef struct { + VTABLE_NAME(ExportHandler) *pMethods; + ShellInState *psi; + int nCol; + sqlite3_uint64 nRow; + void *pData; + void *pRowInfo; + const char *colSep; + const char *rowSep; +} BuiltInCMExporter; +#define BI_CM_EXPORTER_INIT(psi) { & exporter_Vtab_CM, psi, 0, 0, 0, 0 } + +static void EH_FF_destruct(ExportHandler *pMe){ + /* This serves two purposes: idempotent reinitialize, and final takedown */ + BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe; + if( pbie->nCol!=0 ){ + sqlite3_free(pbie->pData); + pbie->pData = 0; + } + pbie->nRow = 0; + pbie->nCol = 0; +} + +static const char *zEmpty = ""; + +static void EH_CM_destruct(ExportHandler *pMe){ + /* This serves two purposes: idempotent reinitialize, and final takedown */ + BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe; + if( pbie->nCol!=0 ){ + sqlite3_uint64 nData = pbie->nCol * (pbie->nRow + 1), i; + const char *zNull = pbie->psi->nullValue; + char **azData = (char**)pbie->pData; + for(i=0; ipData); + sqlite3_free(pbie->pRowInfo); + pbie->pData = 0; + pbie->pRowInfo = 0; + } + pbie->nCol = 0; + pbie->nRow = 0; + pbie->colSep = 0; + pbie->rowSep = 0; +} + +static const char *zModeName(ShellInState *psi){ + int mi = psi->mode; + return (mi>=0 && mipsi); +} +static const char *EH_CM_name(ExportHandler *pMe){ + return zModeName(((BuiltInCMExporter*)pMe)->psi); +} + +static const char *EH_help(ExportHandler *pMe, const char *zWhat){ + (void)(pMe); + (void)(zWhat); + return 0; +} + +static int EH_openResultsOutStream(ExportHandler *pMe, + ShellExState *pSES, char **pzErr, + int numArgs, char *azArgs[], + const char * zName){ + /* The built-in exporters have a predetermined destination, and their + * action is set by the shell state .mode member, so this method has + * nothing to do. For similar reasons, the shell never calls it. That + * could change if .mode command functionality is moved to here. + */ + (void)(pMe); + (void)(pSES); + (void)(pzErr); + (void)(numArgs); + (void)(azArgs); + (void)(zName); + return 0; +} + +static int EH_CM_prependResultsOut(ExportHandler *pMe, + ShellExState *psx, char **pzErr, + sqlite3_stmt *pStmt){ + BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe; + ShellInState *psi = ISS(psx); + sqlite3_int64 nRow = 0; + char **azData = 0; + sqlite3_int64 nAlloc = 0; + char *abRowDiv = 0; + const unsigned char *uz; + const char *z; + char **azQuoted = 0; + int rc; + sqlite3_int64 i, nData; + int j, w, n; + const unsigned char **azNextLine = 0; + int bNextLine = 0; + int bMultiLineRowExists = 0; + int bw = psi->cmOpts.bWordWrap; + int nColumn = sqlite3_column_count(pStmt); + + if( nColumn==0 ){ + rc = sqlite3_step(pStmt); + assert(rc!=SQLITE_ROW); + return rc; + } + EH_CM_destruct(pMe); + + nAlloc = nColumn*4; + if( nAlloc<=0 ) nAlloc = 1; + azData = sqlite3_malloc64( nAlloc*sizeof(char*) ); + shell_check_oom(azData); + azNextLine = sqlite3_malloc64( nColumn*sizeof(char*) ); + shell_check_oom((void*)azNextLine); + memset((void*)azNextLine, 0, nColumn*sizeof(char*) ); + if( psi->cmOpts.bQuote ){ + azQuoted = sqlite3_malloc64( nColumn*sizeof(char*) ); + shell_check_oom(azQuoted); + memset(azQuoted, 0, nColumn*sizeof(char*) ); + } + abRowDiv = sqlite3_malloc64( nAlloc/nColumn ); + shell_check_oom(abRowDiv); + if( nColumn>psx->numWidths ){ + psx->pSpecWidths = realloc(psx->pSpecWidths, (nColumn+1)*2*sizeof(int)); + shell_check_oom(psx->pSpecWidths); + for(i=psx->numWidths; ipSpecWidths[i] = 0; + psx->numWidths = nColumn; + psx->pHaveWidths = &psx->pSpecWidths[nColumn]; + } + memset(psx->pHaveWidths, 0, nColumn*sizeof(int)); + for(i=0; ipSpecWidths[i]; + if( w<0 ) w = -w; + psx->pHaveWidths[i] = w; + } + for(i=0; ipSpecWidths[i]; + if( wx==0 ){ + wx = psi->cmOpts.iWrap; + } + if( wx<0 ) wx = -wx; + uz = (const unsigned char*)sqlite3_column_name(pStmt,i); + azData[i] = translateForDisplayAndDup(uz, &zNotUsed, wx, bw); + } + while( bNextLine || SQLITE_ROW==sqlite3_step(pStmt) ){ + int useNextLine = bNextLine; + bNextLine = 0; + if( (nRow+2)*nColumn >= nAlloc ){ + nAlloc *= 2; + azData = sqlite3_realloc64(azData, nAlloc*sizeof(char*)); + shell_check_oom(azData); + abRowDiv = sqlite3_realloc64(abRowDiv, nAlloc/nColumn); + shell_check_oom(abRowDiv); + } + abRowDiv[nRow] = 1; + nRow++; + for(i=0; ipSpecWidths[i]; + if( wx==0 ){ + wx = psi->cmOpts.iWrap; + } + if( wx<0 ) wx = -wx; + if( useNextLine ){ + uz = azNextLine[i]; + if( uz==0 ) uz = (u8*)zEmpty; + }else if( psi->cmOpts.bQuote ){ + sqlite3_free(azQuoted[i]); + azQuoted[i] = quoted_column(pStmt,i); + uz = (const unsigned char*)azQuoted[i]; + }else{ + uz = (const unsigned char*)sqlite3_column_text(pStmt,i); + if( uz==0 ) uz = (u8*)psi->nullValue; + } + azData[nRow*nColumn + i] + = translateForDisplayAndDup(uz, &azNextLine[i], wx, bw); + if( azNextLine[i] ){ + bNextLine = 1; + abRowDiv[nRow-1] = 0; + bMultiLineRowExists = 1; + } + } + } + sqlite3_free((void*)azNextLine); + if( azQuoted ){ + for(i=0; ipsx->pHaveWidths[j] ) psx->pHaveWidths[j] = n; + } + if( seenInterrupt ) goto done; + switch( psi->cMode ){ + case MODE_Column: { + pbie->colSep = " "; + pbie->rowSep = "\n"; + if( psi->showHeader ){ + for(i=0; ipHaveWidths[i]; + if( psx->pSpecWidths[i]<0 ) w = -w; + utf8_width_print(psi->out, w, azData[i]); + fputs(i==nColumn-1?"\n":" ", psi->out); + } + for(i=0; iout, psx->pHaveWidths[i]); + fputs(i==nColumn-1?"\n":" ", psi->out); + } + } + break; + } + case MODE_Table: { + pbie->colSep = " | "; + pbie->rowSep = " |\n"; + print_row_separator(psx, nColumn, "+"); + fputs("| ", psi->out); + for(i=0; ipHaveWidths[i]; + n = strlenChar(azData[i]); + utf8_printf(psi->out, "%*s%s%*s", + (w-n)/2, "", azData[i], (w-n+1)/2, ""); + fputs(i==nColumn-1?" |\n":" | ", psi->out); + } + print_row_separator(psx, nColumn, "+"); + break; + } + case MODE_Markdown: { + pbie->colSep = " | "; + pbie->rowSep = " |\n"; + fputs("| ", psi->out); + for(i=0; ipHaveWidths[i]; + n = strlenChar(azData[i]); + utf8_printf(psi->out, "%*s%s%*s", + (w-n)/2, "", azData[i], (w-n+1)/2, ""); + fputs(i==nColumn-1?" |\n":" | ", psi->out); + } + print_row_separator(psx, nColumn, "|"); + break; + } + case MODE_Box: { + pbie->colSep = " " BOX_13 " "; + pbie->rowSep = " " BOX_13 "\n"; + print_box_row_separator(psx, nColumn, BOX_23, BOX_234, BOX_34); + utf8_printf(psi->out, BOX_13 " "); + for(i=0; ipHaveWidths[i]; + n = strlenChar(azData[i]); + utf8_printf(psi->out, "%*s%s%*s%s", + (w-n)/2, "", azData[i], (w-n+1)/2, "", + i==nColumn-1?" "BOX_13"\n":" "BOX_13" "); + } + print_box_row_separator(psx, nColumn, BOX_123, BOX_1234, BOX_134); + break; + } + } + done: + pbie->nCol = nColumn; + pbie->pData = azData; + pbie->nRow = nRow; + if( bMultiLineRowExists ){ + pbie->pRowInfo = abRowDiv; + }else{ + pbie->pRowInfo = 0; + sqlite3_free(abRowDiv); + } + if( seenInterrupt ){ + EH_CM_destruct(pMe); + return SQLITE_INTERRUPT; + } + return SQLITE_OK; +} + +static int EH_CM_rowResultsOut(ExportHandler *pMe, + ShellExState *psx, char **pzErr, + sqlite3_stmt *pStmt){ + BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe; + ShellInState *psi = pbie->psi; + sqlite3_int64 nRow = pbie->nRow; + int nColumn = pbie->nCol, j, w; + char **azData = (char**)(pbie->pData); + sqlite3_int64 nData = (nRow+1)*nColumn, i; + char *abRowDiv = pbie->pRowInfo; + const char *z; + + (void)(pzErr); + (void)(pStmt); + if( nRow==0 || nColumn==0 ) return SQLITE_INTERRUPT; + for(i=nColumn, j=0; icMode!=MODE_Column ){ + utf8_printf(psi->out, "%s", psi->cMode==MODE_Box?BOX_13" ":"| "); + } + z = azData[i]; + if( z==0 ) z = zEmpty; + w = psx->pHaveWidths[j]; + if( psx->pSpecWidths[j]<0 ) w = -w; + utf8_width_print(psi->out, w, z); + if( j==nColumn-1 ){ + utf8_printf(psi->out, "%s", pbie->rowSep); + if( abRowDiv!=0 && abRowDiv[i/nColumn-1] && i+1cMode==MODE_Table ){ + print_row_separator(psx, nColumn, "+"); + }else if( psi->cMode==MODE_Box ){ + print_box_row_separator(psx, nColumn, BOX_123, BOX_1234, BOX_134); + }else if( psi->cMode==MODE_Column ){ + raw_printf(psi->out, "\n"); + } + } + j = -1; + if( seenInterrupt ){ + EH_CM_destruct(pMe); + return SQLITE_INTERRUPT; + } + }else{ + utf8_printf(psi->out, "%s", pbie->colSep); + } + } + return SQLITE_DONE; +} + +static int EH_CM_appendResultsOut(ExportHandler *pMe, + ShellExState *psx, char **pzErr, + sqlite3_stmt *pStmt){ + BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe; + ShellInState *psi = ISS(psx); + sqlite3_int64 nRow = pbie->nRow; + int nColumn = pbie->nCol; + char **azData = (char**)(pbie->pData); + sqlite3_int64 nData = (nRow+1)*nColumn; + + if( nRow==0 || nColumn==0 ) return SQLITE_INTERRUPT; + + if( psi->cMode==MODE_Table ){ + print_row_separator(psx, nColumn, "+"); + }else if( psi->cMode==MODE_Box ){ + print_box_row_separator(psx, nColumn, BOX_12, BOX_124, BOX_14); + } + EH_CM_destruct(pMe); + return SQLITE_OK; +} + +static int EH_FF_prependResultsOut(ExportHandler *pMe, + ShellExState *pSES, char **pzErr, + sqlite3_stmt *pStmt){ + BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe; + int nc = sqlite3_column_count(pStmt); + int rc; + + pbie->pMethods->destruct(pMe); + if( nc>0 ){ + /* allocate space for col name ptr, value ptr, and type */ + pbie->pData = sqlite3_malloc64(3*nc*sizeof(const char*) + 1); + if( !pbie->pData ){ + shell_out_of_memory(); + }else{ + ColumnsInfo ci + = { (char **)pbie->pData, &ci.azCols[nc], (int *)&ci.azVals[nc] }; + int i; + assert(sizeof(int) <= sizeof(char *)); + pbie->nCol = nc; + pbie->colInfo = ci; + /* save off ptrs to column names */ + for(i=0; icolInfo.azCols[i] = (char *)sqlite3_column_name(pStmt, i); + } + } + return SQLITE_OK; + } + rc = sqlite3_step(pStmt); + assert(rc!=SQLITE_ROW); + return rc; +} + +static int EH_FF_rowResultsOut(ExportHandler *pMe, + ShellExState *pSES, char **pzErr, + sqlite3_stmt *pStmt){ + BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe; + ShellInState *psi = ISS(pSES); + int rc = sqlite3_step(pStmt); + int i, x, nc = pbie->nCol; + if( rc==SQLITE_ROW ){ + ColumnsInfo *pc = &pbie->colInfo; + sqlite3_uint64 nr = ++(pbie->nRow); + for( i=0; iaiTypes[i] = x = sqlite3_column_type(pStmt, i); + if( x==SQLITE_BLOB + && (psi->cMode==MODE_Insert || psi->cMode==MODE_Quote) ){ + pc->azVals[i] = ""; + }else{ + pc->azVals[i] = (char*)sqlite3_column_text(pStmt, i); + } + if( !pc->azVals[i] && (x!=SQLITE_NULL) ){ + rc = SQLITE_NOMEM; + break; /* from for */ + } + } + /* if data and types extracted successfully... */ + if( SQLITE_ROW==rc ){ + /* call the supplied callback with the result row data */ + if( shell_callback(pSES, nc, pc->azVals, pc->azCols, pc->aiTypes) ){ + rc = SQLITE_ABORT; + } + } + } + return rc; +} + +static int EH_FF_appendResultsOut(ExportHandler *pMe, + ShellExState *pSES, char **pzErr, + sqlite3_stmt *pStmt){ + BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe; + ShellInState *psi = ISS(pSES); + if( psi->cMode==MODE_Json ){ + fputs("]\n", psi->out); + }else if( psi->cMode==MODE_Count ){ + utf8_printf(psi->out, "%llu row%s\n", pbie->nRow, pbie->nRow!=1 ? "s" : ""); + } + EH_FF_destruct(pMe); + return SQLITE_OK; +} + +static void EH_closeResultsOutStream(ExportHandler *pMe, + ShellExState *pSES, + char **pzErr){ + /* The built-in exporters have a predetermined destination which is + * never "closed", so this method has nothing to do. For similar + * reasons, it is not called by the shell. + */ + (void)(pMe); + (void)(pSES); + (void)(pzErr); } +#endif /* SHELL_DATAIO_EXT */ #if SHELL_DYNAMIC_EXTENSION + /* Ensure there is room in loaded extension info list for one being loaded. * On exit, psi->ixExtPending can be used to index into psi->pShxLoaded. */ @@ -8163,7 +8727,7 @@ static int subscribe_events(ShellExState *p, ExtensionId eid, void *pvUserData, } } -/* +/* * Unsubscribe all event listeners having an ExtensionId > 0. This is * done just prior closing the shell DB (when dynamic extensions will * be unloaded and accessing them in any way is good for a crash.) @@ -10413,9 +10977,18 @@ DISPATCHABLE_COMMAND( mode ? 1 0 ){ } }else{ effectMode(psi, foundMode, setMode); - if( MODE_IS_COLUMNAR(setMode) ) psi->cmOpts = cmOpts; - else if( setMode==MODE_Insert ){ - set_table_name(p, zTabname ? zTabname : "table"); + if( MODE_IS_COLUMNAR(setMode) ){ + psi->cmOpts = cmOpts; +#if SHELL_DATAIO_EXT + psi->pActiveExporter = psi->pColumnarExporter; +#endif + }else{ +#if SHELL_DATAIO_EXT + psi->pActiveExporter = psi->pFreeformExporter; +#endif + if( setMode==MODE_Insert ){ + set_table_name(p, zTabname ? zTabname : "table"); + } } } psi->cMode = psi->mode; @@ -12488,6 +13061,7 @@ DISPATCHABLE_COMMAND( shxopts 3 0 0 ){ raw_printf(STD_ERR, "Error: %s %s\n", zAbout, zMoan); return DCR_CmdErred; } + DISPATCHABLE_COMMAND( show ? 1 1 ){ static const char *azBool[] = { "off", "on", "trigger", "full"}; const char *zOut; @@ -13647,14 +14221,7 @@ COMMENT define new or altered dot-commands and their help text. */ INCLUDE( COMMAND_CUSTOMIZE ); -COMMENT This help text is set seperately from dot-command definition section -COMMENT for the always-built-in, non-customizable commands with visible help. -COLLECT_HELP_TEXT[ - ".exit ?CODE? Exit this program with return-code CODE or 0", - ".quit Exit this program", -]; - -static void DotCommand_dtor(DotCommand *); +static void DotCommand_destruct(DotCommand *); static const char * DotCommand_name(DotCommand *); static const char * DotCommand_help(DotCommand *, const char *); static DotCmdRC @@ -13663,7 +14230,7 @@ static DotCmdRC DotCommand_execute(DotCommand *, ShellExState *, char **, int, char *[]); static VTABLE_NAME(DotCommand) dot_cmd_VtabBuiltIn = { - DotCommand_dtor, + DotCommand_destruct, DotCommand_name, DotCommand_help, DotCommand_argsCheck, @@ -13695,7 +14262,7 @@ static DotCommand *builtInCommand(int ix){ return (DotCommand *)&command_table[ix]; } -static void DotCommand_dtor(DotCommand *pMe){ +static void DotCommand_destruct(DotCommand *pMe){ UNUSED_PARAMETER(pMe); } @@ -15353,6 +15920,10 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){ #endif ShellInState data; ShellExState datax; +#if SHELL_DATAIO_EXT + BuiltInFFExporter ffExporter = BI_FF_EXPORTER_INIT( &data ); + BuiltInCMExporter cmExporter = BI_CM_EXPORTER_INIT( &data ); +#endif const char *zInitFile = 0; int bQuiet = 0; /* for testing, to suppress banner and history actions */ int i, aec; @@ -15401,6 +15972,11 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){ } #endif main_init(&data,&datax); +#if SHELL_DATAIO_EXT + data.pFreeformExporter = (ExportHandler*)&ffExporter; + data.pColumnarExporter = (ExportHandler*)&cmExporter; + data.pActiveExporter = (ExportHandler*)&ffExporter; +#endif /* On Windows, we must translate command-line arguments into UTF-8. ** The SQLite memory allocator subsystem has to be enabled in order to @@ -15953,6 +16529,10 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){ free(data.zNonce); for(i=0; idestruct((ExportHandler*)&cmExporter); + ffExporter.pMethods->destruct((ExportHandler*)&ffExporter); +#endif aec = datax.shellAbruptExit; /* Clear shell state objects so that valgrind detects real memory leaks. */ memset(&data, 0, sizeof(data)); diff --git a/src/shext_linkage.h b/src/shext_linkage.h index 788ea9d92d..fdbf9fb754 100644 --- a/src/shext_linkage.h +++ b/src/shext_linkage.h @@ -489,8 +489,8 @@ AGGTYPE_BEGIN(ShellExtensionLink) { /* Combining the above, safely, to provide a single test for extensions to * use for assurance that: (1) the load was as a shell extension (with the - * -shext flag rather than bare .load); and (2) the loading host provides - * stated minimum extension API and helper counts. + * .shxload command rather than .load); and (2) the loading host provides + * the stated minimum extension API and helper counts. */ #define SHELL_EXTENSION_LOADFAIL(link_ptr, minNumApi, minNumHelpers) \ (!SHELL_EXTENSION_LINKED(link_ptr) \ diff --git a/tool/mkshellc.tcl b/tool/mkshellc.tcl index 5e32cc0cec..24d54d0cb0 100755 --- a/tool/mkshellc.tcl +++ b/tool/mkshellc.tcl @@ -70,8 +70,8 @@ set ::headComment {/* DO NOT EDIT! ** transforms code from various constituent source files of SQLite into ** this single OUT_FILE file to implement the SQLite command-line shell. ** -** Much of the code found below comes from the SOURCE_FILE file in -** the canonical SQLite source tree. That main file contains "INCLUDE" +** Much of the code below is from the SOURCE_FILE file +** in the canonical SQLite source tree. That source contains "INCLUDE" ** lines that specify other files in the canonical source tree that are ** inserted and transformed, (via macro invocations explained by running ** "tool/mkshellc.tcl --help"), to generate this complete program source. @@ -80,8 +80,8 @@ set ::headComment {/* DO NOT EDIT! ** file, building the program from it is simplified. ** ** To modify this program, get a copy of the canonical SQLite source tree, -** edit file SOURCE_FILE and/or some of the other files it includes, -** then rerun the tool/mkshellc.tcl script. +** edit file SOURCE_FILE and/or some of the other files +** it includes, then rerun the tool/mkshellc.tcl script. */} set ::useShortHead 0 @@ -94,7 +94,7 @@ proc prepare_emit_header {ostr outFile srcFile targPgm} { } set hcNumLines [expr 1+[regexp -all "\n" $head]] set hc [regsub -all {OUT_FILE} $head $outFile] - set hc [regsub -all {SOURCE_FILE} $hc $srcFile] + set hc [regsub -all {SOURCE_FILE} $hc [proj_dir_normalize $srcFile]] set hc [regsub -all {TARGET_PROGRAM} $hc $targPgm] emit_sync [list $hc] $ostr $hcNumLines } @@ -797,6 +797,14 @@ proc transform_line {lineVar nesting} { return 0 } +# Convert leading path fragment to where applicable. +proc proj_dir_normalize {sdir} { + return [string map [list \ + "$::topDir/src/.." \ + "$::topDir/src" /src \ + "$::topDir" \ + ] $sdir] +} set ::incFileStack {} @@ -814,10 +822,7 @@ proc process_file { inFilepath ostrm } { lappend ::incFileStack $inFilepath set inFns [list $inFilepath $istrm] if {$nesting > 0} { - set sayPath [string map [list \ - "$::topDir/src/.." \ - "$::topDir/src" /src \ - ] $inFilepath] + set sayPath [proj_dir_normalize $inFilepath] set splats [string repeat * [expr {33 - [string length $sayPath]/2 }]] set sayFile [list "/*$splats Begin $sayPath $splats*/"] } else { set sayFile {} }