]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Enhance QRF with the mxTuple field, which if greater than 1 causes the
authordrh <>
Tue, 10 Mar 2026 14:02:03 +0000 (14:02 +0000)
committerdrh <>
Tue, 10 Mar 2026 14:02:03 +0000 (14:02 +0000)
QRF_STYLE_Insert style to group as many as mxTuple adjacent rows into
a single INSERT statement.  The field is accessible using the -mxtuple
option in the TCL interface, and the --mxtuple option in ".mode".  The
output of the ".dump" and ".fullschema" commands responds to the current
--mxtuple setting.

FossilOrigin-Name: 659ff6ab5580250707908af003ecd093bd5313f03f21f0efd000a7aff6638b3c

ext/qrf/qrf.c
ext/qrf/qrf.h
manifest
manifest.uuid
src/shell.c.in
src/tclsqlite.c
test/modeA.sql
test/qrf01.test

index 7d00284c6f18d1ccedc60617976358df3e57af16..6e6c379e7bc36f7d744ad37db8daa96b7dc4f432 100644 (file)
@@ -66,6 +66,7 @@ struct Qrf {
       int iIndent;              /* Current slot */
       int *aiIndent;            /* Indentation for each opcode */
     } sExpln;
+    unsigned int nTuple;      /* Tuples issued under QRF_STYLE_Insert */
   } u;
   sqlite3_int64 nRow;         /* Number of rows handled so far */
   int *actualWidth;           /* Actual width of each column */
@@ -2563,30 +2564,45 @@ static void qrfOneSimpleRow(Qrf *p){
       break;
     }
     case QRF_STYLE_Insert: {
-      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.bTitles==QRF_Yes ){
-        for(i=0; i<p->nCol; 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);
+      int mxTuple = p->spec.iVersion>=2 ? p->spec.mxTuple : 1;
+      if( p->u.nTuple==0 || p->u.nTuple>=mxTuple ){
+        if( p->u.nTuple ){
+          sqlite3_str_append(p->pOut, ";\n", 2);
+        }
+        p->u.nTuple = 0;
+        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.bTitles==QRF_Yes ){
+          for(i=0; i<p->nCol; 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, ")", 1);
+        sqlite3_str_append(p->pOut," VALUES(", 8);
+      }else{
+        sqlite3_str_append(p->pOut,",\n  (", 5);
       }
-      sqlite3_str_append(p->pOut," VALUES(", 8);
       for(i=0; i<p->nCol; i++){
         if( i>0 ) sqlite3_str_append(p->pOut, ",", 1);
         qrfRenderValue(p, p->pOut, i);
       }
-      sqlite3_str_append(p->pOut, ");\n", 3);
+      p->u.nTuple++;
+      if( p->u.nTuple>=mxTuple ){
+        sqlite3_str_append(p->pOut, ");\n", 3);
+        p->u.nTuple = 0;
+      }else{
+        sqlite3_str_append(p->pOut, ")", 1);
+      }
       qrfWrite(p);
       break;
     }
@@ -2695,7 +2711,7 @@ static void qrfInitialize(
   size_t sz;                     /* Size of pSpec[], based on pSpec->iVersion */
   memset(p, 0, sizeof(*p));
   p->pzErr = pzErr;
-  if( pSpec->iVersion!=1 ){
+  if( pSpec->iVersion>2 ){
     qrfError(p, SQLITE_ERROR,
        "unusable sqlite3_qrf_spec.iVersion (%d)",
        pSpec->iVersion);
@@ -2755,6 +2771,7 @@ qrf_reinit:
       if( p->spec.zTableName==0 || p->spec.zTableName[0]==0 ){
         p->spec.zTableName = "tab";
       }
+      p->u.nTuple = 0;
       break;
     }
     case QRF_STYLE_Line: {
@@ -2853,20 +2870,23 @@ static void qrfFinalize(Qrf *p){
   switch( p->spec.eStyle ){
     case QRF_STYLE_Count: {
       sqlite3_str_appendf(p->pOut, "%lld\n", p->nRow);
-      qrfWrite(p);
       break;
     }
     case QRF_STYLE_Json: {
       if( p->nRow>0 ){
         sqlite3_str_append(p->pOut, "}]\n", 3);
-        qrfWrite(p);
       }
       break;
     }
     case QRF_STYLE_JObject: {
       if( p->nRow>0 ){
         sqlite3_str_append(p->pOut, "}\n", 2);
-        qrfWrite(p);
+      }
+      break;
+    }
+    case QRF_STYLE_Insert: {
+      if( p->u.nTuple ){
+        sqlite3_str_append(p->pOut, ";\n", 2);
       }
       break;
     }
@@ -2886,15 +2906,14 @@ static void qrfFinalize(Qrf *p){
                                  SQLITE_SCANSTAT_COMPLEX, (void*)&nCycle);
 #endif
       qrfEqpRender(p, nCycle);
-      qrfWrite(p);
       break;
     }
     case QRF_STYLE_Eqp: {
       qrfEqpRender(p, 0);
-      qrfWrite(p);
       break;
     }
   }
+  qrfWrite(p);
   qrfStrErr(p, p->pOut);
   if( p->spec.pzOutput ){
     if( p->spec.pzOutput[0] ){
index c23ec772f05a7d021c9c709507c4dd1bddf2aabe..e93e8947c29b986bbe1851ae36c5ed30e941d262 100644 (file)
@@ -56,6 +56,8 @@ struct sqlite3_qrf_spec {
   void *pRenderArg;           /* First argument to the xRender callback */
   void *pWriteArg;            /* First argument to the xWrite callback */
   char **pzOutput;            /* Storage location for output string */
+  /* Fields below are only available if iVersion>=2 */
+  unsigned int mxTuple;       /* Tuples per INSERT in QRF_STYLE_Insert */
   /* Additional fields may be added in the future */
 };
 
index 31f771f1dc06b77e00d00c17ee2f09c4d8722a47..2da1f5bdc200f03d6e6685296efb3215bc12b9d9 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Remove\san\sobsolete\spragraph\sfrom\sthe\sheader\scomment\son\sthe\sfileio.c\sextension.
-D 2026-03-09T14:04:34.278
+C Enhance\sQRF\swith\sthe\smxTuple\sfield,\swhich\sif\sgreater\sthan\s1\scauses\sthe\nQRF_STYLE_Insert\sstyle\sto\sgroup\sas\smany\sas\smxTuple\sadjacent\srows\sinto\na\ssingle\sINSERT\sstatement.\s\sThe\sfield\sis\saccessible\susing\sthe\s-mxtuple\noption\sin\sthe\sTCL\sinterface,\sand\sthe\s--mxtuple\soption\sin\s".mode".\s\sThe\noutput\sof\sthe\s".dump"\sand\s".fullschema"\scommands\sresponds\sto\sthe\scurrent\n--mxtuple\ssetting.
+D 2026-03-10T14:02:03.248
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -420,8 +420,8 @@ F ext/misc/zipfile.c c8ee04e1b349270b5df401ad732f5d7c387146e69b33c02fa90322760cc
 F ext/misc/zorder.c bddff2e1b9661a90c95c2a9a9c7ecd8908afab5763256294dd12d609d4664eee
 F ext/qrf/README.md e6e0ce2700acf6fd06312b42726a8f08ca240f30e1b122bff87c71c602046352
 F ext/qrf/dev-notes.md e68a6d91ce4c7eb296ef2daadc2bb79c95c317ad15b9fafe40850c67b29c2430
-F ext/qrf/qrf.c 6eaa4376ace0dbffd8ae80ac558090ce3f8b4ebea969fb0ae475a105da672ec9
-F ext/qrf/qrf.h 2ac14b0aaacf44636d8c81051bfeab4afae50a98fbb2e10ff5aed0c28a87b2b2
+F ext/qrf/qrf.c d9f2e9fe7f34e700cd4245d39b1272bb62fc360ba95aa3de264bc6521ad8dfa0
+F ext/qrf/qrf.h bdd1711b954c2a3714f22d22693e3d76eb8de3db5efa6afa00c55ed1d7f61028
 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8
 F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255
 F ext/rbu/rbu10.test 7c22caa32c2ff26983ca8320779a31495a6555737684af7aba3daaf762ef3363
@@ -733,7 +733,7 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
 F src/resolve.c 928ff887f2a7c64275182060d94d06fdddbe32226c569781cf7e7edc6f58d7fd
 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
 F src/select.c ffe199f025a0dd74670d2a77232bdea364a4d7b36f32c64a6572d39ba6a11576
-F src/shell.c.in 437863e7d9fed5525d5b7a669f40f926a3a536d8e41747431949f8de53324a96
+F src/shell.c.in c0430d45040f7575df212771653420965a4c259ab79d7256b1acb36b05584524
 F src/sqlite.h.in 4d657846d68a58b028f0c4c331b9d3b4a79306f25c3b0d04fb56060343f73d85
 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479
 F src/sqlite3ext.h 1b7a0ee438bb5c2896d0609c537e917d8057b3340f6ad004d2de44f03e3d3cca
@@ -741,7 +741,7 @@ F src/sqliteInt.h 9716721fb57e32938a1d30a84560ce7633c63860a2209e188c87afad15d4b4
 F src/sqliteLimit.h 904a3f520362c7065c18165aaabd504fb13cc1b76cb411f38bd41ac219e4af1e
 F src/status.c 7565d63a79aa2f326339a24a0461a60096d0bd2bce711fefb50b5c89335f3592
 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
-F src/tclsqlite.c 85b5a20df96016e5d1d8fdc68c8a4c279c5b93e2049b77cd806c2cc50b9d8c56
+F src/tclsqlite.c bebd32b937ec1542234cc63228720ecee7be1a6cd682fb6b55378d7ae01b8bc0
 F src/tclsqlite.h 614b3780a62522bc9f8f2b9fb22689e8009958e7aa77e572d0f3149050af348a
 F src/test1.c 3e3b013f59ffcb57dce00c90d55907072d71d4e970cb0a590cb261efe11bae9c
 F src/test2.c 62f0830958f9075692c29c6de51b495ae8969e1bef85f239ffcd9ba5fb44a5ff
@@ -1445,7 +1445,7 @@ F test/mmap4.test 2e2b4e32555b58da15176e6fe750f17c9dcf7f93
 F test/mmapcorrupt.test 470fb44fe92e99c1d23701d156f8c17865f5b027063c9119dcfdb842791f4465
 F test/mmapfault.test d4c9eff9cd8c2dc14bc43e71e042f175b0a26fe3
 F test/mmapwarm.test 2272005969cd17a910077bd5082f70bc1fefad9a875afec7fc9af483898ecaf3
-F test/modeA.sql b751103d73e86be297cd939cb6401cb9703c4798471d8cab0493831f0cec5000
+F test/modeA.sql 24417bc672b9e3dff3243b6678595ecbf77ef30827b5e24f9faaec9edc30b650
 F test/multiplex.test d74c034e52805f6de8cc5432cef8c9eb774bb64ec29b83a22effc8ca4dac1f08
 F test/multiplex2.test 580ca5817c7edbe4cc68fa150609c9473393003a
 F test/multiplex3.test fac575e0b1b852025575a6a8357701d80933e98b5d2fe6d35ddaa68f92f6a1f7
@@ -1515,7 +1515,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 abc3e558a75ae2678a3172051b39960dc6fd4b298b6d594afa50939759f4037f
+F test/qrf01.test d4bf258413592b8beb43d838f89c9f57882fc74efef556d2a201b7372ec543a7
 F test/qrf02.test 39b4afdc000bedccdafc0aecf17638df67a67aaa2d2942865ae6abcc48ba0e92
 F test/qrf03.test e7efe46d204671726b4707585126cd78d107368de4a7d0c7b8d5157cdd8624ed
 F test/qrf04.test 0894692c998d2401dcc33449c02051b503ecce0c94217be54fb007c82d2d1379
@@ -2192,8 +2192,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee
 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
 F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c
-P 00c96cca3de709e40207feb9c10a94e38e7857c9bc01938849c6c5daee7148c7
-R fcb7762703850ff4a223c73c86d8213b
+P 17613b72f34162ce22864621c519e883930c9f51a0a86afbf30ce664f95d7be1
+R f968ce4caf72dda5ab5b6b5f955a5572
 U drh
-Z 5feea05abb68b14007f15d510b4cd497
+Z fe7873b6c74889be5e5d2a0eb3de0493
 # Remove this line to create a well-formed Fossil manifest.
index bc6bc1f84188e38906bb0277b9cffafd0024ffa4..1f7c55bb4a55de9df298b6ac9cbf818d80bf6aed 100644 (file)
@@ -1 +1 @@
-17613b72f34162ce22864621c519e883930c9f51a0a86afbf30ce664f95d7be1
+659ff6ab5580250707908af003ecd093bd5313f03f21f0efd000a7aff6638b3c
index 6a982854b6036f1f665273b38d262745a8f79a61..bfa63d34f48296776e329ec9a75aad684553348f 100644 (file)
@@ -1571,7 +1571,7 @@ static void modeFree(Mode *p){
   free(p->spec.zTableName);
   free(p->spec.zNull);
   memset(p, 0, sizeof(*p));
-  p->spec.iVersion = 1;
+  p->spec.iVersion = 2;
   p->autoExplain = autoExplain;
 }
 
@@ -1686,7 +1686,7 @@ static void modeChange(ShellState *p, unsigned char eMode){
 ** already been freed and zeroed prior to calling this routine.
 */
 static void modeDefault(ShellState *p){
-  p->mode.spec.iVersion = 1;
+  p->mode.spec.iVersion = 2;
   p->mode.autoExplain = 1;
   if( stdin_is_interactive || stdout_is_console ){
     modeChange(p, MODE_TTY);
@@ -7920,6 +7920,9 @@ static int modeTitleDsply(ShellState *p, int bAll){
 **                            can also be "off" to mean "0,0,0" or "on" to
 **                            mean "5,300,20".
 **   --list                   List available modes
+**   --mxtuple N              In "insert" mode, try to put as many as N
+**                            comma-separated tuples after the VALUES in each
+**                            INSERT statement.
 **   --null STRING            Render SQL NULL values as the given string
 **   --once                   Setting changes to the right are reverted after
 **                            the next SQL command.
@@ -8035,8 +8038,9 @@ static int dotCmdMode(ShellState *p){
         p->mode.spec.bBorder = k & 0x3;
       }
       chng = 1;
-    }else if( 0<=(k=pickStr(z,0,"-charlimit","-linelimit","-titlelimit","")) ){
-      int w;                /*   0            1  */
+    }else if( 0<=(k=pickStr(z,0,
+                  "-charlimit","-linelimit","-titlelimit","-mxtuple","")) ){
+      int w;   /*  0            1            2             3 */
       if( i+1>=nArg ){
         dotCmdError(p, i, "missing argument", 0);
         return 1;
@@ -8045,7 +8049,8 @@ static int dotCmdMode(ShellState *p){
       switch( k ){
         case 0:   p->mode.spec.nCharLimit = w;   break;
         case 1:   p->mode.spec.nLineLimit = w;   break;
-        default:  p->mode.spec.nTitleLimit = w;  break;
+        case 2:   p->mode.spec.nTitleLimit = w;  break;
+        default:  p->mode.spec.mxTuple = w;      break;
       }
       chng = 1;
     }else if( 0<=(k=pickStr(z,0,"-tablename","-rowsep","-colsep","-null","")) ){
@@ -8388,6 +8393,11 @@ static int dotCmdMode(ShellState *p){
            p->mode.spec.nTitleLimit);
       }
     }
+    if( bAll
+     || (p->mode.spec.mxTuple>1 && p->mode.spec.eStyle==QRF_STYLE_Insert)
+    ){
+      sqlite3_str_appendf(pDesc, " --mxtuple %u", p->mode.spec.mxTuple);
+    }
     zSetting = aModeStr[pI->eNull];
     if( bAll || (zSetting && cli_strcmp(zSetting,p->mode.spec.zNull)!=0) ){
       sqlite3_str_appendf(pDesc, " --null ");
index 2c7918926643a817bdb2a757d2e4a539c9ff4091..7f51096fb73b8e58a38a5cdca7788abff7232dd6 100644 (file)
@@ -2071,6 +2071,7 @@ static void DbHookCmd(
 **     -linelimit NUMBER                       Max lines for any cell
 **     -charlimit NUMBER                       Content truncated to this size
 **     -titlelimit NUMBER                      Max width of column titles
+**     -mxtuple NUMBER                         Max tuples per INSERT
 **     -align LIST-OF-ALIGNMENT                Alignment of columns
 **     -widths LIST-OF-NUMBERS                 Widths for individual columns
 **     -columnsep TEXT                         Column separator text
@@ -2099,6 +2100,7 @@ static void DbHookCmd(
 **     -linelimit        nLineLimit
 **     -charlimit        nCharLimit
 **     -titlelimit       nTitleLimit
+**     -mxtuple          mxTuple
 **     -align            nAlign, aAlign
 **     -widths           nWidth, aWidth
 **     -columnsep        zColumnSep
@@ -2134,7 +2136,7 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){
   };
 
   memset(&qrf, 0, sizeof(qrf));
-  qrf.iVersion = 1;
+  qrf.iVersion = 2;
   qrf.pzOutput = &zResult;
   for(i=2; i<objc; i++){
     const char *zArg = Tcl_GetString(objv[i]);
@@ -2301,6 +2303,13 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){
       if( v<0 ) v = 0;
       qrf.nCharLimit = v;
       i++;
+    }else if( strcmp(zArg,"-mxtuple")==0 ){
+      int v = 0;
+      rc = Tcl_GetIntFromObj(pDb->interp, objv[i+1], &v);
+      if( rc ) goto format_failed;
+      if( v<0 ) v = 0;
+      qrf.mxTuple = v;
+      i++;
     }else if( strcmp(zArg,"-align")==0 ){
       Tcl_Size n = 0;
       int jj;
index 8db7d9afa5d7a3f32f0c5eeb5b159239c699f049..a031995b569ef82e4efce7fe5ddc063bf04ecb48 100644 (file)
@@ -87,7 +87,7 @@ END
 .testcase 140
 .mode -v
 .check <<END
-.mode qbox --align "" --border on --blob-quote auto --colsep "" --escape auto --limits on --null "NULL" --quote relaxed --rowsep "" --sw auto --tablename "" --textjsonb on --titles on --widths "" --wordwrap off --wrap 10
+.mode qbox --align "" --border on --blob-quote auto --colsep "" --escape auto --limits on --mxtuple 0 --null "NULL" --quote relaxed --rowsep "" --sw auto --tablename "" --textjsonb on --titles on --widths "" --wordwrap off --wrap 10
 END
 .testcase 150 --error-prefix "Error:"
 .mode foo
@@ -146,7 +146,7 @@ END
 .mode --limits 0,0,0
 .mode -v
 .check <<END
-.mode box --align "" --border on --blob-quote auto --colsep "" --escape auto --limits off --null "" --quote off --rowsep "" --sw 0 --tablename "" --textjsonb off --titles on --widths "" --wordwrap off
+.mode box --align "" --border on --blob-quote auto --colsep "" --escape auto --limits off --mxtuple 0 --null "" --quote off --rowsep "" --sw 0 --tablename "" --textjsonb off --titles on --widths "" --wordwrap off
 END
 
 .testcase 400
index 3ae0279577629a910750c68f6008fac31d7622c5..d90c157a6db9472922f242f1f99a5196434bf07e 100644 (file)
@@ -266,6 +266,25 @@ do_test 1.83 {
 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.84 {
+  set result "\n[db format -style insert {SELECT * FROM t1} -mxtuple 2]"
+} {
+INSERT INTO tab VALUES(1,2.5,'three'),
+  (x'424c4f42',NULL,'Ἀμήν');
+}
+do_test 1.85 {
+  set result "\n[db format -style insert {
+     WITH RECURSIVE c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<7)
+       SELECT n, n*10 AS m FROM c;} -mxtuple 5]"
+} {
+INSERT INTO tab VALUES(1,10),
+  (2,20),
+  (3,30),
+  (4,40),
+  (5,50);
+INSERT INTO tab VALUES(6,60),
+  (7,70);
+}
 
 do_test 1.90 {
   set result "\n[db format -style json {SELECT * FROM t1}]"