]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
An alternative implementation of the default-in-values feature that is
authordrh <>
Sat, 15 Feb 2025 20:31:25 +0000 (20:31 +0000)
committerdrh <>
Sat, 15 Feb 2025 20:31:25 +0000 (20:31 +0000)
cleaner (it avoids dodgy poking about in the parser LALR stack looking for
errors) and has less performance impact in the common case where DEFAULT is
not used.

FossilOrigin-Name: a3d831378d146f241e0a6f23eb4b8aac4da99904a91979a2e962e5cfa3ad4605

manifest
manifest.uuid
src/expr.c
src/insert.c
src/parse.y
src/sqliteInt.h

index 2f7a6368f8da4054eb6f84cd3774ab47de22c81c..bcb98675b21c88b45d17fed0ddf33a8e57dfb986 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Remove\stea\sversion\scheck\sfrom\stool/srctree-check.tcl,\sas\sit's\sobsoleted\sby\s[be265559].
-D 2025-02-15T17:29:56.489
+C An\salternative\simplementation\sof\sthe\sdefault-in-values\sfeature\sthat\sis\ncleaner\s(it\savoids\sdodgy\spoking\sabout\sin\sthe\sparser\sLALR\sstack\slooking\sfor\nerrors)\sand\shas\sless\sperformance\simpact\sin\sthe\scommon\scase\swhere\sDEFAULT\sis\nnot\sused.
+D 2025-02-15T20:31:25.346
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md e108e1e69ae8e8a59e93c455654b8ac9356a11720d3345df2a4743e9590fb20d
@@ -729,7 +729,7 @@ F src/date.c 842c08ac143a56a627b05ac51d68624f2b7b03e3b4cba596205e735eed64ee57
 F src/dbpage.c 2e677acb658a29965e55398bbc61161cb7819da538057c8032adac7ab8e4a8c0
 F src/dbstat.c 73362c0df0f40ad5523a6f5501224959d0976757b511299bf892313e79d14f5c
 F src/delete.c 03a77ba20e54f0f42ebd8eddf15411ed6bdb06a2c472ac4b6b336521bf7cea42
-F src/expr.c ca943270395374afc65256ce86cdb152a22fa6ff146895175833b89ba870e117
+F src/expr.c 22cb9d1d3fd849b3c244454e76ddfad3f454af779862424f93ef6a89d754e297
 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
 F src/fkey.c 928ed2517e8732113d2b9821aa37af639688d752f4ea9ac6e0e393d713eeb76f
 F src/func.c b2fb33139972d7d65640b27ea962a49f1616265428001090cab39fcf270228e1
@@ -738,7 +738,7 @@ F src/hash.c 73934a7f7ab1cb110614a9388cb516893b0cf5b7b69e4fd1a0780ac4ce166be7
 F src/hash.h 46b92795a95bfefb210f52f0c316e9d7cdbcdd7e7fcfb0d8be796d3a5767cddf
 F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6
 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
-F src/insert.c 05e04ef637cbc0dccb9a5c5d188a5a2608891e554c8ec17c7a71afe2cf896a06
+F src/insert.c a0d16840019e2906569a4275c1804c80b0cbbd4ad48cc44d585dd0b3fa6aac8c
 F src/json.c 2663a0c7e574cb928de944720dcdcc11c931877d877549b8f1258a4002efd6f7
 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa
 F src/loadext.c 7432c944ff197046d67a1207790a1b13eec4548c85a9457eb0896bb3641dfb36
@@ -768,7 +768,7 @@ F src/os_win.c 49c7725b500f5867e8360e75eeb30f9d70b62fa1f05c8a101da627210578df32
 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
 F src/pager.c 3a1c4e7f69af482e33c8cba8a75afe0dda0ea6391240adac22b040ce1bdeef44
 F src/pager.h 6137149346e6c8a3ddc1eeb40aee46381e9bc8b0fcc6dda8a1efde993c2275b8
-F src/parse.y f84673f1454e2bcf517623d4346e67fb2d73e57826ea103681ad5848238f6029
+F src/parse.y 8febc26b44b19137aaffd4f14b067eaf0c110fe318c5df9802d1298c7d9599e9
 F src/pcache.c 588cc3c5ccaaadde689ed35ce5c5c891a1f7b1f4d1f56f6cf0143b74d8ee6484
 F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5
 F src/pcache1.c 49516ad7718a3626f28f710fa7448ef1fce3c07fd169acbb4817341950264319
@@ -783,7 +783,7 @@ F src/shell.c.in b377a59822f207106424f08aead37e78b609222e98f86f04cc8a03563ccf323
 F src/sqlite.h.in d2902f13ace94d3d3609646bd6d12a2d7a4f6cbdf6a5a4097580ac305f54c3f0
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h 3f046c04ea3595d6bfda99b781926b17e672fd6d27da2ba6d8d8fc39981dcb54
-F src/sqliteInt.h 8cbfef6c26efd539eb93011905f4d3ce7fdb77475d1280764d86f9e7954c464b
+F src/sqliteInt.h a844632bfd2814d13fcad0c535ea0654b869930142a827c41818a1e8d1bcb1a2
 F src/sqliteLimit.h 1bbdbf72bd0411d003267ffebc59a262f061df5653027a75627d03f48ca30523
 F src/status.c cb11f8589a6912af2da3bb1ec509a94dd8ef27df4d4c1a97e0bcf2309ece972b
 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
@@ -2207,8 +2207,11 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350
 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
 F tool/warnings.sh 49a486c5069de041aedcbde4de178293e0463ae9918ecad7539eedf0ec77a139
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P be265559a334eda127862ae54edb58c46051f74445642daa84a9f61a81df1bac
-R ec0de275110a891612615fec90c077b9
-U stephan
-Z db33573bd4969af02b51c5852b05ae57
+P 1860ea060bd373f49d0b5d41367409a4607e9a0a0cb606af99927af15de1e21e
+R 3cb064185b620b2a38969a214589fa36
+T *branch * default-in-values-2
+T *sym-default-in-values-2 *
+T -sym-trunk *
+U drh
+Z d4065c99bc92bee52eb7b186d29eccbe
 # Remove this line to create a well-formed Fossil manifest.
index 99249bbdb0e380d3bcca32ca83a1a4fcd4fb8e27..c2ffb5ad842cb77595df47d716f5c3b4617e1030 100644 (file)
@@ -1 +1 @@
-1860ea060bd373f49d0b5d41367409a4607e9a0a0cb606af99927af15de1e21e
+a3d831378d146f241e0a6f23eb4b8aac4da99904a91979a2e962e5cfa3ad4605
index 3011fcd9add269189845d0cc116d40f1c937b463..b27f114792c65f99d3a8681da9f941fb2f53febf 100644 (file)
@@ -4896,6 +4896,10 @@ expr_code_doover:
       sqlite3VdbeLoadString(v, target, pExpr->u.zToken);
       return target;
     }
+    case TK_DEFAULT: {
+      sqlite3ErrorMsg(pParse, "near \"%#T\": syntax error", pExpr);
+      return target;
+    }
     default: {
       /* Make NULL the default case so that if a bug causes an illegal
       ** Expr node to be passed into this function, it will be handled
index c1ca1897ed8c4797787287c498996e24a75faf2c..56fb794615b4c87fa4f036db320cf1249b685f38 100644 (file)
@@ -671,11 +671,17 @@ static int exprListIsNoAffinity(Parse *pParse, ExprList *pRow){
 **       because the complex rules SQLite uses (see function 
 **       sqlite3SubqueryColumnTypes() in select.c) to determine the effective
 **       affinity of such a column for all rows require access to all values in
-**       the column simultaneously. 
+**       the column simultaneously.
+**
+**    e) The DEFAULT keyword cannot have been used in any of the terms
+**       of the VALUES clause.  This optimization won't work in that case
+**       because we do not yet know the mapping from VALUES-clause terms
+**       into table columns, so we cannot construct the mapping.
 */
 Select *sqlite3MultiValues(Parse *pParse, Select *pLeft, ExprList *pRow){
 
   if( pParse->bHasWith                   /* condition (a) above */
+   || pParse->bDfltInExpr                /* condition (e) above */
    || pParse->db->init.busy              /* condition (b) above */
    || exprListIsConstant(pParse,pRow)==0 /* condition (c) above */
    || (pLeft->pSrc->nSrc==0 &&
@@ -782,6 +788,102 @@ Select *sqlite3MultiValues(Parse *pParse, Select *pLeft, ExprList *pRow){
   return pLeft;
 }
 
+/*
+** The aTabColMap[] array maps table columns into terms of the IDLIST
+** of an INSERT.  For example, if we have:
+**
+**      CREATE TABLE t1(a,b,c,d,e,f,g);
+**                   \/ \___________/
+**           pTab----'        `-------- pTab->aCol[]
+**
+** And we do:
+**
+**      INSERT INTO t1(e,b,g) ....
+**                  \/ \___/
+**           pTab---'    `----- IDLIST in pColumn
+**
+** Then aTabColMap[] contains: { 0, 2, 0, 0, 1, 0, 3 }
+** Thus aTabColMap provides a one-based mapping of table column indexes into
+** IDLIST entries.  0 means "none" because there are often table columns
+** that are not in the IDLIST.  The left-most table columns is 1, and
+** the next table columns is 2 and so forth.
+**
+** This routine creates a new array (obtained from sqlite3DbMalloc() - 
+** the caller must free it) that inverts the mapping.  The returned
+** array aColTabMap[] would be {4, 1, 6}.  This new mapping is zero-based.
+**
+** The aTabColMap and pColumn inputs might both be NULL.  This means
+** that the IDLIST on the INSERT is omitted.  This routine still
+** constructs a column map, but in this case it maps "insertable"
+** columns of the table into actual columns.  Hidden and computed
+** columns are not "insertable" and are thus skipped.
+*/
+static int *computeColTabMap(
+  Parse *pParse,            /* Parsing context */
+  int *const aTabColMap,    /* Mapping from table column to IDList column */
+  Table *pTab,              /* The table */
+  IdList *pColumn           /* The IDLIST */
+){
+  int *aColTabMap;
+
+  if( pParse->nErr ) return 0;
+  assert( aTabColMap!=0 || pColumn==0 );
+  assert( pColumn!=0 || aTabColMap==0 );
+  assert( pTab->nCol>0 );
+  aColTabMap = sqlite3DbMallocZero(pParse->db, sizeof(int)*pTab->nCol);
+  if( aColTabMap==0 ) return 0;
+  if( aTabColMap ){
+    int i;
+    assert( sqlite3DbMallocSize(pParse->db, aTabColMap) >=
+            sizeof(int)*pTab->nCol );
+    for(i=0; i<pTab->nCol; i++){
+      if( aTabColMap[i]>0 ){
+        assert( aTabColMap[i]<=pColumn->nId );
+        aColTabMap[aTabColMap[i]-1] = i;
+      }
+    }
+  }else{
+    int nHidden = 0;
+    int i, j;
+    for(i=j=0; i<pTab->nCol; i++){
+      if( (pTab->aCol[i].colFlags & COLFLAG_NOINSERT)!=0 ){
+        nHidden++;
+      }else{
+        aColTabMap[j++] = i - nHidden;
+      }
+    }
+  }
+  return aColTabMap;
+}
+
+/*
+** Scan all expressions in pList.  If any is just a DEFAULT keyword,
+** convert that expression into a new expressionthat evaluates to
+** the default value of the corresponding table.
+*/
+static void convertDefaultExpr(
+  Parse *pParse,    /* Parsing context */
+  int *aColTabMap,  /* Mapping from pList entry to pTab column number */
+  Table *pTab,      /* Table being inserted into */
+  ExprList *pList   /* The list to scan */
+){
+  int i, iCol;
+  for(i=0; i<pList->nExpr; i++){
+    Expr *p = pList->a[i].pExpr;
+    if( p->op!=TK_DEFAULT ) continue;
+    iCol = aColTabMap[i];
+    assert( iCol>=0 && iCol<pTab->nCol );
+    if( pTab->aCol[iCol].iDflt==0 ){
+      p->op = TK_NULL;
+    }else{
+      p->op = TK_UPLUS;
+      assert( p->pLeft==0 );
+      p->pLeft = sqlite3ExprDup(pParse->db, 
+                           sqlite3ColumnExpr(pTab, &pTab->aCol[iCol]), 0);
+    }
+  }
+}
+
 /* Forward declaration */
 static int xferOptimization(
   Parse *pParse,        /* Parser context */
@@ -845,7 +947,8 @@ static int xferOptimization(
 **
 ** The 3rd template is for when the second template does not apply
 ** and the SELECT clause does not read from <table> at any time.
-** The generated code follows this template:
+** This template is also used when the INSERT has a VALUES clause with
+** two or more rows.  Pseudocode for this, the 3rd template is:
 **
 **         X <- A
 **         goto B
@@ -864,7 +967,7 @@ static int xferOptimization(
 **
 ** The 4th template is used if the insert statement takes its
 ** values from a SELECT but the data is being inserted into a table
-** that is also read as part of the SELECT.  In the third form,
+** that is also read as part of the SELECT.  In the fourth form,
 ** we have to use an intermediate table to store the results of
 ** the select.  The template is like this:
 **
@@ -1104,6 +1207,28 @@ void sqlite3Insert(
     }
   }
 
+  /* If there are DEFAULT keywords within VALUES clauses on the right-hand
+  ** side of this INSERT, convert them into the corresponding column default
+  ** values.
+  */
+  if( pParse->bDfltInExpr ){
+    int *aColTabMap = computeColTabMap(pParse, aTabColMap, pTab, pColumn);
+    if( aColTabMap==0 ){
+      assert( pParse->nErr && pParse->db->mallocFailed );
+    }else if( pSelect==0 ){
+      /* A single-row VALUES clause in pList */
+      convertDefaultExpr(pParse, aColTabMap, pTab, pList);
+    }else if( (pSelect->selFlags & SF_Values)!=0 ){
+      /* A multi-row VALUES clause in pSelect */
+      Select *pS = pSelect;
+      do{
+        convertDefaultExpr(pParse, aColTabMap, pTab, pS->pEList);
+        pS = pS->pPrior;
+      }while( pS );
+    }
+    sqlite3DbFree(db, aColTabMap);
+  }
+
   /* Figure out how many columns of data are supplied.  If the data
   ** is coming from a SELECT statement, then generate a co-routine that
   ** produces a single row of the SELECT on each invocation.  The
index 76c9a8e4ed3de1ccb7701b039d1b06321b1f2165..e4f75cde0c7c6c8343b7b788ad701f5822fdd782 100644 (file)
@@ -1404,6 +1404,13 @@ expr(A) ::= expr(B) PTR(C) expr(D). {
   A = sqlite3ExprFunction(pParse, pList, &C, 0);
 }
 
+expr(A) ::= DEFAULT(X). {
+  Expr *p = sqlite3Expr(pParse->db, TK_DEFAULT, 0);
+  sqlite3ExprSetErrorOffset(p, (int)(X.z - pParse->zTail));
+  pParse->bDfltInExpr = 1;
+  A = p;
+}
+
 %type between_op {int}
 between_op(A) ::= BETWEEN.     {A = 0;}
 between_op(A) ::= NOT BETWEEN. {A = 1;}
index c8ecaf85712e3f819cb6a282b732fb6befde7552..639a7e492601530ae192096c03755bcd0bc5409d 100644 (file)
@@ -3855,6 +3855,7 @@ struct Parse {
   bft bHasWith :1;      /* True if statement contains WITH */
   bft okConstFactor :1; /* OK to factor out constants */
   bft checkSchema :1;   /* Causes schema cookie check after an error */
+  bft bDfltInExpr :1;   /* DEFAULT keyword occurs in an expression */
   int nRangeReg;       /* Size of the temporary register block */
   int iRangeReg;       /* First register in temporary register block */
   int nErr;            /* Number of errors seen */