/ Check-in [dae4a97a]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Fix the SQLITE_ENABLE_UPDATE_DELETE_LIMIT functionality so that it works with views and WITHOUT ROWID tables.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: dae4a97a483bee1e6ac0271ddd28a0dffcebf7522edaf12eb5e0eba5fc62516a
User & Date: dan 2017-11-14 17:06:37
References
2017-11-14
18:26
Fix the SQLITE_ENABLE_UPDATE_DELETE_LIMIT functionality so that it works with views and WITHOUT ROWID tables. This is a modified cherrypick of [dae4a97a]. check-in: b2679d3b user: dan tags: branch-3.8.9
Context
2017-11-14
20:06
Merge changes from trunk. This fixes the SQLITE_ENABLE_UPDATE_DELETE_LIMIT functionality so that it works with views and WITHOUT ROWID tables. check-in: d90e5f34 user: dan tags: begin-concurrent
20:00
Merge all changes from trunk prior to the read-only WAL enhancement. check-in: 1754faef user: drh tags: apple-osx
19:34
Add the ability to read from read-only WAL-mode database files as long as the -wal and -shm files are present on disk. check-in: 00ec95fc user: drh tags: trunk
17:06
Fix the SQLITE_ENABLE_UPDATE_DELETE_LIMIT functionality so that it works with views and WITHOUT ROWID tables. check-in: dae4a97a user: dan tags: trunk
2017-11-10
20:13
Omit some extra code from non-SQLITE_ENABLE_UPDATE_DELETE_LIMIT builds. Closed-Leaf check-in: 72be33f9 user: dan tags: update-delete-limit-fix
12:41
Fix harmless compiler warning seen with MSVC. check-in: 3711ef23 user: mistachkin tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to src/delete.c.

    86     86   ** pWhere argument is an optional WHERE clause that restricts the
    87     87   ** set of rows in the view that are to be added to the ephemeral table.
    88     88   */
    89     89   void sqlite3MaterializeView(
    90     90     Parse *pParse,       /* Parsing context */
    91     91     Table *pView,        /* View definition */
    92     92     Expr *pWhere,        /* Optional WHERE clause to be added */
           93  +  ExprList *pOrderBy,  /* Optional ORDER BY clause */
           94  +  Expr *pLimit,        /* Optional LIMIT clause */
           95  +  Expr *pOffset,       /* Optional OFFSET clause */
    93     96     int iCur             /* Cursor number for ephemeral table */
    94     97   ){
    95     98     SelectDest dest;
    96     99     Select *pSel;
    97    100     SrcList *pFrom;
    98    101     sqlite3 *db = pParse->db;
    99    102     int iDb = sqlite3SchemaToIndex(db, pView->pSchema);
................................................................................
   102    105     if( pFrom ){
   103    106       assert( pFrom->nSrc==1 );
   104    107       pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName);
   105    108       pFrom->a[0].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zDbSName);
   106    109       assert( pFrom->a[0].pOn==0 );
   107    110       assert( pFrom->a[0].pUsing==0 );
   108    111     }
   109         -  pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, 0, 
   110         -                          SF_IncludeHidden, 0, 0);
          112  +  pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, pOrderBy, 
          113  +                          SF_IncludeHidden, pLimit, pOffset);
   111    114     sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur);
   112    115     sqlite3Select(pParse, pSel, &dest);
   113    116     sqlite3SelectDelete(db, pSel);
   114    117   }
   115    118   #endif /* !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) */
   116    119   
   117    120   #if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY)
................................................................................
   128    131     SrcList *pSrc,               /* the FROM clause -- which tables to scan */
   129    132     Expr *pWhere,                /* The WHERE clause.  May be null */
   130    133     ExprList *pOrderBy,          /* The ORDER BY clause.  May be null */
   131    134     Expr *pLimit,                /* The LIMIT clause.  May be null */
   132    135     Expr *pOffset,               /* The OFFSET clause.  May be null */
   133    136     char *zStmtType              /* Either DELETE or UPDATE.  For err msgs. */
   134    137   ){
   135         -  Expr *pWhereRowid = NULL;    /* WHERE rowid .. */
          138  +  sqlite3 *db = pParse->db;
          139  +  Expr *pLhs = NULL;           /* LHS of IN(SELECT...) operator */
   136    140     Expr *pInClause = NULL;      /* WHERE rowid IN ( select ) */
   137         -  Expr *pSelectRowid = NULL;   /* SELECT rowid ... */
   138    141     ExprList *pEList = NULL;     /* Expression list contaning only pSelectRowid */
   139    142     SrcList *pSelectSrc = NULL;  /* SELECT rowid FROM x ... (dup of pSrc) */
   140    143     Select *pSelect = NULL;      /* Complete SELECT tree */
          144  +  Table *pTab;
   141    145   
   142    146     /* Check that there isn't an ORDER BY without a LIMIT clause.
   143    147     */
   144         -  if( pOrderBy && (pLimit == 0) ) {
          148  +  if( pOrderBy && pLimit==0 ) {
   145    149       sqlite3ErrorMsg(pParse, "ORDER BY without LIMIT on %s", zStmtType);
   146         -    goto limit_where_cleanup;
          150  +    sqlite3ExprDelete(pParse->db, pWhere);
          151  +    sqlite3ExprListDelete(pParse->db, pOrderBy);
          152  +    sqlite3ExprDelete(pParse->db, pLimit);
          153  +    sqlite3ExprDelete(pParse->db, pOffset);
          154  +    return 0;
   147    155     }
   148    156   
   149    157     /* We only need to generate a select expression if there
   150    158     ** is a limit/offset term to enforce.
   151    159     */
   152    160     if( pLimit == 0 ) {
   153    161       /* if pLimit is null, pOffset will always be null as well. */
................................................................................
   160    168     **   DELETE FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1
   161    169     ** becomes:
   162    170     **   DELETE FROM table_a WHERE rowid IN ( 
   163    171     **     SELECT rowid FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1
   164    172     **   );
   165    173     */
   166    174   
   167         -  pSelectRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0);
   168         -  if( pSelectRowid == 0 ) goto limit_where_cleanup;
   169         -  pEList = sqlite3ExprListAppend(pParse, 0, pSelectRowid);
   170         -  if( pEList == 0 ) goto limit_where_cleanup;
          175  +  pTab = pSrc->a[0].pTab;
          176  +  if( HasRowid(pTab) ){
          177  +    pLhs = sqlite3PExpr(pParse, TK_ROW, 0, 0);
          178  +    pEList = sqlite3ExprListAppend(
          179  +        pParse, 0, sqlite3PExpr(pParse, TK_ROW, 0, 0)
          180  +    );
          181  +  }else{
          182  +    Index *pPk = sqlite3PrimaryKeyIndex(pTab);
          183  +    if( pPk->nKeyCol==1 ){
          184  +      const char *zName = pTab->aCol[pPk->aiColumn[0]].zName;
          185  +      pLhs = sqlite3Expr(db, TK_ID, zName);
          186  +      pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db, TK_ID, zName));
          187  +    }else{
          188  +      int i;
          189  +      for(i=0; i<pPk->nKeyCol; i++){
          190  +        Expr *p = sqlite3Expr(db, TK_ID, pTab->aCol[pPk->aiColumn[i]].zName);
          191  +        pEList = sqlite3ExprListAppend(pParse, pEList, p);
          192  +      }
          193  +      pLhs = sqlite3PExpr(pParse, TK_VECTOR, 0, 0);
          194  +      if( pLhs ){
          195  +        pLhs->x.pList = sqlite3ExprListDup(db, pEList, 0);
          196  +      }
          197  +    }
          198  +  }
   171    199   
   172    200     /* duplicate the FROM clause as it is needed by both the DELETE/UPDATE tree
   173    201     ** and the SELECT subtree. */
          202  +  pSrc->a[0].pTab = 0;
   174    203     pSelectSrc = sqlite3SrcListDup(pParse->db, pSrc, 0);
   175         -  if( pSelectSrc == 0 ) {
   176         -    sqlite3ExprListDelete(pParse->db, pEList);
   177         -    goto limit_where_cleanup;
   178         -  }
          204  +  pSrc->a[0].pTab = pTab;
          205  +  pSrc->a[0].pIBIndex = 0;
   179    206   
   180    207     /* generate the SELECT expression tree. */
   181         -  pSelect = sqlite3SelectNew(pParse,pEList,pSelectSrc,pWhere,0,0,
   182         -                             pOrderBy,0,pLimit,pOffset);
   183         -  if( pSelect == 0 ) return 0;
          208  +  pSelect = sqlite3SelectNew(pParse, pEList, pSelectSrc, pWhere, 0 ,0, 
          209  +      pOrderBy,0,pLimit,pOffset
          210  +  );
   184    211   
   185    212     /* now generate the new WHERE rowid IN clause for the DELETE/UDPATE */
   186         -  pWhereRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0);
   187         -  pInClause = pWhereRowid ? sqlite3PExpr(pParse, TK_IN, pWhereRowid, 0) : 0;
          213  +  pInClause = sqlite3PExpr(pParse, TK_IN, pLhs, 0);
   188    214     sqlite3PExprAddSelect(pParse, pInClause, pSelect);
   189    215     return pInClause;
   190         -
   191         -limit_where_cleanup:
   192         -  sqlite3ExprDelete(pParse->db, pWhere);
   193         -  sqlite3ExprListDelete(pParse->db, pOrderBy);
   194         -  sqlite3ExprDelete(pParse->db, pLimit);
   195         -  sqlite3ExprDelete(pParse->db, pOffset);
   196         -  return 0;
   197    216   }
   198    217   #endif /* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) */
   199    218          /*      && !defined(SQLITE_OMIT_SUBQUERY) */
   200    219   
   201    220   /*
   202    221   ** Generate code for a DELETE FROM statement.
   203    222   **
................................................................................
   204    223   **     DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
   205    224   **                 \________/       \________________/
   206    225   **                  pTabList              pWhere
   207    226   */
   208    227   void sqlite3DeleteFrom(
   209    228     Parse *pParse,         /* The parser context */
   210    229     SrcList *pTabList,     /* The table from which we should delete things */
   211         -  Expr *pWhere           /* The WHERE clause.  May be null */
          230  +  Expr *pWhere,          /* The WHERE clause.  May be null */
          231  +  ExprList *pOrderBy,    /* ORDER BY clause. May be null */
          232  +  Expr *pLimit,          /* LIMIT clause. May be null */
          233  +  Expr *pOffset          /* OFFSET clause. May be null */
   212    234   ){
   213    235     Vdbe *v;               /* The virtual database engine */
   214    236     Table *pTab;           /* The table from which records will be deleted */
   215    237     int i;                 /* Loop counter */
   216    238     WhereInfo *pWInfo;     /* Information about the WHERE clause */
   217    239     Index *pIdx;           /* For looping over indices of the table */
   218    240     int iTabCur;           /* Cursor number for the table */
................................................................................
   248    270   
   249    271     memset(&sContext, 0, sizeof(sContext));
   250    272     db = pParse->db;
   251    273     if( pParse->nErr || db->mallocFailed ){
   252    274       goto delete_from_cleanup;
   253    275     }
   254    276     assert( pTabList->nSrc==1 );
          277  +
   255    278   
   256    279     /* Locate the table which we want to delete.  This table has to be
   257    280     ** put in an SrcList structure because some of the subroutines we
   258    281     ** will be calling are designed to work with multiple tables and expect
   259    282     ** an SrcList* parameter instead of just a Table* parameter.
   260    283     */
   261    284     pTab = sqlite3SrcListLookup(pParse, pTabList);
................................................................................
   272    295   # define pTrigger 0
   273    296   # define isView 0
   274    297   #endif
   275    298   #ifdef SQLITE_OMIT_VIEW
   276    299   # undef isView
   277    300   # define isView 0
   278    301   #endif
          302  +
          303  +#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
          304  +  if( !isView ){
          305  +    pWhere = sqlite3LimitWhere(
          306  +        pParse, pTabList, pWhere, pOrderBy, pLimit, pOffset, "DELETE"
          307  +    );
          308  +    pOrderBy = 0;
          309  +    pLimit = pOffset = 0;
          310  +  }
          311  +#endif
   279    312   
   280    313     /* If pTab is really a view, make sure it has been initialized.
   281    314     */
   282    315     if( sqlite3ViewGetColumnNames(pParse, pTab) ){
   283    316       goto delete_from_cleanup;
   284    317     }
   285    318   
................................................................................
   320    353     sqlite3BeginWriteOperation(pParse, 1, iDb);
   321    354   
   322    355     /* If we are trying to delete from a view, realize that view into
   323    356     ** an ephemeral table.
   324    357     */
   325    358   #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
   326    359     if( isView ){
   327         -    sqlite3MaterializeView(pParse, pTab, pWhere, iTabCur);
          360  +    sqlite3MaterializeView(pParse, pTab, 
          361  +        pWhere, pOrderBy, pLimit, pOffset, iTabCur
          362  +    );
   328    363       iDataCur = iIdxCur = iTabCur;
          364  +    pOrderBy = 0;
          365  +    pLimit = pOffset = 0;
   329    366     }
   330    367   #endif
   331    368   
   332    369     /* Resolve the column names in the WHERE clause.
   333    370     */
   334    371     memset(&sNC, 0, sizeof(sNC));
   335    372     sNC.pParse = pParse;
................................................................................
   565    602       sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted", SQLITE_STATIC);
   566    603     }
   567    604   
   568    605   delete_from_cleanup:
   569    606     sqlite3AuthContextPop(&sContext);
   570    607     sqlite3SrcListDelete(db, pTabList);
   571    608     sqlite3ExprDelete(db, pWhere);
          609  +#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) 
          610  +  sqlite3ExprListDelete(db, pOrderBy);
          611  +  sqlite3ExprDelete(db, pLimit);
          612  +  sqlite3ExprDelete(db, pOffset);
          613  +#endif
   572    614     sqlite3DbFree(db, aToOpen);
   573    615     return;
   574    616   }
   575    617   /* Make sure "isView" and other macros defined above are undefined. Otherwise
   576    618   ** they may interfere with compilation of other functions in this file
   577    619   ** (or in another file, if this file becomes part of the amalgamation).  */
   578    620   #ifdef isView

Changes to src/fkey.c.

   721    721         }
   722    722         if( !p ) return;
   723    723         iSkip = sqlite3VdbeMakeLabel(v);
   724    724         sqlite3VdbeAddOp2(v, OP_FkIfZero, 1, iSkip); VdbeCoverage(v);
   725    725       }
   726    726   
   727    727       pParse->disableTriggers = 1;
   728         -    sqlite3DeleteFrom(pParse, sqlite3SrcListDup(db, pName, 0), 0);
          728  +    sqlite3DeleteFrom(pParse, sqlite3SrcListDup(db, pName, 0), 0, 0, 0, 0);
   729    729       pParse->disableTriggers = 0;
   730    730   
   731    731       /* If the DELETE has generated immediate foreign key constraint 
   732    732       ** violations, halt the VDBE and return an error at this point, before
   733    733       ** any modifications to the schema are made. This is because statement
   734    734       ** transactions are not able to rollback schema changes.  
   735    735       **

Changes to src/parse.y.

   749    749   /////////////////////////// The DELETE statement /////////////////////////////
   750    750   //
   751    751   %ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
   752    752   cmd ::= with(C) DELETE FROM fullname(X) indexed_opt(I) where_opt(W) 
   753    753           orderby_opt(O) limit_opt(L). {
   754    754     sqlite3WithPush(pParse, C, 1);
   755    755     sqlite3SrcListIndexedBy(pParse, X, &I);
   756         -  W = sqlite3LimitWhere(pParse, X, W, O, L.pLimit, L.pOffset, "DELETE");
   757         -  sqlite3DeleteFrom(pParse,X,W);
          756  +  sqlite3DeleteFrom(pParse,X,W,O,L.pLimit,L.pOffset); 
   758    757   }
   759    758   %endif
   760    759   %ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
   761    760   cmd ::= with(C) DELETE FROM fullname(X) indexed_opt(I) where_opt(W). {
   762    761     sqlite3WithPush(pParse, C, 1);
   763    762     sqlite3SrcListIndexedBy(pParse, X, &I);
   764         -  sqlite3DeleteFrom(pParse,X,W);
          763  +  sqlite3DeleteFrom(pParse,X,W,0,0,0);
   765    764   }
   766    765   %endif
   767    766   
   768    767   %type where_opt {Expr*}
   769    768   %destructor where_opt {sqlite3ExprDelete(pParse->db, $$);}
   770    769   
   771    770   where_opt(A) ::= .                    {A = 0;}
................................................................................
   775    774   //
   776    775   %ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
   777    776   cmd ::= with(C) UPDATE orconf(R) fullname(X) indexed_opt(I) SET setlist(Y)
   778    777           where_opt(W) orderby_opt(O) limit_opt(L).  {
   779    778     sqlite3WithPush(pParse, C, 1);
   780    779     sqlite3SrcListIndexedBy(pParse, X, &I);
   781    780     sqlite3ExprListCheckLength(pParse,Y,"set list"); 
   782         -  W = sqlite3LimitWhere(pParse, X, W, O, L.pLimit, L.pOffset, "UPDATE");
   783         -  sqlite3Update(pParse,X,Y,W,R);
          781  +  sqlite3Update(pParse,X,Y,W,R,O,L.pLimit,L.pOffset);
   784    782   }
   785    783   %endif
   786    784   %ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
   787    785   cmd ::= with(C) UPDATE orconf(R) fullname(X) indexed_opt(I) SET setlist(Y)
   788    786           where_opt(W).  {
   789    787     sqlite3WithPush(pParse, C, 1);
   790    788     sqlite3SrcListIndexedBy(pParse, X, &I);
   791    789     sqlite3ExprListCheckLength(pParse,Y,"set list"); 
   792         -  sqlite3Update(pParse,X,Y,W,R);
          790  +  sqlite3Update(pParse,X,Y,W,R,0,0,0);
   793    791   }
   794    792   %endif
   795    793   
   796    794   %type setlist {ExprList*}
   797    795   %destructor setlist {sqlite3ExprListDelete(pParse->db, $$);}
   798    796   
   799    797   setlist(A) ::= setlist(A) COMMA nm(X) EQ expr(Y). {

Changes to src/resolve.c.

   593    593       ** clause processing on UPDATE and DELETE statements.
   594    594       */
   595    595       case TK_ROW: {
   596    596         SrcList *pSrcList = pNC->pSrcList;
   597    597         struct SrcList_item *pItem;
   598    598         assert( pSrcList && pSrcList->nSrc==1 );
   599    599         pItem = pSrcList->a;
   600         -      if( !HasRowid(pItem->pTab) || pItem->pTab->pSelect!=0 ){
   601         -         sqlite3ErrorMsg(pParse, "ORDER BY and LIMIT not support for table %s",
   602         -                                 pItem->pTab->zName);
   603         -      }
          600  +      assert( HasRowid(pItem->pTab) && pItem->pTab->pSelect==0 );
   604    601         pExpr->op = TK_COLUMN;
   605    602         pExpr->pTab = pItem->pTab;
   606    603         pExpr->iTable = pItem->iCursor;
   607    604         pExpr->iColumn = -1;
   608    605         pExpr->affinity = SQLITE_AFF_INTEGER;
   609    606         break;
   610    607       }

Changes to src/sqliteInt.h.

  3760   3760   void sqlite3SelectDelete(sqlite3*, Select*);
  3761   3761   Table *sqlite3SrcListLookup(Parse*, SrcList*);
  3762   3762   int sqlite3IsReadOnly(Parse*, Table*, int);
  3763   3763   void sqlite3OpenTable(Parse*, int iCur, int iDb, Table*, int);
  3764   3764   #if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY)
  3765   3765   Expr *sqlite3LimitWhere(Parse*,SrcList*,Expr*,ExprList*,Expr*,Expr*,char*);
  3766   3766   #endif
  3767         -void sqlite3DeleteFrom(Parse*, SrcList*, Expr*);
  3768         -void sqlite3Update(Parse*, SrcList*, ExprList*, Expr*, int);
         3767  +void sqlite3DeleteFrom(Parse*, SrcList*, Expr*, ExprList*, Expr*, Expr*);
         3768  +void sqlite3Update(Parse*, SrcList*, ExprList*,Expr*,int,ExprList*,Expr*,Expr*);
  3769   3769   WhereInfo *sqlite3WhereBegin(Parse*,SrcList*,Expr*,ExprList*,ExprList*,u16,int);
  3770   3770   void sqlite3WhereEnd(WhereInfo*);
  3771   3771   LogEst sqlite3WhereOutputRowCount(WhereInfo*);
  3772   3772   int sqlite3WhereIsDistinct(WhereInfo*);
  3773   3773   int sqlite3WhereIsOrdered(WhereInfo*);
  3774   3774   int sqlite3WhereOrderedInnerLoop(WhereInfo*);
  3775   3775   int sqlite3WhereIsSorted(WhereInfo*);
................................................................................
  3885   3885   void sqlite3RegisterDateTimeFunctions(void);
  3886   3886   void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3*);
  3887   3887   int sqlite3SafetyCheckOk(sqlite3*);
  3888   3888   int sqlite3SafetyCheckSickOrOk(sqlite3*);
  3889   3889   void sqlite3ChangeCookie(Parse*, int);
  3890   3890   
  3891   3891   #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
  3892         -void sqlite3MaterializeView(Parse*, Table*, Expr*, int);
         3892  +void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,Expr*,int);
  3893   3893   #endif
  3894   3894   
  3895   3895   #ifndef SQLITE_OMIT_TRIGGER
  3896   3896     void sqlite3BeginTrigger(Parse*, Token*,Token*,int,int,IdList*,SrcList*,
  3897   3897                              Expr*,int, int);
  3898   3898     void sqlite3FinishTrigger(Parse*, TriggerStep*, Token*);
  3899   3899     void sqlite3DropTrigger(Parse*, SrcList*, int);

Changes to src/trigger.c.

   707    707   
   708    708       switch( pStep->op ){
   709    709         case TK_UPDATE: {
   710    710           sqlite3Update(pParse, 
   711    711             targetSrcList(pParse, pStep),
   712    712             sqlite3ExprListDup(db, pStep->pExprList, 0), 
   713    713             sqlite3ExprDup(db, pStep->pWhere, 0), 
   714         -          pParse->eOrconf
          714  +          pParse->eOrconf, 0, 0, 0
   715    715           );
   716    716           break;
   717    717         }
   718    718         case TK_INSERT: {
   719    719           sqlite3Insert(pParse, 
   720    720             targetSrcList(pParse, pStep),
   721    721             sqlite3SelectDup(db, pStep->pSelect, 0), 
................................................................................
   723    723             pParse->eOrconf
   724    724           );
   725    725           break;
   726    726         }
   727    727         case TK_DELETE: {
   728    728           sqlite3DeleteFrom(pParse, 
   729    729             targetSrcList(pParse, pStep),
   730         -          sqlite3ExprDup(db, pStep->pWhere, 0)
          730  +          sqlite3ExprDup(db, pStep->pWhere, 0), 0, 0, 0
   731    731           );
   732    732           break;
   733    733         }
   734    734         default: assert( pStep->op==TK_SELECT ); {
   735    735           SelectDest sDest;
   736    736           Select *pSelect = sqlite3SelectDup(db, pStep->pSelect, 0);
   737    737           sqlite3SelectDestInit(&sDest, SRT_Discard, 0);

Changes to src/update.c.

    87     87   *            onError   pTabList      pChanges             pWhere
    88     88   */
    89     89   void sqlite3Update(
    90     90     Parse *pParse,         /* The parser context */
    91     91     SrcList *pTabList,     /* The table in which we should change things */
    92     92     ExprList *pChanges,    /* Things to be changed */
    93     93     Expr *pWhere,          /* The WHERE clause.  May be null */
    94         -  int onError            /* How to handle constraint errors */
           94  +  int onError,           /* How to handle constraint errors */
           95  +  ExprList *pOrderBy,    /* ORDER BY clause. May be null */
           96  +  Expr *pLimit,          /* LIMIT clause. May be null */
           97  +  Expr *pOffset          /* OFFSET clause. May be null */
    95     98   ){
    96     99     int i, j;              /* Loop counters */
    97    100     Table *pTab;           /* The table to be updated */
    98    101     int addrTop = 0;       /* VDBE instruction address of the start of the loop */
    99    102     WhereInfo *pWInfo;     /* Information about the WHERE clause */
   100    103     Vdbe *v;               /* The virtual database engine */
   101    104     Index *pIdx;           /* For looping over indices */
................................................................................
   171    174   # define isView 0
   172    175   # define tmask 0
   173    176   #endif
   174    177   #ifdef SQLITE_OMIT_VIEW
   175    178   # undef isView
   176    179   # define isView 0
   177    180   #endif
          181  +
          182  +#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
          183  +  if( !isView ){
          184  +    pWhere = sqlite3LimitWhere(
          185  +        pParse, pTabList, pWhere, pOrderBy, pLimit, pOffset, "UPDATE"
          186  +    );
          187  +    pOrderBy = 0;
          188  +    pLimit = pOffset = 0;
          189  +  }
          190  +#endif
   178    191   
   179    192     if( sqlite3ViewGetColumnNames(pParse, pTab) ){
   180    193       goto update_cleanup;
   181    194     }
   182    195     if( sqlite3IsReadOnly(pParse, pTab, tmask) ){
   183    196       goto update_cleanup;
   184    197     }
................................................................................
   340    353     }
   341    354   
   342    355     /* If we are trying to update a view, realize that view into
   343    356     ** an ephemeral table.
   344    357     */
   345    358   #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
   346    359     if( isView ){
   347         -    sqlite3MaterializeView(pParse, pTab, pWhere, iDataCur);
          360  +    sqlite3MaterializeView(pParse, pTab, 
          361  +        pWhere, pOrderBy, pLimit, pOffset, iDataCur
          362  +    );
          363  +    pOrderBy = 0;
          364  +    pLimit = pOffset = 0;
   348    365     }
   349    366   #endif
   350    367   
   351    368     /* Resolve the column names in all the expressions in the
   352    369     ** WHERE clause.
   353    370     */
   354    371     if( sqlite3ResolveExprNames(&sNC, pWhere) ){
................................................................................
   724    741   
   725    742   update_cleanup:
   726    743     sqlite3AuthContextPop(&sContext);
   727    744     sqlite3DbFree(db, aXRef); /* Also frees aRegIdx[] and aToOpen[] */
   728    745     sqlite3SrcListDelete(db, pTabList);
   729    746     sqlite3ExprListDelete(db, pChanges);
   730    747     sqlite3ExprDelete(db, pWhere);
          748  +#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) 
          749  +  sqlite3ExprListDelete(db, pOrderBy);
          750  +  sqlite3ExprDelete(db, pLimit);
          751  +  sqlite3ExprDelete(db, pOffset);
          752  +#endif
   731    753     return;
   732    754   }
   733    755   /* Make sure "isView" and other macros defined above are undefined. Otherwise
   734    756   ** they may interfere with compilation of other functions in this file
   735    757   ** (or in another file, if this file becomes part of the amalgamation).  */
   736    758   #ifdef isView
   737    759    #undef isView

Added test/wherelfault.test.

            1  +# 2008 October 6
            2  +#
            3  +# The author disclaims copyright to this source code.  In place of
            4  +# a legal notice, here is a blessing:
            5  +#
            6  +#    May you do good and not evil.
            7  +#    May you find forgiveness for yourself and forgive others.
            8  +#    May you share freely, never taking more than you give.
            9  +#
           10  +#***********************************************************************
           11  +# This file implements regression tests for SQLite library.  The
           12  +# focus of this file is testing fault-injection with the 
           13  +# LIMIT ... OFFSET ... clause of UPDATE and DELETE statements.
           14  +#
           15  +
           16  +set testdir [file dirname $argv0]
           17  +source $testdir/tester.tcl
           18  +source $testdir/malloc_common.tcl
           19  +set testprefix wherelfault
           20  +
           21  +ifcapable !update_delete_limit {
           22  +  finish_test
           23  +  return
           24  +}
           25  +
           26  +do_execsql_test 1.0 {
           27  +  CREATE TABLE t1(a, b);
           28  +  INSERT INTO t1 VALUES(1, 'f');
           29  +  INSERT INTO t1 VALUES(2, 'e');
           30  +  INSERT INTO t1 VALUES(3, 'd');
           31  +  INSERT INTO t1 VALUES(4, 'c');
           32  +  INSERT INTO t1 VALUES(5, 'b');
           33  +  INSERT INTO t1 VALUES(6, 'a');
           34  +
           35  +  CREATE VIEW v1 AS SELECT a,b FROM t1;
           36  +  CREATE TABLE log(op, a);
           37  +
           38  +  CREATE TRIGGER v1del INSTEAD OF DELETE ON v1 BEGIN
           39  +    INSERT INTO log VALUES('delete', old.a);
           40  +  END;
           41  +
           42  +  CREATE TRIGGER v1upd INSTEAD OF UPDATE ON v1 BEGIN
           43  +    INSERT INTO log VALUES('update', old.a);
           44  +  END;
           45  +}
           46  +
           47  +faultsim_save_and_close
           48  +do_faultsim_test 1.1 -prep {
           49  +  faultsim_restore_and_reopen
           50  +  db eval {SELECT * FROM sqlite_master}
           51  +} -body {
           52  +  execsql { DELETE FROM v1 ORDER BY a LIMIT 3; }
           53  +} -test {
           54  +  faultsim_test_result {0 {}} 
           55  +}
           56  +
           57  +do_faultsim_test 1.2 -prep {
           58  +  faultsim_restore_and_reopen
           59  +  db eval {SELECT * FROM sqlite_master}
           60  +} -body {
           61  +  execsql { UPDATE v1 SET b = 555 ORDER BY a LIMIT 3 }
           62  +} -test {
           63  +  faultsim_test_result {0 {}} 
           64  +}
           65  +
           66  +#-------------------------------------------------------------------------
           67  +sqlite3 db test.db
           68  +do_execsql_test 2.1.0 {
           69  +  CREATE TABLE t2(a, b, c, PRIMARY KEY(a, b)) WITHOUT ROWID;
           70  +}
           71  +faultsim_save_and_close
           72  +
           73  +do_faultsim_test 2.1 -prep {
           74  +  faultsim_restore_and_reopen
           75  +  db eval {SELECT * FROM sqlite_master}
           76  +} -body {
           77  +  execsql { DELETE FROM t2 WHERE c=? ORDER BY a DESC LIMIT 10 }
           78  +} -test {
           79  +  faultsim_test_result {0 {}} 
           80  +}
           81  +
           82  +finish_test

Changes to test/wherelimit.test.

    34     34       COMMIT;
    35     35     }
    36     36     return {}
    37     37   }
    38     38   
    39     39   ifcapable {update_delete_limit} {
    40     40   
           41  +  execsql { CREATE TABLE t1(x, y) }
           42  +
    41     43     # check syntax error support
    42     44     do_test wherelimit-0.1 {
    43     45       catchsql {DELETE FROM t1 ORDER BY x}
    44     46     } {1 {ORDER BY without LIMIT on DELETE}}
    45     47     do_test wherelimit-0.2 {
    46     48       catchsql {DELETE FROM t1 WHERE x=1 ORDER BY x}
    47     49     } {1 {ORDER BY without LIMIT on DELETE}}
    48     50     do_test wherelimit-0.3 {
    49     51       catchsql {UPDATE t1 SET y=1 WHERE x=1 ORDER BY x}
    50     52     } {1 {ORDER BY without LIMIT on UPDATE}}
           53  +
           54  +  execsql { DROP TABLE t1 }
    51     55   
    52     56     # no AS on table sources
    53     57     do_test wherelimit-0.4 {
    54     58       catchsql {DELETE FROM t1 AS a WHERE x=1}
    55     59     } {1 {near "AS": syntax error}}
    56     60     do_test wherelimit-0.5 {
    57     61       catchsql {UPDATE t1 AS a SET y=1 WHERE x=1}
................................................................................
   297    301       BEGIN
   298    302         DELETE FROM t1 WHERE rowid=old.r;
   299    303         DELETE FROM t2 WHERE rowid=old.r;
   300    304       END;
   301    305     } {}
   302    306     do_catchsql_test wherelimit-4.2 {
   303    307       DELETE FROM tv WHERE 1 LIMIT 2;
   304         -  } {1 {ORDER BY and LIMIT not support for table tv}}
          308  +  } {0 {}}
   305    309     do_catchsql_test wherelimit-4.3 {
   306    310       DELETE FROM tv WHERE 1 ORDER BY a LIMIT 2;
   307         -  } {1 {ORDER BY and LIMIT not support for table tv}}
          311  +  } {0 {}}
   308    312     do_execsql_test wherelimit-4.10 {
   309    313       CREATE TABLE t3(a,b,c,d TEXT, PRIMARY KEY(a,b)) WITHOUT ROWID;
   310    314       INSERT INTO t3(a,b,c,d) VALUES(1,2,3,4),(5,6,7,8),(9,10,11,12);
   311    315     } {}
   312    316     do_catchsql_test wherelimit-4.11 {
   313    317       DELETE FROM t3 WHERE a=5 LIMIT 2;
   314         -  } {1 {ORDER BY and LIMIT not support for table t3}}
          318  +  } {0 {}}
   315    319     do_execsql_test wherelimit-4.12 {
   316    320       SELECT a,b,c,d FROM t3 ORDER BY 1;
   317         -  } {1 2 3 4 5 6 7 8 9 10 11 12}
          321  +  } {1 2 3 4 9 10 11 12}
   318    322   
   319    323   }
   320    324   
   321    325   finish_test

Added test/wherelimit2.test.

            1  +# 2008 October 6
            2  +#
            3  +# The author disclaims copyright to this source code.  In place of
            4  +# a legal notice, here is a blessing:
            5  +#
            6  +#    May you do good and not evil.
            7  +#    May you find forgiveness for yourself and forgive others.
            8  +#    May you share freely, never taking more than you give.
            9  +#
           10  +#***********************************************************************
           11  +# This file implements regression tests for SQLite library.  The
           12  +# focus of this file is testing the LIMIT ... OFFSET ... clause
           13  +#  of UPDATE and DELETE statements.
           14  +#
           15  +
           16  +set testdir [file dirname $argv0]
           17  +source $testdir/tester.tcl
           18  +set testprefix wherelimit2
           19  +
           20  +ifcapable !update_delete_limit {
           21  +  finish_test
           22  +  return
           23  +}
           24  +
           25  +#-------------------------------------------------------------------------
           26  +# Test with views and INSTEAD OF triggers.
           27  +#
           28  +do_execsql_test 1.0 {
           29  +  CREATE TABLE t1(a, b);
           30  +  INSERT INTO t1 VALUES(1, 'f');
           31  +  INSERT INTO t1 VALUES(2, 'e');
           32  +  INSERT INTO t1 VALUES(3, 'd');
           33  +  INSERT INTO t1 VALUES(4, 'c');
           34  +  INSERT INTO t1 VALUES(5, 'b');
           35  +  INSERT INTO t1 VALUES(6, 'a');
           36  +
           37  +  CREATE VIEW v1 AS SELECT a,b FROM t1;
           38  +  CREATE TABLE log(op, a);
           39  +
           40  +  CREATE TRIGGER v1del INSTEAD OF DELETE ON v1 BEGIN
           41  +    INSERT INTO log VALUES('delete', old.a);
           42  +  END;
           43  +
           44  +  CREATE TRIGGER v1upd INSTEAD OF UPDATE ON v1 BEGIN
           45  +    INSERT INTO log VALUES('update', old.a);
           46  +  END;
           47  +}
           48  +
           49  +do_execsql_test 1.1 {
           50  +  DELETE FROM v1 ORDER BY a LIMIT 3;
           51  +  SELECT * FROM log; DELETE FROM log;
           52  +} {
           53  +  delete 1 delete 2 delete 3
           54  +}
           55  +do_execsql_test 1.2 {
           56  +  DELETE FROM v1 ORDER BY b LIMIT 3;
           57  +  SELECT * FROM log; DELETE FROM log;
           58  +} {
           59  +  delete 6 delete 5 delete 4
           60  +}
           61  +do_execsql_test 1.3 {
           62  +  UPDATE v1 SET b = 555 ORDER BY a LIMIT 3;
           63  +  SELECT * FROM log; DELETE FROM log;
           64  +} {
           65  +  update 1 update 2 update 3
           66  +}
           67  +do_execsql_test 1.4 {
           68  +  UPDATE v1 SET b = 555 ORDER BY b LIMIT 3;
           69  +  SELECT * FROM log; DELETE FROM log;
           70  +} {
           71  +  update 6 update 5 update 4
           72  +}
           73  +
           74  +#-------------------------------------------------------------------------
           75  +# Simple test using WITHOUT ROWID table.
           76  +#
           77  +do_execsql_test 2.1.0 {
           78  +  CREATE TABLE t2(a, b, c, PRIMARY KEY(a, b)) WITHOUT ROWID;
           79  +  INSERT INTO t2 VALUES(1, 1, 'h');
           80  +  INSERT INTO t2 VALUES(1, 2, 'g');
           81  +  INSERT INTO t2 VALUES(2, 1, 'f');
           82  +  INSERT INTO t2 VALUES(2, 2, 'e');
           83  +  INSERT INTO t2 VALUES(3, 1, 'd');
           84  +  INSERT INTO t2 VALUES(3, 2, 'c');
           85  +  INSERT INTO t2 VALUES(4, 1, 'b');
           86  +  INSERT INTO t2 VALUES(4, 2, 'a');
           87  +}
           88  +
           89  +do_execsql_test 2.1.1 {
           90  +  BEGIN;
           91  +    DELETE FROM t2 WHERE b=1 ORDER BY c LIMIT 2;
           92  +    SELECT c FROM t2 ORDER BY 1;
           93  +  ROLLBACK;
           94  +} {a c e f g h}
           95  +
           96  +do_execsql_test 2.1.2 {
           97  +  BEGIN;
           98  +    UPDATE t2 SET c=NULL ORDER BY a, b DESC LIMIT 3 OFFSET 1;
           99  +    SELECT a, b, c FROM t2;
          100  +  ROLLBACK;
          101  +} {
          102  +  1 1 {} 
          103  +  1 2 g 
          104  +  2 1 {} 
          105  +  2 2 {} 
          106  +  3 1 d 
          107  +  3 2 c 
          108  +  4 1 b 
          109  +  4 2 a
          110  +}
          111  +
          112  +do_execsql_test 2.2.0 {
          113  +  DROP TABLE t2;
          114  +  CREATE TABLE t2(a INTEGER PRIMARY KEY, b, c) WITHOUT ROWID;
          115  +  INSERT INTO t2 VALUES(1, 1, 'h');
          116  +  INSERT INTO t2 VALUES(2, 2, 'g');
          117  +  INSERT INTO t2 VALUES(3, 1, 'f');
          118  +  INSERT INTO t2 VALUES(4, 2, 'e');
          119  +  INSERT INTO t2 VALUES(5, 1, 'd');
          120  +  INSERT INTO t2 VALUES(6, 2, 'c');
          121  +  INSERT INTO t2 VALUES(7, 1, 'b');
          122  +  INSERT INTO t2 VALUES(8, 2, 'a');
          123  +}
          124  +
          125  +do_execsql_test 2.2.1 {
          126  +  BEGIN;
          127  +    DELETE FROM t2 WHERE b=1 ORDER BY c LIMIT 2;
          128  +    SELECT c FROM t2 ORDER BY 1;
          129  +  ROLLBACK;
          130  +} {a c e f g h}
          131  +
          132  +do_execsql_test 2.2.2 {
          133  +  BEGIN;
          134  +    UPDATE t2 SET c=NULL ORDER BY a DESC LIMIT 3 OFFSET 1;
          135  +    SELECT a, b, c FROM t2;
          136  +  ROLLBACK;
          137  +} {
          138  +  1 1 h
          139  +  2 2 g 
          140  +  3 1 f
          141  +  4 2 e
          142  +  5 1 {}
          143  +  6 2 {} 
          144  +  7 1 {} 
          145  +  8 2 a
          146  +}
          147  +
          148  +#-------------------------------------------------------------------------
          149  +# Test using a virtual table
          150  +#
          151  +ifcapable fts5 {
          152  +  do_execsql_test 3.0 {
          153  +    CREATE VIRTUAL TABLE ft USING fts5(x);
          154  +    INSERT INTO ft(rowid, x) VALUES(-45,   'a a');
          155  +    INSERT INTO ft(rowid, x) VALUES(12,    'a b');
          156  +    INSERT INTO ft(rowid, x) VALUES(444,   'a c');
          157  +    INSERT INTO ft(rowid, x) VALUES(12300, 'a d');
          158  +    INSERT INTO ft(rowid, x) VALUES(25400, 'a c');
          159  +    INSERT INTO ft(rowid, x) VALUES(25401, 'a b');
          160  +    INSERT INTO ft(rowid, x) VALUES(50000, 'a a');
          161  +  }
          162  +
          163  +  do_execsql_test 3.1.1 {
          164  +    BEGIN;
          165  +      DELETE FROM ft ORDER BY rowid LIMIT 3;
          166  +      SELECT x FROM ft;
          167  +    ROLLBACK;
          168  +  } {{a d} {a c} {a b} {a a}}
          169  +
          170  +  do_execsql_test 3.1.2 {
          171  +    BEGIN;
          172  +      DELETE FROM ft WHERE ft MATCH 'a' ORDER BY rowid LIMIT 3;
          173  +      SELECT x FROM ft;
          174  +    ROLLBACK;
          175  +  } {{a d} {a c} {a b} {a a}}
          176  +  
          177  +  do_execsql_test 3.1.3 {
          178  +    BEGIN;
          179  +      DELETE FROM ft WHERE ft MATCH 'b' ORDER BY rowid ASC LIMIT 1 OFFSET 1;
          180  +      SELECT rowid FROM ft;
          181  +    ROLLBACK;
          182  +  } {-45 12 444 12300 25400 50000}
          183  +
          184  +  do_execsql_test 3.2.1 {
          185  +    BEGIN;
          186  +      UPDATE ft SET x='hello' ORDER BY rowid LIMIT 2 OFFSET 2;
          187  +      SELECT x FROM ft;
          188  +    ROLLBACK;
          189  +  } {{a a} {a b} hello hello {a c} {a b} {a a}}
          190  +
          191  +  do_execsql_test 3.2.2 {
          192  +    BEGIN;
          193  +      UPDATE ft SET x='hello' WHERE ft MATCH 'a' 
          194  +          ORDER BY rowid DESC LIMIT 2 OFFSET 2;
          195  +      SELECT x FROM ft;
          196  +    ROLLBACK;
          197  +  } {{a a} {a b} {a c} hello hello {a b} {a a}}
          198  +} ;# fts5
          199  +
          200  +#-------------------------------------------------------------------------
          201  +# Test using INDEXED BY clauses.
          202  +#
          203  +do_execsql_test 4.0 {
          204  +  CREATE TABLE x1(a INTEGER PRIMARY KEY, b, c, d);
          205  +  CREATE INDEX x1bc ON x1(b, c);
          206  +  INSERT INTO x1 VALUES(1,1,1,1);
          207  +  INSERT INTO x1 VALUES(2,1,2,2);
          208  +  INSERT INTO x1 VALUES(3,2,1,3);
          209  +  INSERT INTO x1 VALUES(4,2,2,3);
          210  +  INSERT INTO x1 VALUES(5,3,1,2);
          211  +  INSERT INTO x1 VALUES(6,3,2,1);
          212  +}
          213  +
          214  +do_execsql_test 4.1 {
          215  +  BEGIN;
          216  +    DELETE FROM x1 ORDER BY a LIMIT 2;
          217  +    SELECT a FROM x1;
          218  +  ROLLBACK;
          219  +} {3 4 5 6}
          220  +
          221  +do_catchsql_test 4.2 {
          222  +  DELETE FROM x1 INDEXED BY x1bc WHERE d=3 LIMIT 1;
          223  +} {1 {no query solution}}
          224  +
          225  +do_execsql_test 4.3 {
          226  +  DELETE FROM x1 INDEXED BY x1bc WHERE b=3 LIMIT 1;
          227  +  SELECT a FROM x1;
          228  +} {1 2 3 4 6}
          229  +
          230  +do_catchsql_test 4.4 {
          231  +  UPDATE x1 INDEXED BY x1bc SET d=5 WHERE d=3 LIMIT 1;
          232  +} {1 {no query solution}}
          233  +
          234  +do_execsql_test 4.5 {
          235  +  UPDATE x1 INDEXED BY x1bc SET d=5 WHERE b=2 LIMIT 1;
          236  +  SELECT a, d FROM x1;
          237  +} {1 1 2 2 3 5 4 3 6 1}
          238  +
          239  +#-------------------------------------------------------------------------
          240  +# Test using object names that require quoting.
          241  +#
          242  +do_execsql_test 5.0 {
          243  +  CREATE TABLE "x y"("a b" PRIMARY KEY, "c d") WITHOUT ROWID;
          244  +  CREATE INDEX xycd ON "x y"("c d");
          245  +
          246  +  INSERT INTO "x y" VALUES('a', 'a');
          247  +  INSERT INTO "x y" VALUES('b', 'b');
          248  +  INSERT INTO "x y" VALUES('c', 'c');
          249  +  INSERT INTO "x y" VALUES('d', 'd');
          250  +  INSERT INTO "x y" VALUES('e', 'a');
          251  +  INSERT INTO "x y" VALUES('f', 'b');
          252  +  INSERT INTO "x y" VALUES('g', 'c');
          253  +  INSERT INTO "x y" VALUES('h', 'd');
          254  +}
          255  +
          256  +do_execsql_test 5.1 {
          257  +  BEGIN;
          258  +    DELETE FROM "x y" WHERE "c d"!='e' ORDER BY "c d" LIMIT 2 OFFSET 2;
          259  +    SELECT * FROM "x y" ORDER BY 1;
          260  +  ROLLBACK;
          261  +} {
          262  +  a a c c d d e a g c h d
          263  +}
          264  +
          265  +do_execsql_test 5.2 {
          266  +  BEGIN;
          267  +    UPDATE "x y" SET "c d"='e' WHERE "c d"!='e' ORDER BY "c d" LIMIT 2 OFFSET 2;
          268  +    SELECT * FROM "x y" ORDER BY 1;
          269  +  ROLLBACK;
          270  +} {
          271  +  a a b e c c d d e a f e g c h d
          272  +}
          273  +
          274  +proc log {args} { lappend ::log {*}$args }
          275  +db func log log
          276  +do_execsql_test 5.3 {
          277  +  CREATE VIEW "v w" AS SELECT * FROM "x y";
          278  +  CREATE TRIGGER tr1 INSTEAD OF DELETE ON "v w" BEGIN
          279  +    SELECT log(old."a b", old."c d");
          280  +  END;
          281  +  CREATE TRIGGER tr2 INSTEAD OF UPDATE ON "v w" BEGIN
          282  +    SELECT log(new."a b", new."c d");
          283  +  END;
          284  +}
          285  +
          286  +do_test 5.4 {
          287  +  set ::log {}
          288  +  execsql { DELETE FROM "v w" ORDER BY "a b" LIMIT 3 }
          289  +  set ::log
          290  +} {a a b b c c}
          291  +
          292  +do_test 5.5 {
          293  +  set ::log {}
          294  +  execsql { UPDATE "v w" SET "a b" = "a b" || 'x' ORDER BY "a b" LIMIT 5; }
          295  +  set ::log
          296  +} {ax a bx b cx c dx d ex a}
          297  +
          298  +
          299  +finish_test
          300  +