From: drh <> Date: Sat, 15 Feb 2025 20:31:25 +0000 (+0000) Subject: An alternative implementation of the default-in-values feature that is X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=633e3fed602d666cfcffb3d6bc1f50628e27f212;p=thirdparty%2Fsqlite.git An alternative implementation of the default-in-values feature that is 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 --- diff --git a/manifest b/manifest index 2f7a6368f8..bcb98675b2 100644 --- 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. diff --git a/manifest.uuid b/manifest.uuid index 99249bbdb0..c2ffb5ad84 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -1860ea060bd373f49d0b5d41367409a4607e9a0a0cb606af99927af15de1e21e +a3d831378d146f241e0a6f23eb4b8aac4da99904a91979a2e962e5cfa3ad4605 diff --git a/src/expr.c b/src/expr.c index 3011fcd9ad..b27f114792 100644 --- a/src/expr.c +++ b/src/expr.c @@ -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 diff --git a/src/insert.c b/src/insert.c index c1ca1897ed..56fb794615 100644 --- a/src/insert.c +++ b/src/insert.c @@ -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; inCol; 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; inCol; 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; inExpr; i++){ + Expr *p = pList->a[i].pExpr; + if( p->op!=TK_DEFAULT ) continue; + iCol = aColTabMap[i]; + assert( iCol>=0 && iColnCol ); + 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 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 diff --git a/src/parse.y b/src/parse.y index 76c9a8e4ed..e4f75cde0c 100644 --- a/src/parse.y +++ b/src/parse.y @@ -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;} diff --git a/src/sqliteInt.h b/src/sqliteInt.h index c8ecaf8571..639a7e4926 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -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 */