From: drh <> Date: Wed, 5 Nov 2025 18:27:18 +0000 (+0000) Subject: The TCL interface for QRF is now feature complete (at least until I think X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=33cc175e51cd01ebb678f63c35ef2e5998d87057;p=thirdparty%2Fsqlite.git The TCL interface for QRF is now feature complete (at least until I think of new features to add). There is still a lot of testing, bug-fixing, and documentation to be done, however. FossilOrigin-Name: 633fe4fe584ae14ed6ced1ae137bf96a434a012423d70f0f93876737b0ca075c --- diff --git a/ext/qrf/README.md b/ext/qrf/README.md index 0379b5f3b8..e725f75021 100644 --- a/ext/qrf/README.md +++ b/ext/qrf/README.md @@ -50,7 +50,7 @@ struct sqlite3_qrf_spec { unsigned char eEsc; /* How to escape control characters in text */ unsigned char eText; /* Quoting style for text */ unsigned char eBlob; /* Quoting style for BLOBs */ - unsigned char bColumnNames; /* True to show column names */ + unsigned char bColumnNames; /* QRF_SW_On to show column names */ unsigned char bWordWrap; /* Try to wrap on word boundaries */ unsigned char bTextJsonb; /* Render JSONB blobs as JSON text */ short int mxWidth; /* Maximum width of any column */ @@ -112,8 +112,18 @@ ignored, depending on the value of eStyle. ### 2.4 Show Column Names (bColumnNames) -The sqlite3_qrf_spec.bColumnNames field is a boolean. If true, then column -names appear in the output. If false, column names are omitted. +The sqlite3_qrf_spec.bColumnNames field can be either QRF_SW_Auto, +QRF_SW_On, or QRF_SW_Off. + +> ~~~ +#define QRF_SW_Auto 0 /* Let QRF choose the best value */ +#define QRF_SW_Off 1 /* This setting is forced off */ +#define QRF_SW_On 2 /* This setting is forced on */ +~~~ + +If the value is QRF_SW_On, then column names appear in the output. +If the value is QRF_SW_Off, column names are omitted. If the +value is QRF_SW_Auto, then an appropriate default is chosen. ### 2.5 Control Character Escapes (eEsc) @@ -196,12 +206,12 @@ the JSON specification. ### 2.7 How to display BLOB values (eBlob and bTextJsonb) -If the sqlite3_qrf_spec.bTextJsonb flag is true and if the value to be +If the sqlite3_qrf_spec.bTextJsonb flag is QRF_SW_On and if the value to be displayed is JSONB, then the JSONB is translated into text JSON and the text is shown according to the sqlite3_qrf_spec.eText setting as described in the previous section. -If the bTextJsonb flag is false (the usual case) or if the BLOB value to +If the bTextJsonb flag is QRF_SW_Off (the usual case) or if the BLOB value to be displayed is not JSONB, then the sqlite3_qrf_spec.eBlob field determines how the BLOB value is formatted. The following options are available; @@ -250,10 +260,10 @@ mxWidth characters wide. "Width" hear means the actual displayed width of the text on a fixed-pitch font. The code takes into account zero-width and double-width characters when comput the display width. -If the sqlite3_qrf_spec.bWordWrap flag is set, then the formatter -attempts to split lines at whitespace or word boundaries. -If the bWorkWrap flag is zero, then long lines can be split in the middle -of words. +If the sqlite3_qrf_spec.bWordWrap flag is set to QRF_SW_On, +then the formatter attempts to split lines at whitespace or word boundaries. +If the bWorkWrap flag is set QRF_SW_Off, then long lines can be split in +the middle of words. ### 2.9 Column width and alignment (nWidth and aWidth) diff --git a/ext/qrf/qrf-tester.c b/ext/qrf/qrf-tester.c deleted file mode 100644 index 8911882c86..0000000000 --- a/ext/qrf/qrf-tester.c +++ /dev/null @@ -1,362 +0,0 @@ -/* -** 2025-10-20 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** A simple command-line program for testing the Result-Format or "resfmt" -** utility library for SQLite. -*/ -#include -#include -#include -#include "sqlite3.h" -#include "qrf.h" - -#define COUNT(X) (sizeof(X)/sizeof(X[0])) - -/* Report out-of-memory and die if the argument is NULL */ -static void checkOOM(void *p){ - if( p==0 ){ - fprintf(stdout, "out-of-memory\n"); - exit(1); - } -} - -/* A block of memory */ -typedef struct memblock memblock; -struct memblock { - memblock *pNext; -}; - -/* List of all memory to be freed */ -static memblock *pToFree = 0; - -/* Free all memory at exit */ -static void tempFreeAll(void){ - while( pToFree ){ - memblock *pNext = pToFree->pNext; - sqlite3_free(pToFree); - pToFree = pNext; - } -} - -/* Allocate memory that will be freed all at once by freeall() */ -static void *tempMalloc(size_t n){ - memblock *p; - if( n>0x10000000 ) checkOOM(0); - p = sqlite3_malloc64( n+sizeof(memblock) ); - checkOOM(p); - p->pNext = pToFree; - pToFree = p; - return (void*)&pToFree[1]; -} - -/* Make a copy of a string using tempMalloc() */ -static char *tempStrdup(char *zIn){ - size_t n; - char *z; - if( zIn==0 ) zIn = ""; - n = strlen(zIn); - if( n>0x10000000 ) checkOOM(0); - z = tempMalloc( n+1 ); - checkOOM(z); - memcpy(z, zIn, n+1); - return z; -} - -/* Function used for writing to the console */ -static sqlite3_int64 testWriter( - void *pContext, - const unsigned char *p, - sqlite3_int64 n -){ - return fwrite(p,1,n,stdout); -} - -/* Render BLOB values as "(%d-byte-blob)". */ -static char *testBlobRender(void *pNotUsed, sqlite3_value *pVal){ - if( sqlite3_value_type(pVal)!=SQLITE_BLOB ) return 0; - return sqlite3_mprintf("(%d-byte-blob)",sqlite3_value_bytes(pVal)); -} - -int main(int argc, char **argv){ - char *zSrc; - FILE *pSrc; - sqlite3_str *pBuf; - sqlite3 *db = 0; - sqlite3_stmt *pStmt; - int rc; - int lineNum = 0; - int bUseWriter = 1; - short int aWidth[100]; - sqlite3_qrf_spec spec; - char zLine[1000]; - - if( argc<2 ){ - zSrc = ""; - pSrc = stdin; - }else{ - zSrc = argv[1]; - pSrc = fopen(zSrc, "rb"); - if( pSrc==0 ){ - fprintf(stderr, "cannot open \"%s\" for reading\n", zSrc); - exit(1); - } - } - memset(&spec, 0, sizeof(spec)); - spec.iVersion = 1; - spec.eStyle = QRF_STYLE_List; - spec.xWrite = testWriter; - pBuf = sqlite3_str_new(0); - rc = sqlite3_open(":memory:", &db); - if( rc ){ - fprintf(stderr, "unable to open an in-memory database: %s\n", - sqlite3_errmsg(db)); - exit(1); - } - while( fgets(zLine, sizeof(zLine), pSrc) ){ - size_t n = strlen(zLine); - lineNum++; - while( n>0 && zLine[n-1]>0 && zLine[n-1]<=' ' ) n--; - zLine[n] = 0; - printf("%s\n", zLine); - if( strncmp(zLine, "--open=", 7)==0 ){ - if( db ) sqlite3_close(db); - db = 0; - rc = sqlite3_open(&zLine[7], &db); - if( rc!=SQLITE_OK ){ - fprintf(stderr, "%s:%d: cannot open \"%s\": %s\n", - zSrc, lineNum, &zLine[7], - sqlite3_errmsg(db)); - exit(1); - } - }else - if( strcmp(zLine, "--go")==0 ){ - const char *zSql, *zTail; - char *zErr = 0; - int n; - if( db==0 ){ - fprintf(stderr, "%s:%d: database not open\n", zSrc, lineNum); - exit(1); - } - zSql = sqlite3_str_value(pBuf); - pStmt = 0; - while( zSql[0] ){ - rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zTail); - if( rc || pStmt==0 ){ - fprintf(stderr, "%s:%d: sqlite3_prepare() fails: %s\n", - zSrc, lineNum, sqlite3_errmsg(db)); - sqlite3_finalize(pStmt); - break; - } - zSql = sqlite3_sql(pStmt); - while( isspace(zSql[0]) ) zSql++; - n = (int)strlen(zSql); - while( n>0 && isspace(zSql[n-1]) ) n--; - if( n>0 ){ - char *zOut = 0; - printf("/* %.*s */\n", n, zSql); - if( bUseWriter ){ - spec.pzOutput = 0; - spec.xWrite = testWriter; - }else{ - spec.pzOutput = &zOut; - spec.xWrite = 0; - } - rc = sqlite3_format_query_result(pStmt, &spec, &zErr); - if( rc!=SQLITE_OK ){ - fprintf(stderr, "%s:%d: Error %d: %s\n", zSrc, lineNum, - rc, zErr); - }else{ - if( !bUseWriter && zOut ){ - fputs(zOut, stdout); - sqlite3_free(zOut); - } - } - sqlite3_free(zErr); - } - sqlite3_finalize(pStmt); - pStmt = 0; - zSql = zTail; - } - sqlite3_str_reset(pBuf); - }else - if( strncmp(zLine, "--eStyle=", 9)==0 ){ - const struct { const char *z; int e; } aStyle[] = { - { "box", QRF_STYLE_Box, }, - { "csv", QRF_STYLE_Csv, }, - { "column", QRF_STYLE_Column, }, - { "count", QRF_STYLE_Count, }, - { "eqp", QRF_STYLE_EQP, }, - { "explain", QRF_STYLE_Explain, }, - { "html", QRF_STYLE_Html, }, - { "insert", QRF_STYLE_Insert, }, - { "json", QRF_STYLE_Json, }, - { "line", QRF_STYLE_Line, }, - { "list", QRF_STYLE_List, }, - { "markdown", QRF_STYLE_Markdown, }, - { "off", QRF_STYLE_Off, }, - { "quote", QRF_STYLE_Quote, }, - { "table", QRF_STYLE_Table, }, - { "scanexp", QRF_STYLE_ScanExp, }, - }; - int i; - for(i=0; i=COUNT(aStyle) ){ - sqlite3_str *pMsg = sqlite3_str_new(0); - for(i=0; i=COUNT(aQuote) ){ - sqlite3_str *pMsg = sqlite3_str_new(0); - for(i=0; i=COUNT(aBlob) ){ - sqlite3_str *pMsg = sqlite3_str_new(0); - for(i=0; i=COUNT(aEscape) ){ - sqlite3_str *pMsg = sqlite3_str_new(0); - for(i=0; ispec.bTextJsonb ){ + if( p->spec.bTextJsonb==QRF_SW_On ){ const char *zJson = qrfJsonbToJson(p, iCol); if( zJson ){ qrfEncodeText(p, pOut, zJson); @@ -1033,7 +1033,7 @@ static void qrfColumnar(Qrf *p){ const unsigned char **azNextLine = 0; int bNextLine = 0; int bMultiLineRowExists = 0; - int bw = p->spec.bWordWrap; + int bw = p->spec.bWordWrap==QRF_SW_On; int nColumn = p->nCol; rc = sqlite3_step(p->pStmt); @@ -1155,7 +1155,7 @@ static void qrfColumnar(Qrf *p){ case QRF_STYLE_Column: { colSep = " "; rowSep = "\n"; - if( p->spec.bColumnNames ){ + if( p->spec.bColumnNames==QRF_SW_On ){ for(i=0; iactualWidth[i]; if( ispec.nWidth && p->spec.aWidth[i]<0 ) w = -w; @@ -1546,7 +1546,7 @@ qrf_reinit: ** not an EQP statement. */ p->spec.eStyle = QRF_STYLE_Quote; - p->spec.bColumnNames = 1; + p->spec.bColumnNames = QRF_SW_On; p->spec.eText = QRF_TEXT_Sql; p->spec.eBlob = QRF_BLOB_Sql; p->spec.zColumnSep = ","; @@ -1563,7 +1563,7 @@ qrf_reinit: ** statement is not an EXPLAIN statement. */ p->spec.eStyle = QRF_STYLE_Quote; - p->spec.bColumnNames = 1; + p->spec.bColumnNames = QRF_SW_On; p->spec.eText = QRF_TEXT_Sql; p->spec.eBlob = QRF_BLOB_Sql; p->spec.zColumnSep = ","; @@ -1587,6 +1587,46 @@ qrf_reinit: default: p->spec.eBlob = QRF_BLOB_Text; break; } } + if( p->spec.bColumnNames==QRF_SW_Auto ){ + switch( p->spec.eStyle ){ + case QRF_STYLE_Box: + case QRF_STYLE_Csv: + case QRF_STYLE_Column: + case QRF_STYLE_Table: + case QRF_STYLE_Markdown: + p->spec.bColumnNames = QRF_SW_On; + break; + default: + p->spec.bColumnNames = QRF_SW_Off; + break; + } + } + if( p->spec.bWordWrap==QRF_SW_Auto ){ + p->spec.bWordWrap = QRF_SW_On; + } + if( p->spec.bTextJsonb==QRF_SW_Auto ){ + p->spec.bTextJsonb = QRF_SW_Off; + } +} + +/* +** Attempt to determine if identifier zName needs to be quoted, either +** because it contains non-alphanumeric characters, or because it is an +** SQLite keyword. Be conservative in this estimate: When in doubt assume +** that quoting is required. +** +** Return 1 if quoting is required. Return 0 if no quoting is required. +*/ + +static int qrf_need_quote(const char *zName){ + int i; + const unsigned char *z = (const unsigned char*)zName; + if( z==0 ) return 1; + if( !isalpha(z[0]) && z[0]!='_' ) return 1; + for(i=0; z[i]; i++){ + if( !isalnum(z[i]) && z[i]!='_' ) return 1; + } + return sqlite3_keyword_check(zName, i)!=0; } /* @@ -1617,7 +1657,7 @@ static void qrfOneSimpleRow(Qrf *p){ break; } case QRF_STYLE_Html: { - if( p->nRow==0 && p->spec.bColumnNames ){ + if( p->nRow==0 && p->spec.bColumnNames==QRF_SW_On ){ sqlite3_str_append(p->pOut, "", 4); for(i=0; inCol; i++){ const char *zCName = sqlite3_column_name(p->pStmt, i); @@ -1636,7 +1676,25 @@ static void qrfOneSimpleRow(Qrf *p){ break; } case QRF_STYLE_Insert: { - sqlite3_str_appendf(p->pOut,"INSERT INTO %s VALUES(",p->spec.zTableName); + if( qrf_need_quote(p->spec.zTableName) ){ + sqlite3_str_appendf(p->pOut,"INSERT INTO \"%w\"",p->spec.zTableName); + }else{ + sqlite3_str_appendf(p->pOut,"INSERT INTO %s",p->spec.zTableName); + } + if( p->spec.bColumnNames==QRF_SW_On ){ + for(i=0; inCol; i++){ + const char *zCName = sqlite3_column_name(p->pStmt, i); + if( qrf_need_quote(zCName) ){ + sqlite3_str_appendf(p->pOut, "%c\"%w\"", + i==0 ? '(' : ',', zCName); + }else{ + sqlite3_str_appendf(p->pOut, "%c%s", + i==0 ? '(' : ',', zCName); + } + } + sqlite3_str_append(p->pOut, ")", 1); + } + sqlite3_str_append(p->pOut," VALUES(", 8); for(i=0; inCol; i++){ if( i>0 ) sqlite3_str_append(p->pOut, ",", 1); qrfRenderValue(p, p->pOut, i); @@ -1685,7 +1743,7 @@ static void qrfOneSimpleRow(Qrf *p){ break; } default: { /* QRF_STYLE_List */ - if( p->nRow==0 && p->spec.bColumnNames ){ + if( p->nRow==0 && p->spec.bColumnNames==QRF_SW_On ){ for(i=0; inCol; i++){ const char *zCName = sqlite3_column_name(p->pStmt, i); if( i>0 ) sqlite3_str_appendall(p->pOut, p->spec.zColumnSep); diff --git a/ext/qrf/qrf.h b/ext/qrf/qrf.h index 8a81cb6500..4b031b4b68 100644 --- a/ext/qrf/qrf.h +++ b/ext/qrf/qrf.h @@ -114,4 +114,13 @@ int sqlite3_format_query_result( #define QRF_ESC_Ascii 2 /* Unix-style escapes. Ex: U+0007 shows ^G */ #define QRF_ESC_Symbol 3 /* Unicode escapes. Ex: U+0007 shows U+2407 */ +/* +** Allowed values for "boolean" fields, such as "bColumnNames", "bWordWrap", +** and "bTextJsonb". There is an extra "auto" variants so these are actually +** tri-state settings, not booleans. +*/ +#define QRF_SW_Auto 0 /* Let QRF choose the best value */ +#define QRF_SW_Off 1 /* This setting is forced off */ +#define QRF_SW_On 2 /* This setting is forced on */ + #endif /* !defined(SQLITE_QRF_H) */ diff --git a/manifest b/manifest index 658304e110..20680703a8 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C A\sfew\ssimple\stest\scases\sadded. -D 2025-11-05T16:29:06.224 +C The\sTCL\sinterface\sfor\sQRF\sis\snow\sfeature\scomplete\s(at\sleast\suntil\sI\sthink\nof\snew\sfeatures\sto\sadd).\s\sThere\sis\sstill\sa\slot\sof\stesting,\sbug-fixing,\nand\sdocumentation\sto\sbe\sdone,\showever. +D 2025-11-05T18:27:18.151 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -416,10 +416,9 @@ F ext/misc/wholenumber.c 0fa0c082676b7868bf2fa918e911133f2b349bcdceabd1198bba5f6 F ext/misc/windirent.h 02211ce51f3034c675f2dbf4d228194d51b3ee05734678bad5106fff6292e60c F ext/misc/zipfile.c 09e6e3a3ff40a99677de3c0bc6569bd5f4709b1844ac3d1c1452a456c5a62f1c F ext/misc/zorder.c bddff2e1b9661a90c95c2a9a9c7ecd8908afab5763256294dd12d609d4664eee -F ext/qrf/README.md 6bfe3047478b8901694cab91ccc98b35a8e14678f38475dc726be485190d4433 -F ext/qrf/qrf-tester.c 3a733b25a25ba7390cd3df055edd76ac72f488a9c5d9eb523a7508b0b8ac8900 -F ext/qrf/qrf.c e45c181d031f71da73c7d605f56ff62640985a9bbb154f0221c5324bbc1b1167 -F ext/qrf/qrf.h 4542c723805550b48ca81ed53e3beea95e793ecc7d2b01d34a780fbea366323f +F ext/qrf/README.md 5f12f91104d5df8ba8783e2240635698569ce54935e721b3a4e93abf4e00ddcb +F ext/qrf/qrf.c 8dd8f3245f86686ddacdf92354655dd290e5ed7668f6cf7d6bcf1f9453160062 +F ext/qrf/qrf.h 98e02eded10848c42053d37e56bd15ab69cb6fe9fc1f1ca05823d9047e9c8509 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8 F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255 F ext/rbu/rbu10.test 7c22caa32c2ff26983ca8320779a31495a6555737684af7aba3daaf762ef3363 @@ -744,7 +743,7 @@ F src/sqliteInt.h 88f7fc9ce1630d9a5f7e0a8e1f3287cdc63882fba985c18e7eee1b9f457f59 F src/sqliteLimit.h fe70bd8983e5d317a264f2ea97473b359faf3ebb0827877a76813f5cf0cdc364 F src/status.c 7565d63a79aa2f326339a24a0461a60096d0bd2bce711fefb50b5c89335f3592 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 -F src/tclsqlite.c bb00869df54bdbb8f214d37e915eaa3033df4db7c758eed58e6f706a97714fd9 +F src/tclsqlite.c 45476ca0b3088504c7e296ce52b4ab0b6b9af57132abeca2867fbcf1a50f84af F src/tclsqlite.h 614b3780a62522bc9f8f2b9fb22689e8009958e7aa77e572d0f3149050af348a F src/test1.c f880ab766eeedf2c063662bd9538b923fd42c4341b7bfc2150a6d93ab8b9341c F src/test2.c 62f0830958f9075692c29c6de51b495ae8969e1bef85f239ffcd9ba5fb44a5ff @@ -1507,7 +1506,7 @@ F test/printf2.test 3f55c1871a5a65507416076f6eb97e738d5210aeda7595a74ee895f2224c F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc F test/pushdown.test 46a626ef1c0ca79b85296ff2e078b9da20a50e9b804b38f441590c3987580ddd -F test/qrf01.test 6b912e5ceb34fb288d47ade071249b8dcd6df40735ee3a7733af99e77e504c25 +F test/qrf01.test 84699d3a41d371dda947a9ca994e8bc19cbc3e43b83d6716c6b43b1b6a7c1b6f F test/queryonly.test 5f653159e0f552f0552d43259890c1089391dcca F test/quick.test 1681febc928d686362d50057c642f77a02c62e57 F test/quickcheck.test a4b7e878cd97e46108291c409b0bf8214f29e18fddd68a42bc5c1375ad1fb80a @@ -2173,8 +2172,8 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 468ba188f034b23398e1f07b915cf7c8b337dcf7f56a13511947c5322ae98722 -R 83d792277e49d25b00c0094481fa71d7 +P 7200372ed2424d1ac4c88644e0b17a224539f170b9111ced9c88774edbd75261 +R c08f61440d4d487375dc477b759daec9 U drh -Z 7f22080d0311b852cf62e1bdb7405410 +Z 6f4e493d857d3f3fb51f0ded7bd78aca # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 2c029eefa2..da59608a77 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7200372ed2424d1ac4c88644e0b17a224539f170b9111ced9c88774edbd75261 +633fe4fe584ae14ed6ced1ae137bf96a434a012423d70f0f93876737b0ca075c diff --git a/src/tclsqlite.c b/src/tclsqlite.c index 73d7931c18..bda4b8bd40 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -2067,16 +2067,26 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){ qrf.pzOutput = &zResult; for(i=2; iinterp, "unknown argument: ", zArg, (char*)0); + rc = TCL_ERROR; + goto format_failed; + } zSql = zArg; - }else if( strcmp(zArg,"-style")==0 && iinterp, "option has no argument: ", zArg, (char*)0); + rc = TCL_ERROR; + goto format_failed; + }else if( strcmp(zArg,"-style")==0 ){ static const char *azStyles[] = { "auto", "box", "column", "count", "csv", "eqp", "explain", "html", "insert", "json", "line", "list", "markdown", "quote", "scanexp", - "table" + "table", 0 }; static unsigned char aStyleMap[] = { QRF_STYLE_Auto, QRF_STYLE_Box, QRF_STYLE_Column, @@ -2087,13 +2097,92 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){ QRF_STYLE_Table }; int style; - if( Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azStyles, - "format style", 0, &style) ) return SQLITE_ERROR; + rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azStyles, + "format style", 0, &style); + if( rc ) goto format_failed; qrf.eStyle = aStyleMap[style]; i++; + }else if( strcmp(zArg,"-null")==0 ){ + qrf.zNull = Tcl_GetString(objv[i+1]); + i++; + }else if( strcmp(zArg,"-rowsep")==0 ){ + qrf.zRowSep = Tcl_GetString(objv[i+1]); + i++; + }else if( strcmp(zArg,"-columnsep")==0 ){ + qrf.zColumnSep = Tcl_GetString(objv[i+1]); + i++; + }else if( strcmp(zArg,"-tablename")==0 ){ + qrf.zTableName = Tcl_GetString(objv[i+1]); + i++; + }else if( strcmp(zArg,"-columnnames")==0 ){ + int v = 0; + rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBool, + "-columnnames", 0, &v); + if( rc ) goto format_failed; + qrf.bColumnNames = v; + i++; + }else if( strcmp(zArg,"-wordwrap")==0 ){ + int v = 0; + rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBool, + "-wordwrap", 0, &v); + if( rc ) goto format_failed; + qrf.bWordWrap = v; + i++; + }else if( strcmp(zArg,"-testjsonb")==0 ){ + int v = 0; + rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBool, + "-testjsonb", 0, &v); + if( rc ) goto format_failed; + qrf.bTextJsonb = v; + i++; + }else if( strcmp(zArg,"-maxwidth")==0 ){ + int v = 0; + rc = Tcl_GetIntFromObj(pDb->interp, objv[i+1], &v); + if( rc ) goto format_failed; + if( v<0 ){ + v = 0; + }else if( v>QRF_MX_WIDTH ){ + v = QRF_MX_WIDTH; + } + qrf.mxWidth = v; + i++; + }else if( strcmp(zArg,"-widths")==0 ){ + int n = 0; + int jj; + rc = Tcl_ListObjLength(pDb->interp, objv[i+1], &n); + if( rc ) goto format_failed; + sqlite3_free(qrf.aWidth); + qrf.aWidth = sqlite3_malloc64( (n+1)*sizeof(qrf.aWidth[0]) ); + if( qrf.aWidth==0 ){ + Tcl_AppendResult(pDb->interp, "out of memory", (char*)0); + rc = TCL_ERROR; + goto format_failed; + } + memset(qrf.aWidth, 0, (n+1)*sizeof(qrf.aWidth[0])); + qrf.nWidth = n; + for(jj=0; jjinterp, objv[i+1], jj, &pTerm); + if( rc ) goto format_failed; + zTerm = Tcl_GetString(pTerm); + if( strcmp(zTerm, "-0")==0 ){ + qrf.aWidth[jj] = QRF_MINUS_ZERO; + }else{ + int v = atoi(zTerm); + if( vQRF_MX_WIDTH ){ + v = QRF_MX_WIDTH; + } + qrf.aWidth[jj] = v; + } + } + i++; }else{ - Tcl_AppendResult(pDb->interp, "unknown argument: ", zArg, (char*)0); - return TCL_ERROR; + Tcl_AppendResult(pDb->interp, "unknown option: ", zArg, (char*)0); + rc = TCL_ERROR; + goto format_failed; } } while( zSql && zSql[0] ){ @@ -2113,10 +2202,11 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){ } } Tcl_SetResult(pDb->interp, zResult, TCL_VOLATILE); - sqlite3_free(zResult); - return TCL_OK; - + rc = TCL_OK; + /* Fall through...*/ + format_failed: + sqlite3_free(qrf.aWidth); sqlite3_free(zResult); return rc; @@ -3075,7 +3165,7 @@ deserialize_error: ** text and return that text. */ case DB_FORMAT: { - dbQrf(pDb, objc, objv); + rc = dbQrf(pDb, objc, objv); break; } diff --git a/test/qrf01.test b/test/qrf01.test index 490687f090..64975331ca 100644 --- a/test/qrf01.test +++ b/test/qrf01.test @@ -57,6 +57,14 @@ do_test 1.30 { do_test 1.40 { set result "\n[db format -style column {SELECT * FROM t1}]" } { +a b c +---- --- ----- +1 2.5 three +BLOB Ἀμήν +} +do_test 1.41 { + set result "\n[db format -style column -columnnames off {SELECT * FROM t1}]" +} { 1 2.5 three BLOB Ἀμήν } @@ -68,6 +76,9 @@ do_test 1.50 { do_test 1.60 { db format -style csv {SELECT * FROM t1} } "1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n" +do_test 1.61 { + db format -style csv -columnnames on {SELECT * FROM t1} +} "a,b,c\r\n1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n" do_test 1.70 { set result "\n[db format -style html {SELECT * FROM t1}]" @@ -83,6 +94,25 @@ do_test 1.70 { Ἀμήν } +do_test 1.71 { + set result "\n[db format -style html -columnnames on {SELECT * FROM t1}]" +} { + +a +b +c + + +1 +2.5 +three + + +BLOB +null +Ἀμήν + +} do_test 1.80 { set result "\n[db format -style insert {SELECT * FROM t1}]" @@ -90,6 +120,26 @@ do_test 1.80 { INSERT INTO tab VALUES(1,2.5,'three'); INSERT INTO tab VALUES(x'424c4f42',NULL,'Ἀμήν'); } +do_test 1.81 { + set result "\n[db format -style insert -tablename t1 {SELECT * FROM t1}]" +} { +INSERT INTO t1 VALUES(1,2.5,'three'); +INSERT INTO t1 VALUES(x'424c4f42',NULL,'Ἀμήν'); +} +do_test 1.82 { + set result "\n[db format -style insert -tablename t1 -columnnames on \ + {SELECT * FROM t1}]" +} { +INSERT INTO t1(a,b,c) VALUES(1,2.5,'three'); +INSERT INTO t1(a,b,c) VALUES(x'424c4f42',NULL,'Ἀμήν'); +} +do_test 1.83 { + set result "\n[db format -style insert -tablename drop -columnnames on \ + {SELECT a AS "a-b", b, c AS "123" FROM t1}]" +} { +INSERT INTO "drop"("a-b",b,"123") VALUES(1,2.5,'three'); +INSERT INTO "drop"("a-b",b,"123") VALUES(x'424c4f42',NULL,'Ἀμήν'); +} do_test 1.90 { set result "\n[db format -style json {SELECT * FROM t1}]" @@ -109,6 +159,17 @@ a = BLOB b = c = Ἀμήν } +do_test 1.101 { + set result "\n[db format -style line -null (NULL) {SELECT * FROM t1}]" +} { +a = 1 +b = 2.5 +c = three + +a = BLOB +b = (NULL) +c = Ἀμήν +} do_test 1.110 { set result "\n[db format -style list {SELECT * FROM t1}]" @@ -116,6 +177,18 @@ do_test 1.110 { 1|2.5|three BLOB||Ἀμήν } +do_test 1.111 { + set result "\n[db format -style list -columnnames on {SELECT * FROM t1}]" +} { +a|b|c +1|2.5|three +BLOB||Ἀμήν +} +do_test 1.112 { + set rc [catch {db format -style list -columnnames unk {SELECT * FROM t1}} res] + lappend rc $res +} {1 {bad -columnnames "unk": must be auto, off, or on}} + do_test 1.120 { set result "\n[db format -style markdown {SELECT * FROM t1}]" @@ -132,5 +205,29 @@ do_test 1.130 { 1,2.5,'three' x'424c4f42',NULL,'Ἀμήν' } +do_test 1.131 { + set result "\n[db format -style quote -columnnames on {SELECT * FROM t1}]" +} { +'a','b','c' +1,2.5,'three' +x'424c4f42',NULL,'Ἀμήν' +} + + +do_execsql_test 2.0 { + DELETE FROM t1; + INSERT INTO t1 VALUES(1,2,'The quick fox jumps over the lazy brown dog.'); +} +do_test 2.1 { + set result "\n[db format -widths {10 -10 22} -wordwrap off \ + {SELECT * FROM t1}]" +} { +┌────────────┬────────────┬────────────────────────┐ +│ a │ b │ c │ +├────────────┼────────────┼────────────────────────┤ +│ 1 │ 2 │ The quick fox jumps ov │ +│ │ │ er the lazy brown dog. │ +└────────────┴────────────┴────────────────────────┘ +} finish_test