From ad37ec3aa2365660597719298084ca5d1de346da Mon Sep 17 00:00:00 2001 From: drh <> Date: Thu, 7 May 2026 08:58:10 +0000 Subject: [PATCH] Separate conversion letters %J and %j. %J includes surrounding quotes and %j omits them. Some test cases added. FossilOrigin-Name: ea7c2c1a6bd32695fc3a1f2295b4c4719e3d9dd0a8f6480e7c95e17b564c3391 --- manifest | 14 ++++----- manifest.uuid | 2 +- src/printf.c | 75 +++++++++++++++++++++++++----------------------- test/printf.test | 38 ++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 44 deletions(-) diff --git a/manifest b/manifest index 7e7cfa4a43..0417ed7e39 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Get\sprecision\sworking\son\s%J,\sboth\swith\sand\swith\sthe\s!\sflag.\s\sWidth\sis\nstill\sa\sno-op,\sand\sthere\sare\sno\stest\scases.\s\sIncremental\scheck-in. -D 2026-05-07T00:02:55.078 +C Separate\sconversion\sletters\s%J\sand\s%j.\s\s%J\sincludes\ssurrounding\squotes\sand\n%j\somits\sthem.\s\sSome\stest\scases\sadded. +D 2026-05-07T08:58:10.524 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -731,7 +731,7 @@ F src/pcache.h 092b758d2c5e4dabb30eae46d8dfad77c0f70b16bf3ff1943f7a232b0fe0d4ba F src/pcache1.c 131ca0daf4e66b4608d2945ae76d6ed90de3f60539afbd5ef9ec65667a5f2fcd F src/pragma.c 789ef67117b74b5be0a2db6681f7f0c55e6913791b9da309aefd280de2c8a74d F src/prepare.c f6a6e28a281bd1d1da12f47d370a81af46159b40f73bf7fa0b276b664f9c8b7d -F src/printf.c 335f6f468ed6ff0d42e41fac952ab2e69e44e8134af6834589e922b14e080375 +F src/printf.c 1e6fc3f472d530f3a470ed608fdb65e491bcd47aa89f6398e396248d2b8c6eb8 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c fcc406bfb055bee9954ee77c023f4a2a66a24bcdf1573516a72280811a269c20 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 @@ -1519,7 +1519,7 @@ F test/pragma5.test 7b33fc43e2e41abf17f35fb73f71b49671a380ea92a6c94b6ce530a25f8d F test/pragma6.test c5ec577ba087954b4dfa619a3cbe97b155b60a0af487527abe89b10fc17e6512 F test/pragmafault.test 275edaf3161771d37de60e5c2b412627ac94cef11739236bec12ed1258b240f8 F test/prefixes.test b524a1c44bffec225b9aec98bd728480352aa8532ac4c15771fb85e8beef65d9 -F test/printf.test bcb093ef5cbd17e2d94d93d62045ee61ed0f465c1ca123f284774e474e73a9ea +F test/printf.test 764be5acb9a05c1e95223be39f63700e08fef6fd2a00b0132367c24575399513 F test/printf2.test 3f55c1871a5a65507416076f6eb97e738d5210aeda7595a74ee895f2224cce60 F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc @@ -2203,8 +2203,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P 7f6b1bae0849f0a840b95ae95aa6fdc6d51b72fdd50493649f584ad7829a3060 -R 64fe79042d670f076fa407257d28ae73 +P 18166bce208c64b7f3afaa88c27a10e0295a89f9c72942dcaa949cf7efb479e6 +R d336b3fe92a34a0f426cd88f8c5f00bf U drh -Z 9ce0686262e45801ba6d31466b72b666 +Z 137464fef3bd5dbf80b7ef72f16e7f25 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index ae9540d737..e6d9304cae 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -18166bce208c64b7f3afaa88c27a10e0295a89f9c72942dcaa949cf7efb479e6 +ea7c2c1a6bd32695fc3a1f2295b4c4719e3d9dd0a8f6480e7c95e17b564c3391 diff --git a/src/printf.c b/src/printf.c index ccb067dcee..32105d6ecf 100644 --- a/src/printf.c +++ b/src/printf.c @@ -34,9 +34,10 @@ #define etESCAPE_w 14 /* %w -> Strings with '\"' doubled */ #define etORDINAL 15 /* %r -> 1st, 2nd, 3rd, 4th, etc. English only */ #define etDECIMAL 16 /* %d or %u, but not %x, %o */ -#define etJSONSTR 17 /* %J -> generate a JSON string literal */ +#define etESCAPE_j 17 /* %j -> JSON string literal w/o "..." */ +#define etESCAPE_J 18 /* %J -> JSON string literal with "..." */ -#define etINVALID 18 /* Any unrecognized conversion type */ +#define etINVALID 19 /* Any unrecognized conversion type */ /* @@ -66,20 +67,20 @@ typedef struct et_info { /* Information about each format field */ /* ** The table is searched by hash. In the case of %C where C is the character -** and that character has ASCII value j, then the hash is j%24. +** and that character has ASCII value j, then the hash is j%25. ** ** The order of the entries in fmtinfo[] and the hash chain was entered ** manually, but based on the output of the following TCL script: */ #if 0 /***** Beginning of script ******/ -foreach c {d s g z q Q w c o u x X f e E G i n % p T S r J} { +foreach c {d s g z q Q w c o u x X f e E G i n % p T S r J j} { scan $c %c x set n($c) $x } set mx [llength [array names n]] puts "count: $mx" -set mx 27 +set mx 25 puts "*********** mx=$mx ************" for {set r 0} {$r<$mx} {incr r} { puts -nonewline [format %2d: $r] @@ -93,31 +94,32 @@ for {set r 0} {$r<$mx} {incr r} { static const char aDigits[] = "0123456789ABCDEF0123456789abcdef"; static const char aHex[] = "0123456789abcdef"; static const char aPrefix[] = "-x0\000X0"; -static const et_info fmtinfo[24] = { - /* 0 */ { 'x', 16, 0, etRADIX, 16, 1, 0 }, - /* 1 */ { 'J', 0, 0, etJSONSTR, 0, 0, 0 }, /* Hash: 2 */ - /* 2 */ { 'z', 0, 4, etDYNSTRING, 0, 0, 1 }, - /* 3 */ { 'c', 0, 0, etCHARX, 0, 0, 0 }, - /* 4 */ { 'd', 10, 1, etDECIMAL, 0, 0, 0 }, - /* 5 */ { 'e', 0, 1, etEXP, 30, 0, 0 }, - /* 6 */ { 'f', 0, 1, etFLOAT, 0, 0, 0 }, - /* 7 */ { 'g', 0, 1, etGENERIC, 30, 0, 0 }, - /* 8 */ { 'i', 10, 1, etDECIMAL, 0, 0, 0 }, /* Hash: 9 */ - /* 9 */ { 'Q', 0, 4, etESCAPE_Q, 0, 0, 8 }, - /* 10 */ { 'X', 16, 0, etRADIX, 0, 4, 0 }, /* Hash: 16 */ - /* 11 */ { 'S', 0, 0, etSRCITEM, 0, 0, 0 }, - /* 12 */ { 'T', 0, 0, etTOKEN, 0, 0, 0 }, - /* 13 */ { '%', 0, 0, etPERCENT, 0, 0, 0 }, - /* 14 */ { 'n', 0, 0, etSIZE, 0, 0, 0 }, - /* 15 */ { 'o', 8, 0, etRADIX, 0, 2, 0 }, - /* 16 */ { 'p', 16, 0, etPOINTER, 0, 1, 10 }, - /* 17 */ { 'q', 0, 4, etESCAPE_q, 0, 0, 0 }, - /* 18 */ { 'r', 10, 1, etORDINAL, 0, 0, 0 }, - /* 19 */ { 's', 0, 4, etSTRING, 0, 0, 0 }, - /* 20 */ { 'E', 0, 1, etEXP, 14, 0, 0 }, /* Hash: 21 */ - /* 21 */ { 'u', 10, 0, etDECIMAL, 0, 0, 20 }, - /* 22 */ { 'G', 0, 1, etGENERIC, 14, 0, 0 }, /* Hash: 23 */ - /* 23 */ { 'w', 0, 4, etESCAPE_w, 0, 0, 22 } +static const et_info fmtinfo[25] = { + /* 0 */ { 'd', 10, 1, etDECIMAL, 0, 0, 0 }, + /* 1 */ { 'e', 0, 1, etEXP, 30, 0, 0 }, + /* 2 */ { 'f', 0, 1, etFLOAT, 0, 0, 0 }, + /* 3 */ { 'g', 0, 1, etGENERIC, 30, 0, 0 }, + /* 4 */ { 'j', 0, 0, etESCAPE_j, 0, 0, 0 }, /* Hash: 6 */ + /* 5 */ { 'i', 10, 1, etDECIMAL, 0, 0, 0 }, + /* 6 */ { 'Q', 0, 4, etESCAPE_Q, 0, 0, 4 }, + /* 7 */ { 'p', 16, 0, etPOINTER, 0, 1, 0 }, /* Hash: 12 */ + /* 8 */ { 'S', 0, 0, etSRCITEM, 0, 0, 0 }, + /* 9 */ { 'T', 0, 0, etTOKEN, 0, 0, 0 }, + /* 10 */ { 'n', 0, 0, etSIZE, 0, 0, 0 }, + /* 11 */ { 'o', 8, 0, etRADIX, 0, 2, 0 }, + /* 12 */ { '%', 0, 0, etPERCENT, 0, 0, 7 }, + /* 13 */ { 'q', 0, 4, etESCAPE_q, 0, 0, 16 }, + /* 14 */ { 'r', 10, 1, etORDINAL, 0, 0, 0 }, + /* 15 */ { 's', 0, 4, etSTRING, 0, 0, 0 }, + /* 16 */ { 'X', 16, 0, etRADIX, 0, 4, 0 }, /* Hash: 13 */ + /* 17 */ { 'u', 10, 0, etDECIMAL, 0, 0, 0 }, + /* 18 */ { 'w', 0, 4, etESCAPE_w, 0, 0, 0 }, /* Hash: 19 */ + /* 19 */ { 'E', 0, 1, etEXP, 14, 0, 18 }, + /* 20 */ { 'x', 16, 0, etRADIX, 16, 1, 0 }, + /* 21 */ { 'G', 0, 1, etGENERIC, 14, 0, 0 }, + /* 22 */ { 'z', 0, 4, etDYNSTRING, 0, 0, 0 }, + /* 23 */ { 'J', 0, 0, etESCAPE_J, 0, 0, 0 }, /* Hash: 24 */ + /* 24 */ { 'c', 0, 0, etCHARX, 0, 0, 23 } }; /* Additional Notes: @@ -378,8 +380,8 @@ void sqlite3_str_vappendf( } #else /* Fast hash-table lookup */ - assert( ArraySize(fmtinfo)==24 ); - idx = ((unsigned)c) % 24; + assert( ArraySize(fmtinfo)==25 ); + idx = ((unsigned)c) % 25; if( fmtinfo[idx].fmttype==c || fmtinfo[idx = fmtinfo[idx].iNxt].fmttype==c ){ @@ -870,7 +872,8 @@ void sqlite3_str_vappendf( while( ii>=0 ) if( (bufpt[ii--] & 0xc0)==0x80 ) width++; } break; - case etJSONSTR: { /* %J: Generate a JSON string literal */ + case etESCAPE_j: /* %j: JSON string literal w/o "..." */ + case etESCAPE_J: { /* %J: Generate a JSON string literal */ char *escarg; i64 i, j, px; unsigned char ch; @@ -881,9 +884,9 @@ void sqlite3_str_vappendf( escarg = va_arg(ap,char*); } if( escarg==0 ){ - sqlite3_str_append(pAccum, "null", 4); + if( xtype==etESCAPE_J ) sqlite3_str_append(pAccum, "null", 4); }else{ - sqlite3_str_append(pAccum, "\"", 1); + if( xtype==etESCAPE_J ) sqlite3_str_append(pAccum, "\"", 1); px = precision; if( px<=0 ){ px = 0x7fffffff; @@ -913,7 +916,7 @@ void sqlite3_str_vappendf( } } if( j>'abc'; +} 5 +do_execsql_test printf-20.2 { + SELECT format('{%!.3J:5}','αβγδεζη')->>'αβγ'; +} 5 +do_execsql_test printf-20.3 { + SELECT format('{%.6J:5}','abcdefg')->>'abcdef'; +} 5 +do_execsql_test printf-20.4 { + SELECT format('{%.6J:5}','αβγδεζη')->>'αβγ'; +} 5 +do_execsql_test printf-20.5 { + SELECT format('{"%!.3j":5}','abcdefg')->>'abc'; +} 5 +do_execsql_test printf-20.6 { + SELECT format('{"%!.3j":5}','αβγδεζη')->>'αβγ'; +} 5 +do_execsql_test printf-20.7 { + SELECT format('{"%.6j":5}','abcdefg')->>'abcdef'; +} 5 +do_execsql_test printf-20.8 { + SELECT format('{"%.6j":5}','αβγδεζη')->>'αβγ'; +} 5 +do_execsql_test printf-20.9 { + WITH RECURSIVE c(n,t) AS ( + VALUES(1,char(1)) + UNION ALL + SELECT n+1, t||char(n+1) FROM c WHERE n<0x7e + ) + SELECT hex(format('{"a":%J}',t)->>'a') FROM c WHERE n=0x7e; +} {0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E} +do_execsql_test printf-20.10 { + SELECT format('1-%j-2-%J-3',null,null); +} {1--2-null-3} + finish_test -- 2.47.3